通常的socket描述符是阻塞式的,connect连接时可能出现长时间没有连接成功的情形,若将socket描述符设置为非阻塞,那么调用connect后三次握手还可能没有完全建立connect立即返回EINPROGRESS,这个过程中可以设置超时等待或者做其它的事情。
非阻塞式有3种用途:
1.三次握手同时做其他的处理。connect要花一个往返时间完成,从几毫秒的局域网到几百毫秒或几秒的广域网。这段时间可能有一些其他的处理要执行,比如数据准备,预处理等。
2.用这种技术建立多个连接。这在web浏览器中很普遍.
3.由于程序用select等待连接完成,可以设置一个select等待时间限制,从而缩短connect超时时间。多数实现中,connect的超时时间在75秒到几分钟之间。有时程序希望在等待一定时间内结束,使用非阻塞connect可以防止阻塞75秒,在多线程网络编程中,尤其必要。 例如有一个通过建立线程与其他主机进行socket通信的应用程序,如果建立的线程使用阻塞connect与远程通信,当有几百个线程并发的时候,由于网络延迟而全部阻塞,阻塞的线程不会释放系统的资源,同一时刻阻塞线程超过一定数量时候,系统就不再允许建立新的线程(每个进程由于进程空间的原因能产生的线程有限),如果使用非阻塞的connect,连接失败使用select等待很短时间,如果还没有连接后,线程立刻结束释放资源,防止大量线程阻塞而使程序崩溃。
非阻塞式connect的实现方式:客户端发起对服务器的socket的连接请求(connect调用,如果客户端socket描述符为阻塞模式则会一直阻塞到连接建立或者连接失败,注意阻塞模式的超时时间可能为75秒到几分钟之间),而如果为非阻塞模式,则调用connect之后如果连接不能马上建立则返回-1(errno设置为EINPROGRESS,注意连接也可能马上建立成功比如连接本机的服务器进程),如果没有马上建立返回,此时TCP的三路握手动作在背后继续,而程序可以做其他的东西,然后调用select检测非阻塞connect是否完成(此时可以指定select的超时时间,这个超时时间可以设置为比connect的超时时间短),如果select超时则关闭socket,然后可以尝试创建新的socket重新连接,如果select返回非阻塞socket描述符可写则表明连接建立成功。给出一个实例:
客户端非阻塞connect代码:
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<string.h>
#include<iostream>
#define BUFFER_SIZE 1023
using namespace std;
int setnonblocking(int fd){//设置socket描述符为非阻塞
int old_option=fcntl(fd,F_GETFL);
int new_option=old_option|O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_option;//注意返回就的文件描述符属性以便将来恢复文件描述符属性
}
int unblock_connect(const char* ip,int port,int time){//非阻塞式的connect调用
int ret=0;
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port=htons(port);
int sockfd=socket(PF_INET,SOCK_STREAM,0);
int fdopt=setnonblocking(sockfd);
ret=connect(sockfd,(struct sockaddr*)&address,sizeof(address));//connect连接服务端
if(ret==0){//若connect成功返回则表明连接立即建立,这种情况可能出现在本机连接本机
cout<<"connect immediately"<<endl;
fcntl(sockfd,F_SETFL,fdopt);
return sockfd;
}
else if(errno!=EINPROGRESS){//若errno不为EINPROGRESS则表明非阻塞connect出错
cout<<"unblock connect not support"<<endl;
return -1;
}
fd_set readfds;
fd_set writefds;
struct timeval timeout;//connect超时时间结构体
FD_ZERO(&readfds);
FD_SET(sockfd,&writefds);
timeout.tv_sec=time;
timeout.tv_usec=0;
ret=select(sockfd+1,NULL,&writefds,NULL,&timeout);//select监听sockfd上在超时时间timeout内是否可写
if(ret<=0){//若可写事件没有发生则连接超时,返回-1
cout<<"connect time out"<<endl;
close(sockfd);
return -1;
}
if(!FD_ISSET(sockfd,&writefds)){//不是可写事件返回-1
cout<<"no events on sockfd found"<<endl;
close(sockfd);
return -1;
}
int error=0;
socklen_t length=sizeof(error);
if(getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&length)<0){//用getsockopt获取SO_ERROR错误号
cout<<"get socket option error"<<endl;
close(sockfd);
return -1;
}
if(error!=0){//若错误号error不为0则非阻塞connect调用失败
cout<<"connect error after select "<<error<<endl;
close(sockfd);
return -1;
}
cout<<"connect ready after select "<<sockfd<<endl;//运行到这里非阻塞connect调用成功
fcntl(sockfd,F_SETFL,fdopt);//将sockfd恢复为阻塞式
return sockfd;
}
int main(int argc,char* argv[]){
if(argc<=2){
cout<<"argc<=2"<<endl;
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);
int sockfd=unblock_connect(ip,port,10);//非阻塞connect
if(sockfd<0){
return 1;
}
//shutdown(sockfd,SHUT_WR);
//sleep(200);
cout<<"send data out"<<endl;
send(sockfd,"abc",3,0);//向服务端发送abc
return 0;
}
服务端代码:接受客户端发送来的数据并输出
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<iostream>
#define BUF_SIZE 1024
using namespace std;
int main(int argc,char* argv[]){
if(argc<=2){
cout<<"argc<=2"<<endl;
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port=htons(port);
int sock=socket(PF_INET,SOCK_STREAM,0);
assert(sock>=0);
int ret=bind(sock,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);
ret=listen(sock,5);
assert(ret!=-1);
struct sockaddr_in client;
socklen_t client_addrlength=sizeof(client);
int connfd=accept(sock,(struct sockaddr*)&client,&client_addrlength);
if(connfd<0){
cout<<"errno is:"<<errno<<endl;
}
else{
char buf[BUF_SIZE];
memset(buf,'\0',BUF_SIZE);
ret=recv(connfd,buf,BUF_SIZE-1,0);
cout<<buf<<endl;
close(connfd);
}
close(sock);
return 0;
}