套接字的默认状态是阻塞的。当发出一个不能立即完成的套接字调用时,其进程将被投入到睡眠,等待相应操作完成。可能阻塞的套接字调用可分为以下四类:
(1)输入操作,包括read、readv、recv、recvfrom和recvmsg等5个函数
对于非阻塞的套接字,如果输入操作不能被满足,相应的调用立即返回一个EWOULDBLOCK错误
(2)输出操作,包括write、writev、send、sendto和sendmsg共5个函数内核从应用进程的缓冲区复制数据到套接字的发送缓冲区
对于一个非阻塞的套接字,如果其发送缓冲区没有空间,输出函数调用将立即返回一个EWOULDBLOCK.如果其缓冲区中有一些空间,返回值将是内核能够复制到该缓冲区中的字节数
(3)接受外来连接,即accept函数。如果对一个阻塞的套接字调用accept函数,并且尚无新连接到达,调用进程将被投入到睡眠
如果对一个非阻塞的套接字调用accept函数,并且尚无新连接到达,accept调用将立即返回一个EWOULDBLOCK错误
(4)发起外出连接,即用于TCP的connect函数。connect函数一直等到收到对于自己SYN的ACK才会返回,意味着每个connect都会阻塞其调用进程至少一个服务器的RTT时间
如果对一个非阻塞的套接字调用connect,并且连接不能立即建立,那么连接的建立能够照样发起(送出三路握手的第一个分组),不过会返回一个EINPROGRESS错误。但是如果服务器与客户处于同一个主机的情况下,连接一般可以立即建立,也需要预备connect立即成功返回的情况
当在一个非阻塞的套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起三路握手继续进行。我们可以用select检测这个连接建立成功或失败的条件。非阻塞的connect有三个用途
(1)我们可以把三路握手叠加在其他处理上。在建立连接的这段时间中,我们可以执行其他操作
(2)我们可以同时建立多个连接
(3)我们可一个给select一个时间限制,使得我们缩短connect的超时时间
关于select和connect:
当连接建立成功时,描述符变为可写
当连接遇到错误时,描述符变为既可以写又可以读
下面我们发起一个非阻塞的connect:
1: #include"unp.h"
2: int connect_nonb(int sockfd,const SA*saptr,socklen_t salen,int nsec)
3: {
4: int flags,n,error;
5: socklen_t len;
6: struct timeval tval;
7: fd_set rset,wset;
8: flags=fcntl(sockfd,F_GETFL,0);
9: fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);
10: error=0;
11: if((n=connect(sockfd,saptr,salen))<0)
12: if(errno!=EINPROGRESS)
13: return(-1);
14: if(n==0)
15: goto done; //连接立即建立成功
16: FD_ZERO(&rset);
17: FD_SET(sockfd,&rset);
18: wset=rset;
19: if((n=select(sockfd+1,&rset,&wset,NULL,nsec?&tval:NULL))==0)
20: {
21: close(sockfd);
22: errno=ETIMEOUT;
23: return(-1);
24: }
25: ...................
26: ...................//判断是否sockfd既可以读又可以写(出现错误)
27: }
假设connect被信号中断,如果不能由内核重启,那么它将返回EINTR。我们不能再次调用connect等待未完成的连接继续完成。这样做导致返回EADDRINUSE
我们只能调用select等待套接字变为可写或者出错
考虑如下情况:
·客户建立连接后终止它
·select向服务器返回可读条件,不过服务器等待一小段时间才调用accept
·在服务器从select返回到调用accept期间,服务器TCP收到来自客户的RST
·这个已完成连接被服务器去除出已完成连接队列,队中没有其他已完成的连接
·服务器调用accept,阻塞
为了避免上述情况,我们采用以下方法:
(1)当select获悉某个监听套接字上何时有已完成连接准备好时,总是把这个监听套接字设置为非阻塞
(2)在后续调用accept时忽略以下错误:EWOULDBLOCK、ECONNABORTED、EINTR