TCP多客户端通信实现
来源:互联网 发布:考勤统计软件 编辑:程序博客网 时间:2024/06/05 14:37
功能要求:当客户端与服务器成功连接后,客户端自动向服务器发送一个0,服务器接收返回一个1,客户端接收返回一个2.。。。。一直到服务器向客户端发送一个9,停止连接,然后过十秒钟重新连接,并且重复相同的工作,每个客户端也做相同的工作,要求输出打印发送的数字以及对应的客户端id和对应的系统时间
实现方法:
使用单进程,tcp实现。
客户端:
1.创建一个循环,每10s循环一次,每次循环需要close(fd)。
2.在循环内部创建socket连接,创建网络地址,连接到服务器。
3.先发送数据0;
4.循环接收服务器发送过来的数据,加一,再发送给服务器。
服务器:
1.首先定义一个存放客户端文件描述符的数组,并初始化为-1.
2.创建socket连接,创建网络地址,将socket连接fd绑定到网络地址。
4.listen
5.循环监听各文件描述符(fd,cfd[i]),如果可读,则检查是哪个文件描述符的行为。
6.如果是fd,则表明有新客户端,连接,然后accept,新的cfd存放到数组中
7.如果是cfd[i],则表明,已连接的客户端可读,则读取数据,并将接收到的数据+1,返回给客户端。
注意点:
1.服务端创建的fd,需要设置为非阻塞状态。
默认情况下,accept函数是阻塞的,就是说,在没有新连接请求来的情况下 (listen监听),accept一直在这里等,函数没有返回,也就是说,卡在这个地方而不会创建新的socket,程序不会往下运行。如果设置成非阻塞(fcntl函数),accept在没有连接请求的情况下,马上返回,也就是说,程序继续往下运行,但是accept会返回一个负数,表示客户端socket没有创建成功。
2.select系统调用
是用来监视多个文件描述符状态变化的。程序会停在select这里等待,直到被监视的文件描述符中某一个或者多个发生了状态改变,或者超时才返回。
select机制中提供了一fd_set数据结构,实际上就是一long型数组,其中每一个元素都与文件描述符有关系(相当于把所有的文件描述符都存放到这个数组中),每次调用select之前都需要调用FD_ZERO清空fd_set集,然后将所有文件描述符添加到fd_set集中,当调用select时,select会检查当前fd_set中的文件描述符,将那些可读或者可写的文件描述符留下,清理掉其他没有状态变化的文件描述符,然后配合FD_ISSET找出那些文件描述符是可读或可写的,然后对这些文件描述符进行读写操作。
函数原型:
#include <sys/select.h> #include <sys/time.h>#include <sys/types.h>#include <unistd.h>int select(int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, const struct timeval * timeout);
ndfs:select监视的文件句柄数,视进程中打开的文件数而定,一般设为要监视各文件中的最大文件描述符值加1。
readfds:这个文件描述符集合监视文件集中的任何文件是否有数据可读,当select函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符。
writefds:这个文件描述符集合监视文件集中的任何文件是否有数据可写,当select函数返回的时候,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符。
exceptfds:这个文件集将监视文件集中的任何文件是否发生错误,其实,它可用于其他的用途,例如,监视带外数据OOB,带外数据使用MSG_OOB标志发送到套接字上。当select函数返回的时候,exceptfds将清除其中的其他文件描述符,只留下标记有OOB数据的文件描述符。
timeout:本次select()的超时结束时间。这个参数至关重要,它可以使select处于三种状态:
(1)若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
(2)若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
(3)timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
函数的返回值:
正值:表示监视的文件集中有文件描述符符合要求
零值:表示select监视超时
负值:表示发生了错误,错误值由errno指定。
3.超时机制
struct timeval timeout={4,0};设置select超时时间4s,当select与while配合使用是,每次循环都必须重新给timeout赋值
4.r=recv(cfd,buf,sizeof(buf),MSG_DONTWAIT);
第三个参数若使用MSG_WAITALL,表示等待接收满或者断开连接才返回。
第三个参数若使用MSG_DONTWAIT,表示有数据就接收,没有立即返回,此时如果客户端断开连接或者发送完数据,则返回0,不过此时select还是会认为此fd可读,所以还会继续执行default,因此需要手动在服务端close(fd),并置为-1,不过下次循环是不能将该fd添加到fds否则在FD_SET时,会出现段错误,如果使用fcntl设置了fd为非阻塞模式,数据还没有发给接收端时,调用recv就会返回-1,并且errno会被设为EAGAIN.但此时连接应为正常。
客户端代码
#include<stdio.h>#include<errno.h>#include<unistd.h>#include<string.h>#include<stdlib.h>#include<sys/socket.h>#include<arpa/inet.h>#include<fcntl.h>#define SOCK_PORT7777#define SERVER_IP"192.168.0.21"#define SLEEP_TIME 3int main(){int fd,r;while(1){//每10秒重新连接服务器int send_v=0,rec_v=0;//创建socket连接fd=socket(AF_INET,SOCK_STREAM,0);if(fd==-1){printf("create socket failed\n");return -1;} //创建网络地址struct sockaddr_in addr={};addr.sin_family=AF_INET;addr.sin_port=htons(SOCK_PORT);addr.sin_addr.s_addr=inet_addr(SERVER_IP); //连接服务器r=connect(fd,(struct sockaddr*)&addr,sizeof(addr));if(r==-1){printf("connect:%s\n",strerror(errno));printf("connect failed\n");return -1;}printf("connect succeed\n"); //sendr=send(fd,&send_v,4,0);if(r==-1){printf("send failed\n");return -1;}while(1){ //recv r=recv(fd,&rec_v,4,0);if(r==-1){printf("recv failed\n");return -1;}//printf("recv succeed\n");printf("rec:%d\n",rec_v);if(rec_v==9){break;}int temp_v=rec_v+1;r=send(fd,&temp_v,4,0);if(r==-1){printf("send failed\n");return -1;}printf("send:%d\n",temp_v);//sleep(1);//printf("send succeed\n");}close(fd);sleep(SLEEP_TIME);}close(fd);}
服务端
#include<stdio.h>#include<errno.h>#include<unistd.h>#include<string.h>#include<stdlib.h>#include<sys/socket.h>#include<arpa/inet.h>#include<fcntl.h>#include<netinet/in.h>#include<time.h>#define SOCK_PORT7777#define CLIENT_NUMB 20#define MAX_LISTEN_NUMB 10void getlocal_time(void){ time_t nowtime; struct tm *timeinfo; time( &nowtime ); timeinfo = localtime( &nowtime ); int year, month, day,hour,sec,min; year = timeinfo->tm_year + 1900; month = timeinfo->tm_mon + 1; day = timeinfo->tm_mday; hour=timeinfo->tm_hour; sec=timeinfo->tm_sec; min=timeinfo->tm_min; printf(" time:%d.%d.%d %d:%d:%d\n", year, month, day,hour,min,sec);}/*创建socket连接创建网络地址将文件描述符绑定到网络地址上return:成功:文件描述符fd失败:-1*/int creat_bind_socket(void){//printf("creat_bind_socket\n");int fd,r; // create socket创建socket连接fd=socket(AF_INET,SOCK_STREAM,0); //set this file descriptor noblock设置文件描述符fd为非堵塞fcntl(fd,F_SETFL,O_NONBLOCK); //create network address创建网络地址 struct sockaddr_in addr={};addr.sin_family=AF_INET;addr.sin_port=htons(SOCK_PORT); addr.sin_addr.s_addr=htonl(INADDR_ANY);//addr.sin_addr.s_addr=inet_addr("192.168.0.21"); //bound the file descriptor to address将文件描述符fd绑定到网络地址上r=bind(fd,(struct sockaddr*)&addr,sizeof(addr));if(r==-1){printf("%s\n",strerror(errno));printf("bind failed\n");return -1;}return fd;}/*检查各cfd是否可读,如果可读则读取数据,并发送数据input:cfd存放所有客户端文件描述符fd主文件描述符fds所有文件描述符集合i客户端在cfd数组中的序号*/int deal_read_send(int cfd[],int fd,fd_set fds,int i){//printf("deal_read_send\n");int r;int rec_v=0,send_v=0;if(cfd[i]==-1)return 0;//printf("recv cfd:%d,i:%d\n",cfd[i],i); if(FD_ISSET(cfd[i],&fds)){// 如果cfd还在fds中表明cfd[i]可读,则读取数据int re;re=recv(cfd[i],&rec_v,4,0);if(re==-1&&EAGAIN!=errno){//注意,如果sockfd设置为非阻塞模式,数据还没有发给接收端时,调用recv就会返回-1,并且errno会被设为EAGAIN.但此时连接应为正常 printf("%s\n",strerror(errno));//print error inforprintf("recv failed\n");close(cfd[i]);cfd[i]=-1;return 0;}if(re==0){ //if re==0 means client not send data如果客户端不再发送数据则strerror(errno);close(cfd[i]);cfd[i]=-1;printf("recv null\n");return 0;}printf("From:%d,value:%d",cfd[i],rec_v);getlocal_time();//打印时间send_v=rec_v+1; //sendr=send(cfd[i],&send_v,4,MSG_DONTWAIT);//发送数据if(r==-1){printf("send failed\n");return 0;} }return 0;}/*遍历cfd数组,查询没有使用的位,并返回,如果cfd数组,都被使用,则返回0*/int select_free_numb(int cfd[]){int i;for(i=0;i<CLIENT_NUMB;i++){if(cfd[i]==-1)return i;}return 0;}/*检查哪个文件描述符可读,如果是fd,则表明有新的客户端连接,调用accept获取新的客户端描述符循环遍历客户端描述符集,调用deal_read_send();处理input:fd主文件描述符fds存放所有文件描述符的集合cfd[]存放所有客户端文件描述符return:0*/int deal_select_default(int fd,fd_set fds,int cfd[]){//printf("deal_select_default\n");//检查是否有新的client连接,则acceptint new_cfd,i,r;struct sockaddr_in clientaddr={}; //store connect ip存储连接客户端ipint cliaddlen=sizeof(clientaddr);if(FD_ISSET(fd,&fds)){ //检查fd是否还在fds中,如果在,表明有新的客户端连接,则accept,否则跳过new_cfd=select_free_numb(cfd);cfd[new_cfd]=accept(fd,(struct sockaddr*)&clientaddr,&cliaddlen);//accept and return new fd if(cfd[new_cfd]==-1){printf("error:%d,%s\n",errno,strerror(errno));printf("accept failed\n");return 0;}//printf("**cfd numb:%d\n",new_cfd);printf("\nconnect from %s,client id:%d\n",inet_ntoa(clientaddr.sin_addr),cfd[new_cfd]);}for(i=0;i<CLIENT_NUMB;i++){//循环遍历客户端集中个cfd,如果可读则接收数据,并发送数据deal_read_send(cfd,fd,fds,i);}//for i=0return 0;}/*监听各文件描述符(fd,cfd[i]),如果可读则调用deal_select_default()进行处理input:fd主文件描述符cfd[]存放所有客户端文件描述符return:0*/int loop_deal_socket(int fd,int cfd[]){//printf("loop_deal_socket\n");fd_set fds; //file descriptor set文件描述符集struct timeval timeout={4,0}; //4s超时4sint i;FD_ZERO(&fds); //clear fd set清除fd集FD_SET(fd,&fds);// put fd into set将fd放入fd集中for(i=0;i<CLIENT_NUMB;i++){//将各cfd放入fd集中if(cfd[i]==-1)continue;FD_SET(cfd[i],&fds);}int maxfd=fd;for(i=0;i<CLIENT_NUMB;i++)maxfd=(maxfd>cfd[i])?maxfd:cfd[i]; //find the max fd //选出最大的fd//printf("select\n");switch(select(maxfd+1,&fds,NULL,NULL,&timeout)){//监视各fd,如果fd可读或者可写返回正直,如果网络错误返回-1,如果超时返回0 case -1: //net work error网络错误 printf("caseerror:%s\n",strerror(errno));break; case 0: //if timeout超时 printf("time out\n"); break; default:// if some fd can read如果可读可写等。。。//printf("default\n");deal_select_default(fd,fds,cfd);}return 0;}int main(void){int fd=-1,len,r,i;int cfd[CLIENT_NUMB];//存放各客户端fdfor(i=0;i<CLIENT_NUMB;i++){//将客户端fd集元素初始化为-1cfd[i]=-1;}if((fd=creat_bind_socket())==-1){return -1;}printf("bind succeed\n");//listen监听最多十个连接r=listen(fd,MAX_LISTEN_NUMB);if(r==-1){printf("listen failed\n");return -1;}printf("listen succeed\n");while(1){//循环监听各cfd如果可读,则读取数据,并发送数据,如果有新的客户端连接,创建新的cfd。if(loop_deal_socket(fd,cfd)){;}//sleep(1);}}
- TCP多客户端通信实现
- Java 基于 TCP/IP 实现 Socket中的多客户端通信
- Java 基于 TCP/IP 实现 Socket中的多客户端通信
- TCP实现客户端和服务器的通信
- TCP/UDP客户端服务器实现通信
- C++ 简单的 Tcp 实现[socket] 客户端与客户端通信
- C++ 简单的 Tcp 实现[socket] 客户端与客户端通信
- C++ 简单的 Tcp 实现[socket] 客户端与客户端通信
- Java多线程实现多个客户端同时与服务器进行TCP通信
- 实现采用客户/服务器通信模式,基于TCP网络通信协议的多客户端简单应用
- Java基于TCP实现服务器和多客户端之间的通信
- java 通过 socket 实现 服务器和客户端的通信 TCP
- TCP通信客户端Socket 心跳线程的实现
- Java实现单个客户端与服务器TCP通信
- 基于TCP协议实现服务器和客户端的通信程序
- 从简单到复杂实现TCP客户端服务器通信
- C语言实现服务端和客户端进行TCP通信实例
- Java简单实现TCP服务端和客户端通信
- 润乾——Tomcat部署项目去除项目名和端口号通过IP地址直接访问
- 如何确定jquery文件的版本
- QtCreator excel编程 QAxObject(持续更新)
- 编程开发中最浪费时间和资源的7个错误
- nyist oj 17 单调递增最长子序列 (动态规划经典题)
- TCP多客户端通信实现
- java 中整型与字符型的相互转换
- 使用 Segue 傳送選取的 Table Cell 內容到下一個 View
- HDU--2473 junk mail (并查集,函数)
- ubuntu12.04LTS升级到14.04LTS后VMWARE问题处理
- property生成属性的时候,同时重写setter与getter方法,那么实例变量不自动生成
- 定义区间DP
- Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框
- 获取请求头和响应头