写过网络程序的同学,应该都知道 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]);//创建 socketint32_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);#endifstd::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 是否可写了

测试

在另一台机器上执行 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 表示连接成功,其他表示连接失败

如何实现异步 connect相关推荐

  1. 要掌握的异步connect 用法

    在 socket 是阻塞模式下 connect 函数会一直到有明确的结果才会返回(或连接成功或连接失败),如果服务器地址"较远",连接速度比较慢,connect 函数在连接过程中可 ...

  2. connect函数在阻塞和非阻塞模式下的行为

    connect函数在阻塞和非阻塞模式下的行为 当socket使用阻塞模式时,connect函数会阻塞到有明确结果才会返回,如果网络环境较差,可能要等一会,影响体验, 为了解决这个问题,我们使用异步co ...

  3. Linux下使用hiredis库与libevent实现异步接口的I/O复用

    1 前言 之前的一篇文章<Linux下使用hiredis库实现优先级队列>,用的同步的接口实践: 后来遇到一个场景,同时需要处理Redis订阅的消息,又需要处理其他网络socket操作.定 ...

  4. 【★更新★】高性能 Windows Socket 服务端与客户端组件(HP-Socket v2.0.1 源代码及测试用例下载)...

    HP-Socket 以前为某大型通信项目开发了一套通用 Windows Socket TCP 底层通信组件,组件代号为 HP-Socket.现在把 HP-Socket 的所有代码向大众公开,希望能对大 ...

  5. 网络编程中的关键问题总结

    网络编程中的关键问题总结 总结下网络编程中关键的细节问题,包含连接建立.连接断开.消息到达.发送消息等等: 连接建立 包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接 ...

  6. boost::shared_ptr shared_from_this

    需要将指针再构造为一个shared_ptr时, 可以让类继承自enable_shared_from_this: 然后类内部使用shared_from_this()生成一个shared_ptr. 不能直 ...

  7. 与 30 家公司过招,得到了这章面试心法

    来源:开源中国 |编辑部的故事 作者介绍 笔者坐标上海,做技术开发,之前有几个月的时间,基本上都是在面试中度过的.我求职的职位是Linux 服务器开发,最倾向的职位是服务器开发主程或技术经理.在那几个 ...

  8. 学习笔记之与 30 家公司过招,得到了这章面试心法

    与 30 家公司过招,得到了这章面试心法 - 算法与数据结构 https://mp.weixin.qq.com/s/Ml5RdaK4KMSZMctWko7wwA 总结下来,技术面试大致有三种情形,下边 ...

  9. unix 网络编程总结

    from:http://middleware123.com/tuxedo/intro/303.html 1.处理SIGCHLD信号 当编写fork子进程处理连接的服务器程序时,子进程退出会给父进程产生 ...

最新文章

  1. 低版本系统兼容的ActionBar(四)添加Tab+添加自定义的Tab视图+Fragment
  2. linux shell dig nslookup 指定dns服务器 查询域名解析
  3. zz让你成功的九个心理定律
  4. java 计算 日期_java 计算某日期 多少天后的日期
  5. 6.494 - Kindergarten Counting Game
  6. android 获取sim卡,Android 获取手机SIM卡运营商
  7. fopen 參数具体解释
  8. matlab状态空间法算反馈阵,matlab中已知系统的状态方程怎样绘制系统阶跃响应曲线...
  9. 好久不上来,发现这个世界变得真是快啊,都.NET 2.0 AJAX了~~
  10. java 异常java.lang.UnsupportedOperationException
  11. Android集成三方腾讯浏览器X5内核
  12. 第25版 OpenStack Yoga 已发布:稳定性与创新并重
  13. 数据链路层LLDP协议
  14. 完全免费的Windows代码签名证书(大神勿喷)
  15. 解压ubi文件_UBI文件系统
  16. Threejs实现酷炫3D地球技术点汇总
  17. 企业发文的红头文件_公司红头文件格式范文6篇
  18. Linux用RPM安装vsftpd,Linux通过RPM方式安装vsftpd
  19. 苹果 iOS 10 更新消息汇总,iPhone 4s 可能用不了
  20. C++ 开源协程库 libco——原理及应用

热门文章

  1. 软件测试设计之MFQ建模维度
  2. vagrant日常操作
  3. 在街上弹弹吉它、唱唱歌怎么样?
  4. 云服务器部署和安装开源笔记leanote完整教程
  5. 学习笔记(2022-5-26)——bind-dns
  6. Cocos2d坐标系具体解释
  7. Android API:Activity.managedQuery()
  8. 在游戏中学会进制转换
  9. JS之replaceState与pushState的妙用
  10. 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)