百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

linux网络编程之如何实现异步 connect

liebian365 2024-10-30 04:48 20 浏览 0 评论

推荐视频:

支撑互联网的基石tcpip,5个方面全面解析

手写一个用户态协议栈以及零拷贝的实现

C/C++ Linux服务器开发学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

写过网络程序的同学,应该都知道 connect 函数,在 socket 开始读写操作之前,先要进行连接,也即 TCP 的三次握手 , 这个过程就是在 connect 函数中完成的, connect 函数本身是阻塞的,通过设置 socket 的选项及调用 select/poll 函数可以实现异步 connect 的功能

socket 默认是阻塞模式,处于阻塞模式时,调用 connect 函数之后, 会一直等待连接结果返回为止,要么成功,要么失败,connect 函数返回 0 时成功,返回 -1 失败

在局域网中,调用 connect 函数,基本上会立即返回结果,当服务器在国外时,connect 函数时会阻塞一段时间,大概几秒钟吧,具体的还要看当时的网络状况

为什么要用异步 connect

Linux 下 connect 默认的超时时间大概在一分钟左右(不同的Linux版本略有差别),在实际的开发中,这个时间显得有点儿长了

对于服务器来说,需要为很多的客户端服务,要尽量减少阻塞,所以,一般都是采用 异步 connect 的技术

对于每一个编写网络程序的同学来说,异步connect 应该是必须掌握的基本功

异步connect 步骤

(1) 创建socket,调用 fcntl 函数将其设置为非阻塞 

(2) 调用 connect 函数,返回 0 表示连接成功,返回 -1,需要检查错误码

    如果错误码为 EINPROGRESS,表示正在建立连接中
    
    如果错误码是 EINTR 表示,表示发生了系统中断,这时继续执行连接即可
    
    如果是其他错误码,调用 close(fd) 函数关闭 socket, 连接失败

(3) 将 socket 加入 select/poll 的可写文件描述符集合中,并设置超时时间

(4) 判断 select/poll 函数的返回值
    
    小于等于 0 表示失败
    
    其他,表示 socket 可写,调用 getsockopt 函数 捕获 socket 的错误信息
           

具体的代码如下:

/*
    异步 connect 测试代码, test_connect.cpp
*/
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <poll.h>
#include <limits.h>
#include <iostream>
using namespace std;

int32_t main(int32_t argc, char *argv[])
{
    if(argc < 3)
    {
        std::cout << "argc < 3..." << std::endl;
        return -1;
    }
    std::string strip = argv[1];
    uint32_t port = atoi(argv[2]);
    //创建 socket
    int32_t fd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == fd)
    {
        std::cout << "create socket error:" << errno << std::endl;
        return -1;
    }
    //将 socket 设置成非阻塞
    int32_t flag = fcntl(fd, F_GETFL, 0);
    flag |= O_NONBLOCK;
    if(-1 == fcntl(fd, F_SETFL, flag))
    {
        std::cout << " set socket nonblock error:" << errno << std::endl;
        close(fd);
        return -1;
    }
    //服务器地址
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(strip.c_str());
    //
    for(; ;)
    {
        //连接服务器
        int32_t ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr) );
        if(-1 == ret)
        {
            int32_t err = errno;
            if(EINTR == err)
            {
                //connect被中断,继续重试
                //如果不处理 EINTR 错误的话,connect逻辑可以不用放到 for 循环中
                continue;
            }
            if(EINPROGRESS != err)
            {
                std::cout << "connect err:" << errno << ", str:" << strerror(errno) <<  std::endl;
                goto exit;
            }
            //正在连接中
            std::cout << "connecting..." << std::endl;
            //处理结果
            int32_t result = -1;
    #if 1
            //将 socket 加入到 poll 的可写集合中
            struct pollfd wfd[1];
            wfd[0].fd = fd;
            wfd[0].events = POLLOUT;
            //检测 socket 是否可写
            result = poll(wfd, 1, 3000);
    #elif 0
            //设置超时时间
            struct timeval tval;
            tval.tv_sec = 3;
            tval.tv_usec = 0;
            //将 socket 加入到 select 的可写集合中
            fd_set wfds;
            FD_ZERO(&wfds);
            FD_SET(fd,&wfds);
            //检测 socket 是否可写
            result = select(fd + 1, nullptr, &wfds, nullptr,&tval);
    #endif
            std::cout << "async connect result:" << result << std::endl;
            // 失败
            if(result <= 0 )
            { 
                std::cout << "async connect err:" << errno << ", str:" << strerror(errno) << std::endl;
                goto exit;
            }
            //检查socket 错误信息
            int32_t temperr = 0;
            socklen_t temperrlen = sizeof(temperr);
            if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&temperr, &temperrlen) )
            {
                std::cout << "async connect...getsockopt err:" << errno << ", str:" << strerror(errno) <<  std::endl;
                goto exit;
            }
            if(0 != temperr)
            {
                std::cout << "async connect...getsockopt temperr:" << temperr << ", str:" << strerror(temperr) << std::endl;
                goto exit;
            }
            //成功
            std::cout << "async connect success..." << std::endl;
            goto exit;
        }
        else
        {
             //连接成功
            std::cout << "connect success..." << std::endl;
            goto exit;          
        }
    } // end of  for(; ;)
exit:
    std::cout << "quit...." << std::endl;
    close(fd);
    return 0;
}
  • 代码说明

如果不处理 EINTR 错误的话,connect 函数及后面的逻辑可以不用放到 for 循环中

检查 socket 是否可写,调用 select 或者 poll 函数都可以,上述代码中使用的是 poll 函数,将代码中的 #if 1 改成 #if 0 以及 #elif 0 改成 #elif 1 , 就是使用 select 函数检测 socket 是否可写了

【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)

测试

在另一台机器上执行 nc -l -v -k 192.168.70.20 5000 命令,启动一个服务器程序

在当前机器上执行 g++ -g -Wall -std=c++11 -o test_connect test_connect.cpp 进行编译

执行 ./test_connect 192.168.70.20 5000, 结果如下图

此时,服务器程序显示如下:

通过 test_connect 程序端的截图可以看出,调用 connect 函数之后,返回了 EINPROGRESS 错误码,然后调用 select/poll 函数返回 1, 表示 socket 可写,紧接着调用 getsockopt 函数检查 socket 错误信息,通过打印的信息知道,socket 无错误信息,即 连接成功

我们在服务器机器上按 CTRL + C 停止服务器程序,然后关闭 test_connect 程序,再次执行 ./test_connect 192.168.70.20 5000 ,结果如下图:

从上图可以看出,即使服务器程序已经退出了,调用 select/poll 之后还是返回 socket 可写,当继续调用 getsockopt 函数检查 socket 错误码,此时错误码是 111, 表示连接被拒绝,也即连接失败

这里要注意一个很重要的点, 在 Linux 上,即使 socket 没有连接成功,调用 select/poll 时,仍然返回 socket 是可写的,所以 除了调用 select/poll 检查 socket 可写之外,还需要调用 getsockopt 函数检查 socket 的错误码,错误码为 0 表示连接成功,其他表示连接失败

相关推荐

精品博文嵌入式6410中蓝牙的使用

BluetoothUSB适配器拥有一个BluetoothCSR芯片组,并使用USB传输器来传输HCI数据分组。因此,LinuxUSB层、BlueZUSB传输器驱动程序以及B...

win10跟这台计算机连接的前一个usb设备工作不正常怎么办?

前几天小编闲来无事就跑到网站底下查看粉丝朋友给小编我留言询问的问题,还真的就给小编看到一个问题,那就是win10跟这台计算机连接的一个usb设备运行不正常怎么办,其实这个问题的解决方法时十分简单的,接...

制作成本上千元的键盘,厉害在哪?

这是稚晖君亲自写的开源资料!下方超长超详细教程预警!!全文导航:项目简介、项目原理说明、硬件说明、软件说明项目简介瀚文智能键盘是一把我为自己设计的——多功能、模块化机械键盘。键盘使用模块化设计。左侧的...

E-Marker芯片,USB数据线的“性能中枢”?

根据线缆行业的研究数据,在2019年搭载Type-C接口的设备出货量已达到20亿台,其中80%的笔记本电脑和台式电脑采用Type-C接口,50%的智能手机和平板电脑也使用Type-C接口。我们都知道,...

ZQWL-USBCANFD二次开发通讯协议V1.04

修订历史:1.功能介绍1.1型号说明本文档适用以下型号:  ZQWL-CAN(FD)系列产品,USB通讯采用CDC类实现,可以在PC机上虚拟出一个串口,串口参数N,8,1格式,波特率可以根据需要设置(...

win10系统无法识别usb设备怎么办(win10不能识别usb)

从驱动入手,那么win10系统无法识别usb设备怎么办呢?今天就为大家分享win10系统无法识别usb设备的解决方法。1、右键选择设备管理器,如图:  2、点击更新驱动程序,如图:  3、选择浏览...

微软七月Win8.1可选补丁有内涵,含大量修复

IT之家(www.ithome.com):微软七月Win8.1可选补丁有内涵,含大量修复昨日,微软如期为Win7、Win8.1发布7月份安全更新,累计为6枚安全补丁,分别修复总计29枚安全漏洞,其中2...

如何从零开始做一个 USB 键盘?(怎么制作usb)

分两种情况:1、做一个真正的USB键盘,这种设计基本上不涉及大量的软件编码。2、做一个模拟的USB键盘,实际上可以没有按键功能,这种的需要考虑大量的软件编码,实际上是一个单片机。第一种设计:买现成的U...

电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题

电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题注意:有些方法会清除USB设备里的数据,请谨慎操作,如果不想丢失数据,可以先连接到其他电脑,看能否将数据复制出来,或者用一些数据恢复软件去扫...

未知usb设备设备描述符请求失败怎么解决

出现未知daousb设备设备描述符请求失du败解决办zhi法如下:1、按下Windows+R打开【运行】;2、在版本运行的权限输入框中输入:services.msc按下回车键打开【服务】;2、在服务...

读《飘》47章20(飘每章概括)

AndAhwouldn'tleaveMissEllen'sgrandchildrenfornotrashystep-patobringup,never.Here,Ah...

英翻中 消失的过去 37(消失的英文怎么说?)

翻译(三十七):消失的过去/茱迪o皮考特VanishingActs/JodiPicoult”我能做什么?“直到听到了狄利亚轻柔的声音,我才意识到她已经在厨房里站了好一会儿了。当她说话的时候,...

RabbitMQ 延迟消息实战(rabbitmq如何保证消息不被重复消费)

现实生活中有一些场景需要延迟或在特定时间发送消息,例如智能热水器需要30分钟后打开,未支付的订单或发送短信、电子邮件和推送通知下午2:00开始的促销活动。RabbitMQ本身没有直接支持延迟...

Java对象拷贝原理剖析及最佳实践(java对象拷贝方法)

作者:宁海翔1前言对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化。Java对象拷贝分为深拷贝和浅拷贝,目前常用的...

如何将 Qt 3D 渲染与 Qt Quick 2D 元素结合创建太阳系行星元素?

Qt组件推荐:QtitanRibbon:遵循MicrosoftRibbonUIParadigmforQt技术的RibbonUI组件,致力于为Windows、Linux和MacOSX提...

取消回复欢迎 发表评论: