如何实现异步 connect

如何实现异步 connect

写过网络程序的同学,应该都知道 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]);
    //&#x521B;&#x5EFA; socket
    int32_t fd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == fd)
    {
        std::cout << "create socket error:" << errno << std::endl;
        return -1;
    }
    //&#x5C06; socket &#x8BBE;&#x7F6E;&#x6210;&#x975E;&#x963B;&#x585E;
    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;
    }
    //&#x670D;&#x52A1;&#x5668;&#x5730;&#x5740;
    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(; ;)
    {
        //&#x8FDE;&#x63A5;&#x670D;&#x52A1;&#x5668;
        int32_t ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr) );
        if(-1 == ret)
        {
            int32_t err = errno;
            if(EINTR == err)
            {
                //connect&#x88AB;&#x4E2D;&#x65AD;&#xFF0C;&#x7EE7;&#x7EED;&#x91CD;&#x8BD5;
                //&#x5982;&#x679C;&#x4E0D;&#x5904;&#x7406; EINTR &#x9519;&#x8BEF;&#x7684;&#x8BDD;&#xFF0C;connect&#x903B;&#x8F91;&#x53EF;&#x4EE5;&#x4E0D;&#x7528;&#x653E;&#x5230; for &#x5FAA;&#x73AF;&#x4E2D;
                continue;
            }
            if(EINPROGRESS != err)
            {
                std::cout << "connect err:" << errno << ", str:" << strerror(errno) <<  std::endl;
                goto exit;
            }
            //&#x6B63;&#x5728;&#x8FDE;&#x63A5;&#x4E2D;
            std::cout << "connecting..." << std::endl;
            //&#x5904;&#x7406;&#x7ED3;&#x679C;
            int32_t result = -1;
    #if 1
            //&#x5C06; socket &#x52A0;&#x5165;&#x5230; poll &#x7684;&#x53EF;&#x5199;&#x96C6;&#x5408;&#x4E2D;
            struct pollfd wfd[1];
            wfd[0].fd = fd;
            wfd[0].events = POLLOUT;
            //&#x68C0;&#x6D4B; socket &#x662F;&#x5426;&#x53EF;&#x5199;
            result = poll(wfd, 1, 3000);
    #elif 0
            //&#x8BBE;&#x7F6E;&#x8D85;&#x65F6;&#x65F6;&#x95F4;
            struct timeval tval;
            tval.tv_sec = 3;
            tval.tv_usec = 0;
            //&#x5C06; socket &#x52A0;&#x5165;&#x5230; select &#x7684;&#x53EF;&#x5199;&#x96C6;&#x5408;&#x4E2D;
            fd_set wfds;
            FD_ZERO(&wfds);
            FD_SET(fd,&wfds);
            //&#x68C0;&#x6D4B; socket &#x662F;&#x5426;&#x53EF;&#x5199;
            result = select(fd + 1, nullptr, &wfds, nullptr,&tval);
    #endif
            std::cout << "async connect result:" << result << std::endl;
            // &#x5931;&#x8D25;
            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) connect...getsockopt if(0 !="temperr)" temperr:" strerror(temperr) 成功 success..." else 连接成功 "connect end of for(; ;) exit: "quit...." close(fd); return 0; < code></=></iostream></limits.h></poll.h></stdarg.h></errno.h></netdb.h></string.h></fcntl.h></unistd.h></arpa></netinet></netinet></sys></poll.h></sys></sys></sys></stdint.h>
  • *代码说明

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

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

测试

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

如何实现异步 connect

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

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

如何实现异步 connect

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

如何实现异步 connect

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

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

如何实现异步 connect

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

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

Original: https://www.cnblogs.com/wanng/p/socket-asyn-connect.html
Author: Linux开发那些事儿
Title: 如何实现异步 connect

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/607333/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • VIM快捷键全集

    VIM快捷键大法 vim是我最喜欢的编辑器,也是linux下第二强大的编辑器。 虽然emacs是公认的世界第一,我认为使用emacs并没有使用vi进行编辑来得高效。 如果是初学vi…

    Linux 2023年6月7日
    086
  • redis重点是 dir 的默认配置一定要改

    find / -name dump.rdb 发现有两个dump文件,这两个文件目录不一致,问题在于 redis.conf 文件属性dir,默认配置是dir ./ 表示在哪启动ser…

    Linux 2023年5月28日
    077
  • Mysql Date操作

    根据format字符串格式化date值。 下列修饰符可以被用在format字符串中: %W 星期名字(Sunday……Saturday) %D 有英语前缀的月份的日期(1s…

    Linux 2023年6月7日
    062
  • SQL45 将titles_test表名修改为titles_2017

    本题链接本题省略表结构。需要用到RENAME TABLE子句,该子句可实现一或多个表名称的修改。子句语法为: RENAME TABLE tbl_name TO new_tbl_na…

    Linux 2023年6月13日
    079
  • Oracle 恢复delete误删数据

    — 开启行移动功能 alter table 表名 enable row movement; — 查询删除前的数据 select * from 表名 as of timestam…

    Linux 2023年6月8日
    083
  • 自动升级shell

    make_version.sh ./make_version.sh 第一次提示”y/N” 表示接下来的操作是手动(y)还是自动(N); 自动(N)会为镜像自…

    Linux 2023年5月28日
    095
  • 嵌入式软件开发之程序架构设计-任务调度

    1 前言 在嵌入式MCU软件开发过程中,程序任务调度架构的搭建尤为重要,直接关系到该程序能支持多少功能(随着功能越多系统响应能力越弱,好的任务调度架构能够在保持相同的系统响应能力前…

    Linux 2023年6月7日
    0109
  • 前端基础之JavaScript(二)

    一、函数 1.1 函数定义 JavaScript中的函数和Python中的非常相似,只是定义方式有点区别。 // 普通函数定义 function f1() { console.lo…

    Linux 2023年6月14日
    088
  • Linux基础和命令

    Linux的哲学思想 优势 一切都是一个文件。(包括硬件,文本,二进制,源代 码) 系统中拥有小型,单一用途的程序。(一个程序只负责 做好自己的本职工作) 当遇到复杂任务,通过不同…

    Linux 2023年6月6日
    080
  • 什么是守护进程?

    在了解守护进程之前,需要先知道什么是什么是终端?什么是作业?什么是进程组?什么是会话? 在 Linux 中, 每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都…

    Linux 2023年6月8日
    0101
  • Web前端基础精品入门(HTML+CSS+JavaScript+JS)[爱前端]听课笔记3:三角形的制作

    菜单中有的项目有夏季菜单,需要添加一个三角形,这个三角形是利用两个边框不同颜色产生的楔形制作的 设置盒子的高度和宽度均为0,边框合适的大小,透明颜色,对应边设置高度、颜色 几个变形…

    Linux 2023年6月14日
    093
  • 数据结构 图

    cpp;gutter:true;</p> <h1>include</h1> <p>using namespace std;</…

    Linux 2023年6月13日
    068
  • 同城双活概述

    引言 同城双活,是年度最大的架构变更。同城容灾,对于生产的高可用保障,重大的意义和价值是不言而喻的。 用储总的话说,这么重要的架构工作,所有架构师都应该重点主导和参与。 同城双活,…

    Linux 2023年6月14日
    0112
  • [云计算]OpenStack这一篇就够了!

    OpenStack简介 OpenStack背景介绍 OpenStack应用场景 OpenStack发展历程 OpenStack架构 架构设计原则 架构全景图 核心服务组件 系统通信…

    Linux 2023年6月13日
    0210
  • zabbix5.0报错PHP时区未设置(配置参数”date.timezone”)

    解决办法 : 1、编辑文件/etc/opt/rh/rh-php72/php-fpm.d/zabbix.conf,取消注释并设置为所在地时区 vim /etc/opt/rh/rh-p…

    Linux 2023年6月7日
    072
  • 搭建dashboard 出现浏览器无法访问

    搭建dashboard 出现浏览器无法访问 解决办法参考:https://www.gl.sh.cn/2020/11/05/jie_jue_k8s_dashboard_qi_ta_l…

    Linux 2023年6月14日
    0125
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球