在 socket 是阻塞模式下 connect 函数会一直到有明确的结果才会返回(或连接成功或连接失败),如果服务器地址“较远”,连接速度比较慢,connect 函数在连接过程中可能会导致程序阻塞在 connect 函数处好一会儿(如两三秒之久),虽然这一般也不会对依赖于网络通信的程序造成什么影响,但在实际项目中,我们一般倾向使用所谓的异步的 connect 技术,或者叫非阻塞的 connect。这个流程一般有如下步骤:

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

按上述流程编写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*** 异步的connect写法,nonblocking_connect.cpp* zhangyl 2018.12.17*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"int main(int argc, char* argv[])
{//1.创建一个socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd == -1){std::cout << "create client socket error." << std::endl;return -1;}//将 clientfd 设置成非阻塞模式 int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1){close(clientfd);std::cout << "set socket to nonblock error." << std::endl;return -1;}//2.连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);serveraddr.sin_port = htons(SERVER_PORT);for (;;){int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));if (ret == 0){std::cout << "connect to server successfully." << std::endl;close(clientfd);return 0;} else if (ret == -1) {if (errno == EINTR){//connect 动作被信号中断,重试connectstd::cout << "connecting interruptted by signal, try again." << std::endl;continue;} else if (errno == EINPROGRESS){//连接正在尝试中break;} else {//真的出错了,close(clientfd);return -1;}}}fd_set writeset;FD_ZERO(&writeset);FD_SET(clientfd, &writeset);//可以利用tv_sec和tv_usec做更小精度的超时控制struct timeval tv;tv.tv_sec = 3;  tv.tv_usec = 0;if (select(clientfd + 1, NULL, &writeset, NULL, &tv) == 1){std::cout << "[select] connect to server successfully." << std::endl;} else {std::cout << "[select] connect to server error." << std::endl;}//5. 关闭socketclose(clientfd);return 0;
}

为了区别到底是在调用 connect 函数时判断连接成功还是通过 select 函数判断连接成功,我们在后者的输出内容中加上了“[select]”标签以示区别。

我们先用 nc 命令启动一个服务器程序:

1
nc -v -l 0.0.0.0 3000

然后编译客户端程序并执行:

1
2
3
[root@localhost testsocket]# g++ -g -o nonblocking_connect nonblocking_connect.cpp
[root@localhost testsocket]# ./nonblocking_connect
[select] connect to server successfully.

我们把服务器程序关掉,再重新启动一下客户端,这个时候应该会连接失败,程序输出结果如下:

1
2
[root@localhost testsocket]# ./nonblocking_connect
[select] connect to server successfully.

奇怪?为什么连接不上也会得出一样的输出结果?难道程序有问题?这是因为:

  • 在 Windows 系统上,一个 socket 没有建立连接之前,我们使用 select 函数检测其是否可写,能得到正确的结果(不可写),连接成功后检测,会变为可写。所以,上述介绍的异步 connect 写法流程在 Windows 系统上是没有问题的。

  • 在 Linux 系统上一个 socket 没有建立连接之前,用 select 函数检测其是否可写,你也会得到可写的结果,所以上述流程并不适用于 Linux 系统。正确的做法是,connect 之后,不仅要用 select 检测可写,还要检测此时 socket 是否出错,通过错误码来检测确定是否连接上,错误码为 0 表示连接上,反之为未连接上。完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/*** Linux 下正确的异步的connect写法,linux_nonblocking_connect.cpp* zhangyl 2018.12.17*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       "helloworld"int main(int argc, char* argv[])
{//1.创建一个socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd == -1){std::cout << "create client socket error." << std::endl;return -1;}//将 clientfd 设置成非阻塞模式,int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1){close(clientfd);std::cout << "set socket to nonblock error." << std::endl;return -1;}//2.连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);serveraddr.sin_port = htons(SERVER_PORT);for (;;){int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));if (ret == 0){std::cout << "connect to server successfully." << std::endl;close(clientfd);return 0;} else if (ret == -1) {if (errno == EINTR){//connect 动作被信号中断,重试connectstd::cout << "connecting interruptted by signal, try again." << std::endl;continue;} else if (errno == EINPROGRESS){//连接正在尝试中break;} else {//真的出错了,close(clientfd);return -1;}}}fd_set writeset;FD_ZERO(&writeset);FD_SET(clientfd, &writeset);//可以利用tv_sec和tv_usec做更小精度的超时控制struct timeval tv;tv.tv_sec = 3;  tv.tv_usec = 0;if (select(clientfd + 1, NULL, &writeset, NULL, &tv) != 1){std::cout << "[select] connect to server error." << std::endl;close(clientfd);return -1;}int err;socklen_t len = static_cast<socklen_t>(sizeof err);if (::getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &err, &len) < 0){close(clientfd);return -1;}if (err == 0)std::cout << "connect to server successfully." << std::endl;elsestd::cout << "connect to server error." << std::endl;//5. 关闭socketclose(clientfd);return 0;
}

当然,在实际的项目中,第 3 个步骤中 Linux 平台上你也可以使用 poll 函数来判断 socket 是否可写;在 Windows 平台上你可以使用 WSAEventSelect 或 WSAAsyncSelect 函数判断连接是否成功,关于这三个函数我们将在后面的章节中详细讲解,这里暂且仅以 select 函数为例。

要掌握的异步connect 用法相关推荐

  1. 如何实现异步 connect

    写过网络程序的同学,应该都知道 connect 函数,在 socket 开始读写操作之前,先要进行连接,也即 TCP 的三次握手 , 这个过程就是在 connect 函数中完成的, connect 函 ...

  2. dojo初解和dojo.connect用法(转)

    最近在学习arcgis javascript api,javascript api是基于Dojo,所以先熟悉一下Dojo. DOJO常用的: 1,通过dojo.require以类似C编程中#inclu ...

  3. Java8 异步CompletableFuture 用法

    1.核心代码 CompletableFuture<Map<String, String>> onlineShopNoFuture = CompletableFuture.sup ...

  4. UDP socket编程中使用connect

    转自:http://hi.baidu.com/rwen2012/item/545a39ba741307d085dd7957 标准的udp客户端开了套接口后,一般使用sendto和recvfrom函数来 ...

  5. 进程(并发,并行) join start 进程池 (同步异步)

    一.背景知识 顾名思义,进程即正在执行的一个过程.进程是对正在运行程序的一个抽象.进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有 ...

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

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

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

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

  8. NSURLConnection-网络访问(同步异步)

    /********访问网络同步请求.异步请求基本概念********/ // 访问网络的基本概念 访问网络方式: 同步请求 : 主线程执行 , 请求无法取消, 无法坚挺加载进度 异步请求 : 多线程执 ...

  9. 路由懒加载import和require用法的区别

    vue-router同步和异步普通用法: // 普通import同步加载用法 import HelloWorld from "@/components/HelloWorld.vue" ...

最新文章

  1. ubuntu 常见配置文件
  2. 3.QT中的debug相关的函数,以及文件锁的使用
  3. 飞鸽传书(IPMSG)协议(翻译稿)
  4. 用Swashbuckle给ASP.NET Core的项目自动生成Swagger的API帮助文档
  5. 【渝粤教育】国家开放大学2018年秋季 0017-22T大学英语 参考试题
  6. JAVA :RESTLET开发实例(一)基于JAX-RS的REST服务
  7. 优麒麟(Ubuntu Kylin)安装日志
  8. 《Java程序员职场全功略:从小工到专家》连载四:IT人不容易
  9. js获取单选按钮选项
  10. kettle的下载|运行及简单kettle数据抽取(MAC kettle|windows Kettle)
  11. ubuntu安装QQ教程
  12. 智能网关路灯杆智慧照明解决方案
  13. php form标签中的属性,form标签中的属性详解
  14. python旋转图片
  15. built a JNCIS LAB系列:Chapter 1 Routing Policy Processing v1.0
  16. 相较于本地渲染,云渲染用起来感觉怎么样?
  17. 集群系统性能测试Linpack-HPL安装
  18. Codeforces 300D Painting Square 题解
  19. 艺术摄影--曝光与测光(2学时)--SDUST
  20. S32K144时钟配置

热门文章

  1. jsPdf+html2Canvas+react实现前端页面导出pdf,并解决jspdf分页图片过长截断问题
  2. 硬盘占用100%解决
  3. 如火如荼的数字化转型,有哪些发展趋势?
  4. Mysql去重查询---DISTINCT、group by
  5. 更“逼真”更“真实”,Facebook 还有什么不能实现?
  6. 【无标题】报告实录文本挖掘与分析(简单版)
  7. 刚刚用python爬取一千个微信朋友圈数据,他们的秘密原来这么多。
  8. 【与达梦同行】DM8适配JetBrains_Exposed框架
  9. [451]pandas.get_dummies 的用法
  10. War3快捷键大全—通用快捷键