非阻塞connect
当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的三路握手将继续进行。我们接着使用select检测这个连接或成功或失败的已建立条件。
非阻塞的connect有三个用途:
1、我们可以把三路握手叠加在其他处理上。完成一个connect要花一个RTT时间,而RTT不稳定,这段时间内可能有我们想要执行的其他处理工作要执行
2、我们可以使用这个技术同时建立多个连接。此技术现在已随着web浏览器变得流行起来
3、既然使用select等待连接的建立,我们可以给select指定一个时间的限制,使我们缩短connect的超时(许多实现有75秒甚至更多的超时时间,但我们想要缩短,方法之一就是使用非阻塞connect)
非阻塞connect要处理的一些细节:
1、尽管套接字是非阻塞的,如果连接的服务器在同一主机上,那我们调用connect时,连接通常立即建立,我们需要处理这种情形
2、两个规则:
当连接建立成功时,描述符变为可写(若select调用发现有数据来了,当然也会变成可读的)
当连接建立遇到错误时,描述符既可读也可写(这就可能会造成迷惑,那我们如何发现错误呢?下面程序就通过 getsockopt来解决了这个问题)
以下就贴出非阻塞connect函数的实现:
既然我们发现了套接字可写(而不可读)条件并不是select返回套接字操作成功条件的唯一方法(之前也提到了,在connect和select之间可能有数据来临导致sockfd可读),下一个移植性问题就是怎样判断建立连接是否成功。以下一些方法:
1、调用getpeername代替getsockopt。 如果getpeername以ENOTCONN错误返回,那么连接就是失败的,我们必须接着以SO_ERROR调用getsockopt来取得错误
2、以值为0的长度参数调用read。如果read失败,那么connect调用失败,read返回的errno给出了连接失败的原因。如果连接是成功的,那么该是返回0的
3、再调用connect。它应该失败,如果错误是EISCONN,那么套接字已连接,即之前已连接成功
『
这里提一下,之前看UDP的时候我也记得有再次调用connect这个概念,现在把它贴在这里,是关于UDP调用connect部分里的:
拥有一个已连接的UDP套接字的进程可以为以下两个目的之一再次调用connect
1、指定新的IP地址和端口号(对于TCP,connect只能调用一次)
2、断开套接字(只要在再次调用时把地址族改为 AF_UNSPEC)
』
被中断的connect
对于一个正常的阻塞connect,若正在三路握手的TCP,如果在完成之前被中断,(譬如捕捉到某个信号了),那么假设中断的connect不由内核重启,那么它将返回EINTR。我们不能再调用connect等待未完成的连接继续完成,因为这样将导致EADDRINUSE错误
此时我们只能调用select,像对待这里的非阻塞connect一样,连接建立成功时候select返回套接字可写条件,失败时返回既可读也可写条件
(connect的问题,当connect遇到EINTR错误时,不能向上面那样重新进入循环处理,原因是,【connect的请求已经发送向对方,正在等待对方回应,这是如果重新调用connect,而对方已经接受了上次的connect请求,这一次的connect就会被拒绝】,因此,需要使用select或poll调用来检查socket的状态,如果socket的状态就绪,则connect已经成功,否则,视错误原因,做对应的处理。)
当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的三路握手将继续进行。我们接着使用select检测这个连接或成功或失败的已建立条件。
非阻塞的connect有三个用途:
1、我们可以把三路握手叠加在其他处理上。完成一个connect要花一个RTT时间,而RTT不稳定,这段时间内可能有我们想要执行的其他处理工作要执行
2、我们可以使用这个技术同时建立多个连接。此技术现在已随着web浏览器变得流行起来
3、既然使用select等待连接的建立,我们可以给select指定一个时间的限制,使我们缩短connect的超时(许多实现有75秒甚至更多的超时时间,但我们想要缩短,方法之一就是使用非阻塞connect)
非阻塞connect要处理的一些细节:
1、尽管套接字是非阻塞的,如果连接的服务器在同一主机上,那我们调用connect时,连接通常立即建立,我们需要处理这种情形
2、两个规则:
当连接建立成功时,描述符变为可写(若select调用发现有数据来了,当然也会变成可读的)
当连接建立遇到错误时,描述符既可读也可写(这就可能会造成迷惑,那我们如何发现错误呢?下面程序就通过 getsockopt来解决了这个问题)
以下就贴出非阻塞connect函数的实现:
#include "unp.h"
int connect_nonb(int sockfd, struct sockaddr *saptr, socklen_t alen, int nsec)
{
int flag, rl, n, error;
fd_set rset, wset;
struct timeval tval;
//set up O_NONBLOCK
flag = fcntl(sockfd, F_GETFL);
fcntl(sockfd, F_SETFL, flag | O_NONBLOCK);
error = 0;
//excute connect
if((rl=connect(sockfd, saptr, alen)) < 0)
if(errno != EINPROGRESS) //EINPROGRESS is the expected signal once we use "unblocked connect"
return -1;
if( rl != 0 ) //if rl is "0", it means that client and service connect immediately (maybe they are in the same computer)
{
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) //time out
{
close(sockfd);
errno = ETIMEDOUT;
return -1;
}
// if connection is established and data hasn't arrived, sockfd can be written
// if connection is established and data has arrived and can be read, sockfd can be written and read
// However, if errors occur, sockfd can be read and written too.
// So , we have to use "getsockopt " to judge wheather something goes wrong or not
if(FD_ISSET(sockfd,&rset) || FD_ISSET(sockfd,&wset))
{
len = sizeof(error);
if(getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) < 0)
/* 如果发生错误,Solaris实现的getsockopt返回-1,
* 把pending error设置给errno. Berkeley实现的
* getsockopt返回0, pending error返回给error.
* 我们需要处理这两种情况
* 因此在下方 if(error) 中处理了
*/
return -1;
}
else
oops("socket not set");
}
fcntl(sockfd,F_SETFL,flag);
//为了可移植性,我们需要这么做
if(error)
{
close(sockfd);
errno = error;
return -1;
}
return 0;
}
既然我们发现了套接字可写(而不可读)条件并不是select返回套接字操作成功条件的唯一方法(之前也提到了,在connect和select之间可能有数据来临导致sockfd可读),下一个移植性问题就是怎样判断建立连接是否成功。以下一些方法:
1、调用getpeername代替getsockopt。 如果getpeername以ENOTCONN错误返回,那么连接就是失败的,我们必须接着以SO_ERROR调用getsockopt来取得错误
2、以值为0的长度参数调用read。如果read失败,那么connect调用失败,read返回的errno给出了连接失败的原因。如果连接是成功的,那么该是返回0的
3、再调用connect。它应该失败,如果错误是EISCONN,那么套接字已连接,即之前已连接成功
『
这里提一下,之前看UDP的时候我也记得有再次调用connect这个概念,现在把它贴在这里,是关于UDP调用connect部分里的:
拥有一个已连接的UDP套接字的进程可以为以下两个目的之一再次调用connect
1、指定新的IP地址和端口号(对于TCP,connect只能调用一次)
2、断开套接字(只要在再次调用时把地址族改为 AF_UNSPEC)
』
被中断的connect
对于一个正常的阻塞connect,若正在三路握手的TCP,如果在完成之前被中断,(譬如捕捉到某个信号了),那么假设中断的connect不由内核重启,那么它将返回EINTR。我们不能再调用connect等待未完成的连接继续完成,因为这样将导致EADDRINUSE错误
此时我们只能调用select,像对待这里的非阻塞connect一样,连接建立成功时候select返回套接字可写条件,失败时返回既可读也可写条件
(connect的问题,当connect遇到EINTR错误时,不能向上面那样重新进入循环处理,原因是,【connect的请求已经发送向对方,正在等待对方回应,这是如果重新调用connect,而对方已经接受了上次的connect请求,这一次的connect就会被拒绝】,因此,需要使用select或poll调用来检查socket的状态,如果socket的状态就绪,则connect已经成功,否则,视错误原因,做对应的处理。)