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


  • 测试程序tcp_server1-1(源程序名任意,允许多个,C/C++语言任选,make 后得到tcp_server1即可,下同),运行后绑定某个TCP 端口号,并进入等待连接状态(下面称为LISTEN 状态),要求端口号通过main 函数带参数的方式传入(例:./tcp_server1 4000 表示绑定TCP 4000端口)

  • 测试程序tcp_client1-1,运行时带入服务端IP 地址及端口号,即可向服务端发起连接,要求IP 地址、端口号通过main 函数带参数的方式传入(例:./tcp_client1 192.168.80.230 4000则表示连接192.168.80.230 的TCP 4000 端口)

  • Server 端用于listen 的socket,不设置为非阻塞方式,accept 成功后,将accept 的socket设置为非阻塞方式

  • Client 端建立的socket,先不设置为非阻塞方式,待connect 成功后,再设置为非阻塞方式

  • 连接成功后,双方均进入read(recv)状态,read(recv)函数后直接关闭socket,程序退出

  • read(recv)函数的表现会如何?程序会阻塞在read(recv)还是立即结束? read(recv)函数 返回什么?

    read(recv)函数不会阻塞,读取了0字节,read(recv)函数返回0。

  • 测试程序tcp_server1-2/tcp_client1-2,在1-1 的基础上,用select 使read(recv)停下来而不立即返回

    用select函数处于无限阻塞

    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(socket_fd, &rfds);
    
    int selres = select(socket_fd + 1, &rfds, NULL, NULL, NULL);
    

  • 测试程序tcp_server1-3,要求socket 建立成功后,先设置非阻塞方式,再进行bind、listen和accept,accept 的新socket,也是立即设置为非阻塞方式,再进行后续操作

  • 测试程序tcp_client1-3,要求socket 建立成功后,先设置非阻塞方式,再connect

  • 要求tcp_client1-3 能连接tcp_server1-3 成功,并在连接成功后,使双方都停在read(recv)上而不立即返回

    server 非阻塞accpet失败后用select建立新的连接,核心代码:

    if( (connectfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1) {
    	FD_ZERO(&rfds);
    	FD_SET(listenfd, &rfds);
    	selres = select(listenfd + 1, &rfds, NULL, NULL, NULL);
    	if(selres<0) {
    		perror("select error\n");
    		exit(0);
    	}
    
    	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);
    

    client端建立非阻塞连接时,select返回大于0,调用connect可能返回-1,若errno被设为EINPROGRESS,意即connect仍旧在进行还没有完成,需要时间建立,多次select。

    int nonBlockConnect(int socket_fd, struct sockaddr_in *servaddr) {
    	fd_set rfds, wfds;
    	struct timeval tv;
    	int selres, conres, ret;
    
    	conres=connect(socket_fd, (struct sockaddr*)servaddr, sizeof(struct sockaddr_in));
    	if (0 == conres) {
    		printf("socket connect succeed immediately.\n");
    		ret=0;
    	} else {
    		printf("get the connect result by select().\n");
    		if (errno == EINPROGRESS) {
    			int times = 0;
    			while (times++ < 5) {
    				FD_ZERO(&rfds);
    				FD_ZERO(&wfds);
    				FD_SET(socket_fd, &rfds);
    				FD_SET(socket_fd, &wfds);
    
    				tv.tv_sec = 10;
    				tv.tv_usec = 0;
    				selres=select(socket_fd + 1, &rfds, &wfds, NULL, &tv);
    				if(-1==selres) {
    					printf("select error\n");
    					ret = -1;
    				} else if(0==selres) {
    					printf("select time out\n");
    					ret = -1;
    				} else {
    					if (FD_ISSET(socket_fd, &rfds) || FD_ISSET(socket_fd, &wfds)) {
    						connect(socket_fd, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in));
    						int err = errno;
    						if  (err == EISCONN) {
    							printf("connect finished.\n");
    							ret = 0;
    						} else {
    							printf("connect failed. errno = %d\n", errno);
    							printf("FD_ISSET(socket_fd, &rfds) :%d FD_ISSET(socket_fd, &wfds) :%d\n",
    							       FD_ISSET(socket_fd, &rfds) , FD_ISSET(socket_fd, &wfds));
    							ret = errno;
    						}
    					}
    				}
    
    				if (-1 != selres && (ret != 0)) {
    					printf("check connect result again... times=%d\n", times);
    					continue;
    				} else {
    					break;
    				}
    			}
    		} else {
    			printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
    			ret = errno;
    			exit(0);
    		}
    	}
    	return ret;
    }