Linux-Socket编程-TCP非阻塞方式05


  • 测试程序tcp_server5,接受连接成功后,server 发数据(每次10 字节,间隔1 秒)并同时用大小100 的缓冲区收数据,死循环运行

  • 测试程序tcp_client5-1,连接成功后,client 发数据(每次15 字节,间隔3 秒)并同时用大小100 的缓冲区收数据,死循环运行

  • Server 端先接受一个Client 端的连接,进入死循环读写状态

  • 要求此时Server 能接受一个新的Client 端的连接,也进入死循环读写状态(Server 端一个程序维护一个listen socket 和多个accept 的socket,要保证accept 的socket 进入死循环读写的同时,仍然能接受新的Client 端的连接)

    利用select进行单进程并发。

    将所有fds(包括listenfd, 所有connectfd)加入到allfds中管理;

    rfds=allfds; 
    selres=select(maxfd+1, &rfds, NULL, NULL, NULL);
    

    selres<0 判断是否为定时中断,可写时执行写操作;
    selres>0
    若FD_ISSET(listenfd, &rfds)有效,建立新的连接,新连接设置为非阻塞,加入到allfds中; 若其他连接的connectfd在FD_ISSET(connectfd, &rfds)有效,进行读操作。

    关键代码:

    for (i=0; i<FD_SETSIZE; ++i) {
    	client[i]=-1;
    }
    maxfd=listenfd;
    maxi=-1;
    FD_ZERO(&allfds);
    FD_SET(listenfd, &allfds);
    
    while(1) {
    	rfds=allfds;
    	wfds=allfds;
    
    	selres=select(maxfd+1, &rfds, NULL, NULL, NULL);
    	if(selres<0) {
    		err=errno;
    		if(err==EINTR) {
    			wfds=allfds;
    			selres = select(maxfd + 1, NULL, &wfds, NULL, NULL);
    			for (i=0; i<=maxi; ++i) {
    				if((connectfd=client[i])<0)
    					continue;
    				if(FD_ISSET(connectfd, &wfds)) {
    					n = write(connectfd, writebuff, WRITE_SIZE);
    					writebuff[n] = '\0';
    					printf("server write %d bytes to client's fds %d \n", n, connectfd);
    				}
    			}
    			continue;
    		} else {
    			printf("select error: %s(errno: %d)\n",strerror(errno),errno);
    			exit(0);
    		}
    	}
    
    	nready=selres;
    	if(FD_ISSET(listenfd, &rfds)) {
    		if( (connectfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1) {
    			printf("accept socket error: %s(errno: %d)\n",strerror(errno),errno);
    			exit(0);
    		}
    		setNonBlock(connectfd);
    		outputConnect(connectfd);
    		for (i=0; i<FD_SETSIZE; ++i) {
    			if(client[i]<0) {
    				client[i]=connectfd;
    				break;
    			}
    		}
    		if(i==FD_SETSIZE) {
    			printf("too many clients \n");
    			exit(0);
    		}
    
    		FD_SET(connectfd, &allfds);
    		maxfd=maxfd>connectfd? maxfd: connectfd;
    		maxi=maxi>i? maxi: i;
    
    		if (--nready == 0)
    			continue;
    	}
    
    	for (i = 0; i <= maxi; i++) {
    		if ( (connectfd = client[i]) < 0)
    			continue;
    		if (FD_ISSET(connectfd, &rfds)) {
    			n = read(connectfd, readbuff, READ_SIZE);
    			if (n == 0) {
    				/* connection closed by client */
    				if(close(connectfd) == -1) {
    					printf("close socket error: %s(errno: %d)\n",strerror(errno),errno);
    					exit(0);
    				}
    				printf("client fds %d closed \n", connectfd);
    				FD_CLR(connectfd, &allfds);
    				client[i] = -1;
    			} else {
    				printf("server read %d bytes from client's fds %d\n", n, connectfd);
    			}
    			if (--nready == 0)
    				break;
    		}
    	}
    }
    

    测试截图:

    启动两个client:

    关闭两个client:

  • 用两个会话窗口分别启动两个tcp_server5 (如:./tcp_server5 4000 ./tcp_server5 5000)

  • 测试程序tcp_client5-2,运行时带两个端口号(例:./tcp_client5-2 192.168.80.230 40005000),表示在一个程序中建立两个socket,分别连接两个不同端口号的server 端,client发数据(每次15 字节,间隔3 秒)并同时用大小100 的缓冲区收数据,死循环运行,允许在不同会话窗口启动多个client 端

  • Server 端和Client 端均不允许采用分裂进程的方式,只能是一个程序

    tcp_client5-2 原理类似server程序,不再赘述。

    测试截图:

    启动两个server,启动两个client,每个client连接两个server。