非阻塞connect

通常的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;
}



  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值