一、非堵塞connect版本
当在非堵塞TCP套接字上, 我们调用connect函数会立刻返回EINPROGRESS, 不过此时TCP三路握手正在进行, 我们可以通过检查套接字的状态。
*非堵塞connect有三个用途
1)、我们可以把三路握手叠加到其他的处理上面。 因为在连接过程中花费1个RTT时间,在局域网或者广域网上的时间幅度偏差很大(几毫秒甚至几秒), 那么我们可以利用这些时间让CPU处理其他事情。
2)、在web程序当中的多连接技术。
3)、通常情况非堵塞的connect连接的超时间是75秒, 但是通过非堵塞connect版本 ,我们可以自己设置等待时间。
*非堵塞connect细节处理
● 如果套接字是非堵塞的情况下, 如果我们连接到的服务器在同一个主机上面, 那么连接将会立刻完成。
● 在非堵塞套接字连接的时候, 我们需要用select来检查connect连接情况有以下两个规则:(1)当连接成功建立时, 描述符号将会变的可写。(2)当连接遇到错误的时候, 描述符变为即可读又可写。
二、非堵塞connect代码分析
源代码如下:
connect_nonb.c
#include "unp.h"
int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
flags = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, flags | O_ONOBLOCK);
error = 0;
if((n = connect(sockfd, saptr, salen)) < 0){
if(error != EINPROGRESS) return -1;
}
else if(n == 0)
goto done;
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if((n = select(sockfd + 1, &rset, &wset, NULL, (nsec? &tval:NULL))) == 0){
close(sockfd);
errno = ETIMEOUT;
return -1;
}
if(FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)){
len = sizeof(error);
if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) return -1; // Solaris可移植问题
}else{
fprintf(stderr, "select error: sockfd not set\n");
}
done:
fcntl(sockfd, F_SETFL, flags);
if(error){ // berkeley可移植问题。
close(sockfd);
errno = error;
return -1;
}
return 0;
}
10-11 行:设置套接字为非堵塞
13-16行: 发起非堵塞的connect。期望错误值是EINPROGRESS, 那表示连接已经启动但是尚未完成。
17-18行:非堵塞连接成功, connect返回0。一般只有客户和服务器在同一台主机上面才会发生这样的情况
26-28行:超时调用,errno置为ETIMEDOUT。
32-37行:造成可读可写条件, 我们通过调用getsockopt去的套接字的待处理错误条件, 如果连接成功返回的是0。在这里存在一个可移植的问题,就是在Berkeley中我们的变量error中返回待处理错误时候, getsockopt本身返回0; 而在Solaris中让是getsockopt返回-1, 将errno处理置为待处理错误。我们的程度充分考虑到了该问题
38-46行:恢复套接字标志, 如果是在Berkeley的实现下, 对error进行判断,若有错误则将errno置于error的值。
*针对代码中的第34行中的getsockopt的条件我们还可以有以下解决方案:
1)、调用getpeername代替getsockopt。如果getpeername以ENOTCONN错误返回,那么建立连接失败, 那么我们必须使用SOL_SOCKET中的SO_ERROR标志来调用getsockopt函数已获得待处理错误。
2)、read(sockfd, buffer, 0)调用socket。如果read失败, 那么connect失败, 那么read返回errno将会给出失败原因。 若果建立成功, read将会返回0.
3)、根据TCP套接字的connect只能连接一次的原因, 再次调用connect应该是失败的, 看错误原因,如果返回错误EISCONN, 那么代表连接成功
三、总结
1、了解非堵塞IO的三个用途。
2、非堵塞connect情况下, EINPROGRESS错误值分析, 以及连接成功的条件。
3、判断连接是否成功的三个解决方案。
4、connect可移植问题研究