非阻塞模式有3种用途
1.三次握手同时做其他的处理。connect要花一个往返时间完成,从几毫秒的局域网到几百毫秒或几秒的广域网。这段时间可能有一些其他的处理要执行,比如数据准备,预处理等。
2.用这种技术建立多个连接。这在web浏览器中很普遍.
3.由于程序用select等待连接完成,可以设置一个select等待时间限制,从而缩短connect超时时间。多数实现中,connect的超时时
间在75秒到几分钟之间。有时程序希望在等待一定时间内结束,使用非阻塞connect可以防止阻塞75秒,在多线程网络编程中,尤其必要。
例如有一个通过建立线程与其他主机进行socket通信的应用程序,如果建立的线程使用阻塞connect与远程通信,当有几百个线程并发的时候,由于网
络延迟而全部阻塞,阻塞的线程不会释放系统的资源,同一时刻阻塞线程超过一定数量时候,系统就不再允许建立新的线程(每个进程由于进程空间的原因能产生的
线程有限),如果使用非阻塞的connect,连接失败使用select等待很短时间,如果还没有连接后,线程立刻结束释放资源,防止大量线程阻塞而使程
序崩溃。
目前connect非阻塞编程的普遍思路是:
在一个TCP套接口设置为非阻塞后,调用connect,connect会在系统提供的errno变量中返回一个EINRPOCESS错误,此时TCP的三路握手继续进行。之后可以用select函数检查这个连接是否建立成功。
1 int ConnectNonb(int sockfd, struct sockaddr *saptr, socklen_t salen, int nsec) 2 { 3 int flags, n, error; 4 socklen_t len; 5 fd_set rset, wset; 6 struct timeval tval; 7 8 flags = fcntl(sockfd, F_GETFL, 0); 9 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); 10 errno = 0; 11 12 if ((n = connect(sockfd, saptr, salen)) < 0) 13 { 14 if (errno != EINPROGRESS) 15 { 16 return EXIT_FAILURE; 17 } 18 } 19 // 可以做任何其它的操作 20 if (n == 0) 21 { 22 goto done; // 一般是同一台主机调用,会返回 0 23 } 24 FD_ZERO(&rset); 25 FD_SET(sockfd, &rset); 26 wset = rset; // 这里会做 block copy 27 tval.tv_sec = nsec; 28 tval.tv_usec = 0; 29 30 // 如果nsec 为0,将使用缺省的超时时间,即其结构指针为 NULL 31 // 如果tval结构中的时间为0,表示不做任何等待,立刻返回 32 if ((n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) 33 { 34 close(sockfd); 35 errno = 10; 36 return EXIT_FAILURE; 37 } 38 if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) 39 { 40 len = sizeof(error);// 如果连接成功,此调用返回 0 41 if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) 42 return EXIT_FAILURE; 43 } 44 else 45 { 46 printf("select error: sockfd not set"); 47 } 48 done: fcntl(sockfd, F_SETFL, flags); // 恢复socket 属性 49 if (error) 50 { 51 close(sockfd); 52 errno = error; 53 return EXIT_FAILURE; 54 } 55 56 return EXIT_SUCCESS; 57 }
1.首先填写套接字结构,包括远程的ip,通信端口
2.建立socket套接字
3.将socket建立为非阻塞,此时socket被设置为非阻塞模式
flags = fcntl(sockfd,F_GETFL,0);//获取建立的sockfd的当前状态(非阻塞)
fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);//将当前sockfd设置为非阻塞
4. 建立connect连接,此时socket设置为非阻塞,connect调用后,无论连接是否建立立即返回-1,同时将errno(包含errno.h就 可以直接使用)设置为 EINPROGRESS, 表示此时tcp三次握手仍旧进行,如果errno不是EINPROGRESS,则说明连接错误,程序结束。
当客户端和服务器端在同一台主机上的时候,connect回马上结束,并返回0;无需等待,所以使用goto函数跳过select等待函数,直接进入连接后的处理部分.
5.设置等待时间,使用select函数等待正在后台连接的connect函数,这里需要说明的是使用select监听socket描述符是否可读或者可写, 如果只可写,说明连接成功,可以进行下面的操作。如果描述符既可读又可写,分为两种情况,第一种情况是socket连接出现错误(不要问为什么,这是系统 规定的,可读可写时候有可能是connect连接成功后远程主机断开了连接close(socket)),第二种情况是connect连接成 功,socket读缓冲区得到了远程主机发送的数据。需要通过connect连接后返回给errno的值来进行判定,或者通过调用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函数返回值来判断是否发生错误.
6. 用select查看接收描述符,如果可读,就读出数据,程序结束。在接收数据的时候注意要先对先前的rset重新赋值为描述符,因为select会对 rset清零,当调用select后,如果socket没有变为可读,则rset在select会被置零。所以如果在程序中使用了rset,最好在使用时 候重新对rset赋值。