Linux 非阻塞connect

2023-11-01

套接字执行I/O操作有阻塞和非阻塞两种模式。在阻塞模式下,在I/O操作完成前,执行操作的函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回-1,而不管I/O是否完成,该函数所在的线程会继续运行。

客户端调用connect()发起对服务端的socket连接,如果客户端的socket描述符为阻塞模式,则connect()会阻塞到连接建立成功或连接建立超时(linux内核中对connect的超时时间限制是75s, Soliris 9是几分钟,因此通常认为是75s到几分钟不等)。如果为非阻塞模式,则调用connect()后函数立即返回,如果连接不能马上建立成功(返回-1),则errno设置为EINPROGRESS,此时TCP三次握手仍在继续。此时可以调用select()检测非阻塞connect是否完成。select指定的超时时间可以比connect的超时时间短,因此可以防止连接线程长时间阻塞在connect处。

select 判断规则:

1)如果 select 返回 -1,表示 select 出错,可以关闭 socket 套接字,重新发起连接过程;
2)如果 select 返回 0,表示在 select 超时,超时时间内未能成功建立连接,也可以再次执行 select 进行检测,如若多次超时,需返回超时错误给用户;
3)如果 select 返回大于 0 的值,则说明检测到可读或可写的套接字描述符。源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:

  • 当套接字连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写);
  • 当套接字连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写);

因此,当发现套接口描述符可读或可写时,可进一步判断是连接成功还是出错。这里必须将 第二条规则和另外一种连接正常的情况区分开,就是连接建立好了之后,服务器端发送了数据给客户端,此时 select 同样会返回非阻塞 socket 描述符既可读又可写。

对于 Unix 环境,可通过调用 getsockopt 来检测描述符集合是连接成功还是出错,但是该方法在 Linux 环境上测试是无效的。因为在 Linux 下无论网络是否发生错误,getsockopt 始终返回 0,不返回-1。若采用 getsockopt 来检查:

  • 如果连接建立是成功的,则通过 getsockopt(sockfd,SOL_SOCKET,SO_ERROR, &error,&len) 获取的 error 值将是 0;
  • 如果建立连接时遇到错误,则 errno 的值是连接错误所对应的 errno 值,比如ECONNREFUSED,ETIMEDOUT 等;

在 Linux 环境下可以使用以下方法进行测试连接是成功还是出错:再次调用connect,相应返回失败,如果错误 errno 是EISCONN,表示 socket 连接已经建立,否则认为连接失败。即在一次 select 调用之后,若发现此时套接口描述字可读或可写,则再次执行 connect 调用,此时 errno 始终仍为 EINPROGRESS,则再次执行 select 和 connect 函数,直到 errno 被置为EISCONN,表示 connect 成功。

非阻塞 connect 编程步骤:

第一步:调用 socket 创建套接字,并使用 fcntl 函数使该套接字变为非阻塞式;
第二步:调用 connect 函数请求建立连接,并判断连接是否成功建立;

  • 若 connect 调用返回 0,则表示连接请求成功建立;
  • 若 connect 调用返回 -1,首先检查 errno 错误类型,若不为 EINPROGRESS 错误,则直接退出,否则只是当前连接不能立即建立,但是已经发起的 TCP 连接请求三次握手过程会继续进行,此时调用 select 函数判断连接是否建立成功:

    • 若 select 调用返回 0,则表示 select 超时期限内不能成功建立连接,则此时返回一个超时错误,且关闭该链接,以防止 TCP 连接的三次握手过程继续进行;
    • 若 select 调用返回正值,则表示在超时期限内检查到套接字可读或可写或异常,若可读或可写,在 Unix 系统中,此时通过调用 getsockopt 函数检查连接状态,若连接成功,则该值为 0,若连接建立发生错误,则该值是对应连接错误的 errno 值;

假设在调用 select 函数之前连接已经建立,并服务器发送的数据已到达客户端,此时非阻塞套接字处于即可读又可写状态。然而由 select 函数调用返回大于 0 值时,使用 getsockopt 检查到连接出错时,非阻塞套接字也是 既可读又可写。这样就会导致移植性问题,我们可以使用下列方法代替 getsockopt 调用:

  • 调用 getpeername 代替 getsockopt。若 getpeername 以 ENOTCONN 错误失败返回,则表示连接建立失败,紧接着必须以 SO_ERROR 调用 getsockopt 取得套接字上待处理的错误;
  • 以值为 0 的长度参数调用 read 函数。若 read 调用失败,表示 connect 连接失败,read 返回的 errno 给出连接失败的原因,若连接建立成功,则 read 返回 0;
  • 再一次调用 connect 函数。如果返回错误 EISCONN,表示套接字已经连接,即连接建立成功;

示例

//非阻塞connect连接
int tcp_conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
{
    int flags, error;
    int ret = -1;
    fd_set rset,wset;
    struct timeval tval;
    //设置非阻塞
    flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    errno = 0;
    ret = connect(sockfd,saptr,salen);
    if(ret != 0)
    {
        if(errno != EINPROGRESS)
        {
            LOG_PRINT("%s\n",strerror(errno));
        }
        else
        {
            int res;
            FD_ZERO(&rset);
            FD_SET(sockfd, &rset);
            wset = rset;

            tval.tv_sec = nsec;
            tval.tv_usec = 0;
            //如果nsec为0,将使用缺省的超时时间,即其结构指针为NULL
            //如果过tval结构中的时间为0,表示不做任何等待,立即返回
            res = select(sockfd+1, &rset, &wset,NULL, nsec ? &tval : NULL);
            if (res == 0)   //超时
            {
                LOG_PRINT("%s\n",strerror(errno));
            }
            else if( res < 0 )
            {
                 LOG_PRINT("%s\n",strerror(errno));
            }
            else if( res == 1)
            {
                 if(FD_ISSET(sockfd,&wset)||FD_ISSET(sockfd,&rset))
                {
                    len = sizeof(error);
                    res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
                    LOG_PRINT("getsockopt %d %d\n",res,error);
                    if(error == 0)
                        ret = 0;
                    else
                        ret = -1;
                }
            }
            else
            {
                 LOG_PRINT("%s",strerror(errno));
            }
        }
    }   
    fcntl(sockfd, F_SETFL, flags);  /* restore file status flags */

    return ret;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux 非阻塞connect 的相关文章

  • 注销租约抛出 InvalidOperationException

    我有一个使用插件的应用程序 我在另一个应用程序域中加载插件 我使用 RemoteHandle 类http www pocketsilicon com post Things That Make My Life Hell Part 1 App
  • 如何将 protobuf-net 与不可变值类型一起使用?

    假设我有一个像这样的不可变值类型 Serializable DataContract public struct MyValueType ISerializable private readonly int x private readon
  • 确保 StreamReader 不会挂起等待数据

    下面的代码读取从 tcp 客户端流读取的所有内容 并且在下一次迭代中它将仅位于 Read 上 我假设正在等待数据 我如何确保它不会在没有任何内容可供读取时返回 我是否必须设置低超时 并在失败时响应异常 或者有更好的办法吗 TcpClient
  • 提交后禁用按钮

    当用户提交付款表单并且发布表单的代码导致 Firefox 中出现重复发布时 我试图禁用按钮 去掉代码就不会出现这个问题 在firefox以外的任何浏览器中也不会出现这个问题 知道如何防止双重帖子吗 System Text StringBui
  • 在 DataView 的 RowFilter 中选择 DISTINCT

    我试图根据与另一个表的关系缩小 DataView 中的行范围 我使用的 RowFilter 如下 dv new DataView myDS myTable id IN SELECT DISTINCT parentID FROM myOthe
  • MVC 在布局代码之前执行视图代码并破坏我的脚本顺序

    我正在尝试将所有 javascript 包含内容移至页面底部 我正在将 MVC 与 Razor 一起使用 我编写了一个辅助方法来注册脚本 它按注册顺序保留脚本 并排除重复的内容 Html RegisterScript scripts som
  • ClickOnce 应用程序错误:部署和应用程序没有匹配的安全区域

    我在 IE 中使用 FireFox 和 Chrome 的 ClickOnce 应用程序时遇到问题 它工作正常 异常的详细信息是 PLATFORM VERSION INFO Windows 6 1 7600 0 Win32NT Common
  • 复制 std::function 的成本有多高?

    While std function是可移动的 但在某些情况下不可能或不方便 复制它会受到重大处罚吗 它是否可能取决于捕获变量的大小 如果它是使用 lambda 表达式创建的 它依赖于实现吗 std function通常被实现为值语义 小缓
  • 获取两个工作日之间的天数差异

    这听起来很简单 但我不明白其中的意义 那么获取两次之间的天数的最简单方法是什么DayOfWeeks当第一个是起点时 如果下一个工作日较早 则应考虑在下周 The DayOfWeek 枚举 http 20 20 5B1 5D 3a 20htt
  • 为什么 Google 测试会出现段错误?

    我是 Google Test 的新手 正在尝试提供的示例 我的问题是 当我引入失败并设置GTEST BREAK ON FAILURE 1 或使用命令行选项 GTest 将出现段错误 我正在考虑这个例子 https code google c
  • 使用接口有什么好处?

    使用接口有什么用 我听说它用来代替多重继承 并且还可以用它来完成数据隐藏 还有其他优点吗 哪些地方使用了接口 程序员如何识别需要该接口 有什么区别explicit interface implementation and implicit
  • 如何使用 LINQ2SQL 连接两个不同上下文的表?

    我的应用程序中有 2 个数据上下文 不同的数据库 并且需要能够通过上下文 B 中的表的右连接来查询上下文 A 中的表 我该如何在 LINQ2SQL 中执行此操作 Why 我们正在使用 SaaS 产品来跟踪我们的时间 项目等 并希望向该产品发
  • 尽管我已在 python ctypes 中设置了信号处理程序,但并未调用它

    我尝试过使用 sigaction 和 ctypes 设置信号处理程序 我知道它可以与python中的信号模块一起使用 但我想尝试学习 当我向该进程发送 SIGTERM 时 但它没有调用我设置的处理程序 只打印 终止 为什么它不调用处理程序
  • 我可以使用 moq Mock 来模拟类而不是接口吗?

    正在经历https github com Moq moq4 wiki Quickstart https github com Moq moq4 wiki Quickstart 我看到它 Mock 一个接口 我的遗留代码中有一个没有接口的类
  • x86-64 AMD 上 CALL 指令的操作数生成

    以下是示例程序 objdump 的输出 080483b4
  • 如何在 Xaml 文本中添加电子邮件链接?

    我在 Windows Phone 8 应用程序中有一些大文本 我希望其中有电子邮件链接 例如 mailto 功能 这是代码的一部分
  • 为什么 std::strstream 被弃用?

    我最近发现std strstream已被弃用 取而代之的是std stringstream 我已经有一段时间没有使用它了 但它做了我当时需要做的事情 所以很惊讶听到它的弃用 我的问题是为什么做出这个决定 有什么好处std stringstr
  • 外键与独立关系 - Entity Framework 5 有改进吗?

    我读过了several http www ladislavmrnka com 2011 05 foreign key vs independent associations in ef 4 文章和问题 https stackoverflow
  • C++ 函数重载类似转换

    我收到一个错误 指出两个重载具有相似的转换 我尝试了太多的事情 但没有任何帮助 这是那段代码 CString GetInput int numberOfInput BOOL clearBuffer FALSE UINT timeout IN
  • 从列表中选择项目以求和

    我有一个包含数值的项目列表 我需要使用这些项目求和 我需要你的帮助来构建这样的算法 下面是一个用 C 编写的示例 描述了我的问题 int sum 21 List

随机推荐

  • QObject::connect: Cannot connect QTimer::timeout() to (null)::fuction()

    这几天连接一对信号与槽怎么也连接不上 后来在下面的这个网站找到了答案 https stackoverflow com questions 6238486 qt qobjectconnect cannot connect null 问题在于c
  • [CocoaPods]podspec文件中的resource和resource_bundle

    相信基本上所有的iOS开发同学针对于CocoaPods都不陌生 即便没有用过 也是久闻大名如雷贯耳 作为Objective C和Swift中非常流行的依赖管理工具 它拥有超过10000个公有程序库 通过一份Podfile文件和pod ins
  • Linux集群常用脚本(个人总结)

    声明 1 本文为我的个人复习总结 并非那种从零基础开始普及知识 内容详细全面 言辞官方的文章 2 由于是个人总结 所以用最精简的话语来写文章 3 若有错误不当之处 请指出 常用脚本编写汇总 自定义脚本放在 bin下 并把这个目录配到PATH
  • js生成随机字符串

    function randomString len len len 32 var str ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678 默认去掉了容易混淆的字符oOLl 9gq Vv Uu
  • 2023-9-8 求组合数(一)

    题目链接 求组合数 I include
  • C语言 _ 指针(超详解析 3分钟完全掌握 总结性讲解 经典通俗)

    目 录 1 指针 指针变量及指针的大小 2 指针类型 3 指针的解引用 4 指针运算 4 1指针 整数 4 2指针 指针 5 野指针 5 1 为什么会有野指针 5 2 如何规避野指针 6 指针和数组 7 二级指针 7 1二级指针的定义 8
  • Java中的加号“+”

    在Java中 加号 与数学上的加号有些不同 它有两个意思 一个是算术运算中的 一个是连接符的重要 类似于C语言中的strcat函数 怎么区分这两种作用呢 1 当 两边是非数值类型 就被看作连接符 2 当 两边都是数值类型 就被看作算术运算中
  • ZYNQ平台在SDK下引导启动UBOOT

    ZYNQ芯片 Linux系统搭建完成后 希望通过QSPI Flash的方式来进行程序加载 QSPI Flash启动则需要烧录以下文件 BOOT bin fsbl elf uboot elf uImage linux内核 zynq board
  • 小吴学前端--Element-UI tree 组件 选中节点高亮(持续高亮)

    最近开发的时候遇到的一个需求 1 加深选中单据颜色框 2 在不切换tree组件时 选中的一直保持高亮 由于对该组件不是很熟悉 记下笔记 用来巩固学习 1 点击后高亮显示的背景颜色修改 仅仅需要修改css部分即可 deep el tree n
  • 从语言到品牌

    在竞争激烈的商业环境中 一个强大的品牌所带来的广泛品牌影响力是企业成功的关键要素之一 无论是刚刚起步的初创公司 还是已经存在多年的大型企业 都需要创建最符合自身 最贴合市场受众的品牌名称 并且持续努力来塑造和维护自己的品牌 以吸引更多客户
  • WSDL文档结构详解(五)

    1 实例截图 2 wsdl文件分析
  • C++静态链接库

    1 打开VS2010 新建win32项目 命名libm 下一步选中静态库完成 2 新建空文件mylib h和mylib cpp 其中mylib h文件中代码如下 ifndef LIB H define LIB H extern C int
  • 9个爬虫基础实战汇总+4个专业爬虫练手站推荐

    个人主页 互联网阿星 格言 选择有时候会大于努力 但你不努力就没得选 作者简介 大家好我是互联网阿星 和我一起合理使用Python 努力做时间的主人 如果觉得博主的文章还不错的话 请点赞 收藏 留言 支持一下博主哦 行业资料 PPT模板 简
  • 嵌入式系统图解

    嵌入式系统组成 嵌入式系统硬件结构图 嵌入式软件运行流程 体系结构和接口 ARM Cotex M3内核架构图
  • 查看jvm运行情况

    使用 jps 或top显示当前所有java进程pid 详细的使用方法可参考博客 https blog csdn net u013250071 article details 80496623 https blog csdn net weix
  • Yolov4部署到ZYNQ系列4-网络地址调整和部署

    文章目录 前言 一 笔记本上的步骤 二 开发板上的步骤 三 部署 四 总结 前言 本文参照上一节的工作 使用Vitis AI 1 4与Vitis AI 2 5的工具得到的量化和编译 在开发板上部署 但在此之前 不想使用路由的方式 直接通过网
  • HIVE介绍(五)

    文章目录 HIVE介绍 hql语法 hive优缺点 Hive运行原理 Hive为什么要分区 partitioned by Hive与mysql的对比 Hive内部表和外部表 hive数据类型 hive数据存格式 自定义函数UDF和UDTF
  • 数字IC笔试面试常考问题及答案

    来源 知乎 链接 https zhuanlan zhihu com p 261298869 基础知识 原理务必理解透彻 锁存器的结构 DFF的结构 建立保持时间 亚稳态 STA CDC 亚稳态的成因 危害 解决方法 建立保持时间的计算 违例
  • centos 软件卸载

    1 查找系统注册名称 rpm q a grep 软件名 2 卸载 rpm e package name 即可卸载软件 参数e的作用是使rpm进入卸载模式 对名为 package name 的软件包进行卸载 由于系统中各个软件包之间相互有依赖
  • Linux 非阻塞connect

    套接字执行I O操作有阻塞和非阻塞两种模式 在阻塞模式下 在I O操作完成前 执行操作的函数一直等候而不会立即返回 该函数所在的线程会阻塞在这里 相反 在非阻塞模式下 套接字函数会立即返回 1 而不管I O是否完成 该函数所在的线程会继续运