非阻塞connnect的使用

前言

一般来说,在TCP建立连接的过程中,我们的客户端使用connect发起连接,默认使用的是阻塞模式,只有在有明确结果的时候才会返回(连接成功或连接失败)。这样可能照成当我们向一个“比较远”的服务器发起连接时,我们的程序会阻塞在connect好几秒,这并不是我们想要看到的,如果解决这种问题呢?

引入

正是由于阻塞connect可能照成上述问题,所以在实际项目中我们一般使用非阻塞connect来解决这个问题。connect在出错时存在有一种errno值:**EINPROGRESS.**这种错误出现在对非阻塞的socket调用connect,而连接又没有建立时。根据man文档解释,我们可以调用select,poll等函数返后,再利用getsockopt来读取错误码并清除该socket上的错误。如果错误码是0表示建立连接成功,否则连接失败。

使用

  1. 创建socket,并将 socket 设置成非阻塞模式
  2. 调用 connect 函数,此时无论 connect 函数是否连接成功会立即返回;如果返回 -1 并不一定表示连接出错,如果此时错误码是EINPROGRESS,则表示正在尝试连接;
  3. 接着调用 select 函数,在指定的时间内判断该 socket 是否可写,如果可写,使用getsockopt获取错误码,判断错误码是否为0,错误码为0表示连接成功,否则失败

代码实现

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <iostream>

#define BUFFER_SIZE 1023

int setnonblocking(int fd)
{
    int old_flag = fcntl(fd,F_GETFL);
    int new_flag = old_flag|O_NONBLOCK;
    fcntl(AF_INET,F_SETFL,new_flag);
    return old_flag;
}

int unblock_connect(const char* ip,int port,int time)
{
    int ret = 0;
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr.s_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));
    if(ret == 0)
    {
        //连接成功,则恢复sockfd的属性立即返回
        printf("connect with server immediately\n");
        fcntl(sockfd,F_SETFL,fdopt);
        return sockfd;
    }
    else if(errno != EINPROGRESS)
    {
        //表示出错
        printf("unblock connect not support\n");
        return -1;
    }
    //使用select检测连接的成功或失败
    fd_set readfds;
    fd_set writefds;
    struct timeval timeout;
    FD_ZERO(&readfds);
    FD_SET(sockfd,&writefds);

    timeout.tv_sec = time;
    timeout.tv_usec = 0;

    ret = select(sockfd+1,NULL,&writefds,NULL,&timeout);
    if(ret <= 0)
    {
        //select超时或者出错,立即返回
        printf("connection time out\n");
        close(sockfd);
        return -1;
    }

    if(!FD_ISSET(sockfd,&writefds))
    {
        printf("no events on sockfd found\n");
        close(sockfd);
        return -1;
    }
    
    int error = 0;
    socklen_t length = sizeof(error);
    //调用getsockopt来获取并清除sockfd上的错误
    if(getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&length) < 0)
    {
        printf("get socket opt failed\n");
        close(sockfd);
        return -1;
    }
    //错误号不为0表示连接出错
    if(error != 0)
    {
        printf("connection failed after select with the error:%d\n",error);
        close(sockfd);
        return -1;
    }
    //连接成功
    printf("connection ready after select with the socket:%d\n",sockfd);
    fcntl(sockfd,F_SETFL,fdopt);
    return sockfd;

}

int main(int argc,char** argv)
{
    if(argc <= 2)
    {
        printf("usage:%s ip_address port_number\n",basename(argv[1]));
        return 1;
    }

    const char* ip = argv[1];
    int port = atoi(argv[2]);

    int sockfd = unblock_connect(ip,port,10);
    if(sockfd < 0)
    {
        return 1;
    }
    close(sockfd);
    return 0;
}


要点

  • 在建立连接成功后,恢复sockfd的属性
  • 通过select检测可写事件时,不能以是否有可写事件来判断是否连接成功,因为在Linux系统下,在建立连接前,使用select检测是否有可写事件,会得可写的结果(巨坑啊!!!),所以必须使用errno错误码来判断。
  • 使用getsockopt来获取socket上的错误码并清除socket上的错误
    • int error = 0;
    • getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&length);
  • 通过判断错误码error是否为0来确定连接是否成功,error = 0表示连接成功。

非阻塞connect的优点

  1. 可以把三次握手叠加到其他处理上,既在完成connect的事件内执行其他动作
  2. 可以同时建立多个连接
  3. 使用select等待连接可以设置事件限制,缩短connect超时。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值