TCP实现服务器与客户端的通信流程实例

描述

主要函数:

------------------------------------------

//服务器端---服务器是一个被动的角色

1.socket          //买一个手机

2.bind            //SIM卡 绑定一个手机号(ip+port)

3.listen          //待机(等待电话打入)

4.accept          //接听电话

5.read/write      //通话

6.close           //挂机

//客户端---客户端是一个主动发起请求的一端

1.socket           //买一个手机

2.bind(可选的)     //SIM卡(绑定号码)

3.connect          //拨打电话

4.read/write       //通话

5.close            //挂机

//1.socket  ---- 插口 

int socket(int domain, int type, int protocol);

功能:  创建通信的一端 (socket)

参数:

@domain    //"域" --范围

AF_INET  //IPV4 协议的通信

@type      SOCK_STREAM //TCP (流式套接字)

@protocol  0           //LINUX下 流式套接字 ==>TCP

//协议 

返回值:

成功 对应的socket文件描述符

失败 返回-1 

注意:

文件描述符:

实际上就是 创建好的 socket的一个标识符

//2.bind 

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

功能:

给指定的 socket 绑定地址信息

参数:

@sockfd    //表示操作的socket

@addr      //填充的地址信息(ip + port)

@addrlen   //地址信息结构体的大小

返回值:

成功 0

失败 -1

//通用的地址结构

struct sockaddr {

sa_family_t sa_family; //AF_INET //IPV4的协议

char       sa_data[14];//(ip+port)

}

//网络通信的地址结构(internet)

struct sockaddr_in {

sa_family_t    sin_family; /* address family: AF_INET */

in_port_t      sin_port;   /* port in network byte order */

struct in_addr sin_addr;   /* internet address */

};

/* Internet address. */

struct in_addr {

uint32_t       s_addr;      /* address in network byte order */

};

//1.定义一个 地址结构体变量

struct sockaddr_in addr;

bzero(&addr,sizeof(addr)); //清0的函数

//2.之后进行信息填充

addr.sin_family = AF_INET;

addr.sin_port = htons(8888);

addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 

//127.0.0.1 是回环测试的地址

//3.进行绑定

if(bind(sockfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)

{

perror("bind fail");

return 0;

}

//3.listen  --- 设置监听 ---作用:让操作系统监控是否有客户端发起连接

int listen(int sockfd, int backlog);

功能:

设置监听 

参数:

@sockfd    //监听套接字

@backlog   //监听队列的大小

返回值

成功 0

失败 -1

listenfd 

--------------监听队列------------------

fd1 fd2 fd3 fd4

|

-|--------------------------------------

|

\---->建立好连接的套接字 

accept函数获取已连接的套接字 返回对应

的标识符 

|--->后面的读写操作 都是通过这个标识符

进行的

-----------------------------------------------

accept(); //accept 从监听队列中获得已连接的的socket,返回一个标示符来表示已连接的socket

//后续通过已连接的socket进行通信

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

功能: 获取连接

参数:

@sockfd    //监听套接字的fd(标识符)

@addr     //来电显示(保存对端的地址信息)(ip+port)

@addrlen  //表示 addr 参数对应类型的大小,值结果参数 --- 就是在用的时候,必须先赋一个初值,最后函数调用完成

//通过该参数,返回一个结果值

返回值:

成功 已连接的socket的标识符

失败 -1

//connect ---发起连接

int connect(

int sockfd,  //表示 进行通信的 socket的标识符

const struct sockaddr *addr, //对端的地址信息(ip+port)

socklen_t addrlen); //表示的是 addr 参数类型的长度

参数:

@sockfd   //通过socket函数获得的fd

@addr     //服务器端的地址

@addrlen  //参数addr类型的大小 

//数据流向  fd --> buf (count 表示一次读取多少个字节)

ssize_t read(int fd, void *buf, size_t count);

//数据流向  buf--> fd (count 表示一次写多少个字节)

ssize_t    write(int  fd,    const  void  *buf, size_t count);

参数:

@fd 就是要操作的 socket对应的 标示符

@buf 保存数据的一块内存首地址

@count 一次操作的字节数

confd 

char buf[] = "hello QCXY\n";

write(confd,buf,strlen(buf)); //写 数据到socket中

//读数据出来

char rbuf[1024] = {0}; //表示申请了一块1024个字节大小

//的内存空间

read(confd,rbuf,sizeof(rbuf)); //读取数据

//练习:

实现 客户端 向服务器发送数据

服务器回发数据的功能

client       -----        server

scanf();  ---(1)---->        read 之后printf

read     <--(2)----         write

printf

循环做,结束条件

当客户端输入 "quit"字符串时 客户端结束

怎么判断客户端读到的是"quit"

c语言中

"quit" == buf; (X) //不能这么写

//字符串的比较函数

strcmp("quit",buf);

strncmp("quit",buf,4);

客户端的程序:

#include

#include           /* See NOTES */

#include

#include

#include

#include

#include

//./client 127.0.0.1 8888 

int main(int argc, const char *argv[])

{

int fd;

int ret = 0;

char buf[1024] = {0};

char rbuf[1024] = {0};

//处理命令行参数

//1.socket(手机)

//2.bind(电话卡)

//3.connect (拨打电话)

//处理命令行参数

if(argc != 3)

{

printf("Usage: %s \n",argv[0]);

return -1;

}

//1.socket(手机)

fd = socket(AF_INET,SOCK_STREAM,0);

if(fd < 0) //出错处理

{

perror("socket fail");

return -1;

}

printf("fd = %d\n",fd);

//2.bind(电话卡)---绑定的是客户端自己的地址信息

//客户端地址信息

//1.定义一个 地址结构体变量

struct sockaddr_in cli_addr;

bzero(&cli_addr,sizeof(cli_addr)); //清0的函数

//2.之后进行信息填充

cli_addr.sin_family = AF_INET;

cli_addr.sin_port = htons(7777);

cli_addr.sin_addr.s_addr = inet_addr(argv[1]);

if(bind(fd,(struct sockaddr*)&cli_addr,sizeof(cli_addr)) < 0)

{

perror("bind fail");

return -1;

}

//服务器端的地址信息

//1.定义一个 地址结构体变量

struct sockaddr_in addr;

bzero(&addr,sizeof(addr)); //清0的函数

//2.之后进行信息填充

addr.sin_family = AF_INET;

addr.sin_port = htons(atoi(argv[2]));

addr.sin_addr.s_addr = inet_addr(argv[1]); 

//3.connect (拨打电话)

if(connect(fd,(struct sockaddr*)&addr,sizeof(addr))<0)

{

perror("connect fail");

return -1;

}

printf("connect success\n");

//通信过程

while(1)

{

//客户端从键盘获得数据 

//数据流向stdin --> buf

fgets(buf,sizeof(buf),stdin); //stdin表示是从键盘获得数据

//发送给服务器

write(fd,buf,strlen(buf));

//接受服务器回发的消息

ret = read(fd,rbuf,sizeof(rbuf));

//如果回发的消息是

//quit 

//则结束

rbuf[ret] = '\0';

printf("rbuf = %s\n",rbuf);

if(strncmp("quit",buf,4) == 0)

{

close(fd);

break;

}

}

return 0;

}

服务端

#include

#include           /* See NOTES */

#include

#include

#include

#include

//./server 127.0.0.1 8888

int main(int argc, const char *argv[])

{

int fd = 0;

int connfd = 0;

int ret = 0; 

char buf[1024] = {0};

//处理命令行参数

if(argc != 3)

{

printf("Usage: %s \n",argv[0]);

return -1;

}

//1.socket 创建套接字

fd = socket(AF_INET,SOCK_STREAM,0);

if(fd < 0) //出错处理

{

perror("socket fail");

return -1;

}

printf("fd = %d\n",fd);

//2.绑定

//1.准备地址信息

//2.绑定

//

//1.定义一个 地址结构体变量

struct sockaddr_in addr;

bzero(&addr,sizeof(addr)); //清0的函数

//2.之后进行信息填充

addr.sin_family = AF_INET;

addr.sin_port = htons(atoi(argv[2]));

addr.sin_addr.s_addr = inet_addr(argv[1]); 

//127.0.0.1 是回环测试的地址

//3.进行绑定

if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < 0)

{

perror("bind fail");

return 0;

}

printf("bind success\n");

//4.设置监听

if(listen(fd,5) < 0)

{

perror("listen fail");

return -1;

}

struct sockaddr_in peer_addr;

socklen_t addrlen = sizeof(peer_addr); 

//5.获取连接 -- 接听电话

while(1) //可以不断接受客户端的请求

{

//connfd =  accept(fd,NULL,NULL);

connfd =  accept(fd,(struct sockaddr*)&peer_addr,&addrlen);

if(connfd < 0)

{

perror("accept fail");

return -1;

}

printf("connfd = %d\n",connfd);

printf("-----------------------\n");

printf("ip = %s\n",inet_ntoa(peer_addr.sin_addr));

printf("port = %d\n",ntohs(peer_addr.sin_port));

printf("-----------------------\n");

//通信过程

//效果实现数据回发

while(1)

{

//read 与 write 的返回值 大于0 时表示的是

//一次成功操作到的字节数

ret = read(connfd,buf,sizeof(buf));

//hello

buf[ret] = '\0'; //添加'\0'--转换成字符串

printf("buf = %s\n",buf);//字符串打印 需要

//结束标志 '\0'

if(ret == 0 || strncmp(buf,"quit",4) == 0)

{

close(connfd);

break;

}

write(connfd,buf,ret);

}

} //telnet

return 0;

}

补充:

可以用如下函数替代read,write

ssize_t recv(int sockfd,  void* buf,size_t len,int flags);

ssize_t send(int sockfd,const void *buf,size_t len,int flags);

@sockfd //进行操作的socket的文件描述符

@buf    //保存数据的首地址

@len    //一次操作的字节数

@flags   //标志0--默认的操作方式(阻塞)

返回值:

成功  成功操作的字节数

失败   -1&errno

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分