建立socket后默认connect()函数为阻塞连接状态,在大多数实现中,connect的超时时间在75s至几分钟之间,想要缩短超时时间,可解决问题的两种方法:
-
方法一、将socket句柄设置为非阻塞状态。
-
方法二、采用信号处理函数设置阻塞超时控制。
设置套接字为非阻塞
可以将套接字设置为非阻塞,下面是具体步骤
-
建立socket
-
将该socket设置为非阻塞模式
-
调用connect(),如果返回0,则连接建立;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立。
-
使用select()检查该socket描述符是否可写(注意,是可写)
-
根据select()返回的结果判断connect()结果,如果规定时间内成功建立,则描述符变为可写;否则,采用getsockopt函数捕获错误信息
-
将socket设置为阻塞模式(如果你的程序不需要用阻塞模式的,这步就省了,不过一般情况下都是用阻塞模式的,这样也容易管理)
非阻塞connect听起来虽然简单,但是仍然有一些细节问题要处理:
1.即使套接口是非阻塞的,如果连接的服务器在同一台主机上,那么在调用connect建立连接时,连接通常会立即建立成功.我们必须处理这种情况;
2.源自Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:
A:当连接建立成功时,套接口描述符变成可写;
B:当连接出错时,套接口描述符变成既可读又可写;
注意:当一个套接口出错时,它会被select调用标记为既可读又可写(由于有未决的错误,从而可读又可写);
第一步:创建socket,返回套接口描述符; 第二步:调用fcntl把套接口描述符设置成非阻塞;
第三步:调用connect开始建立连接;
第四步:判断连接是否成功建立;
A:如果connect返回0,表示连接简称成功(服务器可客户端在同一台机器上时就有可能发生这种情况);
B:调用select来等待连接建立成功完成;
如果select返回0,则表示建立连接超时;我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去;
如果select返回大于0的值,则需要检查套接口描述符是否可读或可写;如果套接口描述符可读或可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR),如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等).
如果select返回非阻塞socket描述符既可读又可写则表明连接出错(注意:这儿必须跟另外一种连接正常的情况区分开来,就是连接建立好了之后,服务器端发送了数据给客户端,此时select同样会返回非阻塞socket描述符既可读又可写,这时可以通过以下方法区分:
1.调用getpeername获取对端的socket地址.如果getpeername返回ENOTCONN,表示连接建立失败,然后用SO_ERROR调用getsockopt得到套接口描述符上的待处理错误;
2.调用read,读取长度为0字节的数据.如果read调用失败,则表示连接建立失败,而且read返回的errno指明了连接失败的原因.如果连接建立成功,read应该返回0;
3.再调用一次connect.它应该失败,如果错误errno是EISCONN,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的;
对于无连接的socket类型(SOCK_DGRAM),客户端也可以调用connect进行连接,此连接实际上并不建立类似SOCK_STREAM的连接,而仅仅是在本地保存了对端的地址,这样后续的读写操作可以默认以连接的对端为操作对象。
1.首先填写套接字结构,包括远程的ip,通信端口如下: */
struct sockaddr_in serv_addr;
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(9999);
serv_addr.sin_addr.s_addr = inet_addr("58.31.231.255"); //inet_addr转换为网络字节序
bzero(&(serv_addr.sin_zero),8);
// 2.建立socket套接字:
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket creat error");
return 1;
}
// 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等待函数,直接进入连接后的处理部分。*/
if ( ( n = connect( sockfd, ( struct sockaddr *)&serv_addr , sizeof(struct sockaddr)) ) < 0 )
{
if(errno != EINPROGRESS) return 1;
}
if(n==0)
{
printf("connect completed immediately");
goto done;
}
/* 5.设置等待时间,使用select函数等待正在后台连接的connect函数,这里需要说明的是使用select监听socket描述符是否可读或者可写,如果只可写,说明连接成功,可以进行下面的操作。如果描述符既可读又可写,分为两种情况,第一种情况是socket连接出现错误(不要问为什么,这是系统规定的,可读可写时候有可能是connect连接成功后远程主机断开了连接close(socket)),第二种情况是connect连接成功,socket读缓冲区得到了远程主机发送的数据。需要通过connect连接后返回给errno的值来进行判定,或者通过调用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函数返回值来判断是否发生错误,这里存在一个可移植性问题,在solaris中发生错误返回-1,但在其他系统中可能返回0.我首先按unix网络编程的源码进行实现。如下:*/
FD_ZERO(&rset);
FD_SET(sockfd,&rset);
wset = rset;
tval.tv_sec = 0;
tval.tv_usec = 300000;
int error;
socklen_t len;
if(( n = select(sockfd+1, &rset, &wset, NULL,&tval)) <= 0)
{
printf("time out connect error");
close(sockfd);
return -1;
}
If ( FD_ISSET(sockfd,&rset) || FD_ISSET(sockfd,&west) )
{
len = sizeof(error);
if( getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) <0)
return 1;
}
阅读(2246) | 评论(0) | 转发(0) |