一个网络资深者发起的思考

陈硕 (giantchen AT gmail)

blog.csdn.net/Solstice

前几天我在新浪微博上出了两道有关 TCP 的思考题,引发了一场讨论 http://weibo.com/1701018393/eCuxDrta0Nn 。

第一道初级题目是:

有一台机器,它有一个 IP,上面运行了一个 TCP 服务程序,程序只侦听一个端口,问:从理论上讲(只考虑 TCP/IP 这一层面,不考虑IPv6)这个服务程序可以支持多少并发 TCP 连接?答 65536 上下的直接刷掉。

具体来说,这个问题等价于:有一个 TCP 服务程序的地址是 1.2.3.4:8765,问它从理论上能接受多少个并发连接?

第二道进阶题目是:

一台被测机器 A,功能同上,同一交换机上还接有一台机器 B,如果允许 B 的程序直接收发以太网 frame,问:让 A 承担 10 万个并发 TCP 连接需要用多少 B 的资源?100万个呢?

从讨论的结果看,很多人做出了第一道题,而第二道题几乎无人问津。

这里先不公布答案(第一题答案见文末),让我们继续思考一个本质的问题:一个 TCP 连接要占用多少系统资源。

在现在的 Linux 操作系统上,如果用 socket()/connect() 或 accept() 来创建 TCP 连接,那么每个连接至少要占用一个文件描述符(file descriptor)。为什么说“至少”?因为文件描述符可以复制,比如 dup();也可以被继承,比如 fork();这样可能出现系统里边同一个 TCP 连接有多个文件描述符与之对应。据此,很多人给出的第一题答案是:并发连接数受限于系统能同时打开的文件数目的最大值。这个答案在实践中是正确的,却不符合原题意。

如果抛开操作系统层面,只考虑 TCP/IP 层面,建立一个 TCP 连接有哪些开销?理论上最小的开销是多少?考虑两个场景:

1. 假设有一个 TCP 服务程序,向这个程序成功发起连接需要做哪些事情?换句话说,如何才能让这个 TCP 服务程序认为有客户连接到了它(让它的 accept() 调用正常返回)?

2. 假设有一个 TCP 客户端程序,让这个程序成功建立到服务器的连接需要做哪些事情?换句话说,如何才能让这个 TCP 客户端程序认为它自己已经连接到服务器了(让它的 connect() 调用正常返回)?

以上这两个问题问的不是如何编程,如何调用 Sockets API,而是问如何让操作系统的 TCP/IP 协议栈认为任务已经成功完成,连接已经成功建立。

学过 TCP/IP 协议,理解三路握手的同学明白,TCP 连接是虚拟的连接,不是电路连接,维持 TCP 连接理论上不占用网络资源(会占用两头程序的系统资源)。只要连接的双方认为 TCP 连接存在,并且可以互相发送 IP packet,那么 TCP 连接就一直存在。

对于问题 1,向一个 TCP 服务程序发起一个连接,客户端(为明白起见,以下称为 faketcp 客户端)只需要做三件事情(三路握手):

1a. 向 TCP 服务程序发一个 IP packet,包含 SYN 的 TCP segment

1b. 等待对方返回一个包含 SYN 和 ACK 的 TCP segment

1c. 向对方发送一个包含 ACK 的 segment

在做完这三件事情之后,TCP 服务器程序会认为连接已建立。而做这三件事情并不占用客户端的资源(?),如果faketcp 客户端程序可以绕开操作系统的 TCP/IP 协议栈,自己直接发送并接收 IP packet 或 Ethernet frame 的话。换句话说,faketcp 客户端可以一直重复做这三件事件,每次用一个不同的 IP:PORT,在服务端创建不计其数的 TCP 连接,而 faketcp 客户端自己毫发无损。很快我们将看到如何用程序来实现这一点。

对于问题 2,为了让一个 TCP 客户端程序认为连接已建立,faketcp 服务端只需要做两件事情:

2a. 等待客户端发来的 SYN TCP segment

2b. 发送一个包含 SYN 和 ACK 的 TCP segment

2c. 忽视对方发来的包含 ACK 的 segment

在做完这两件事情(收一个 SYN、发一个 SYN+ACK)之后,TCP 客户端程序会认为连接已建立。而做这三件事情并不占用 faketcp 服务端的资源(?)换句话说,faketcp 服务端可以一直重复做这两件事件,接受不计其数的 TCP 连接,而 faketcp 服务端自己毫发无损。很快我们将看到如何用程序来实现这一点。

基于对以上两个问题的分析,说明单独谈论“TCP 并发连接数”是没有意义的,因为连接数基本上是要多少有多少。更有意义的性能指标或许是:“每秒钟收发多少条消息”、“每秒钟收发多少字节的数据”、“支持多少个活动的并发客户”等等。

faketcp 的程序实现

代码见: https://github.com/chenshuo/recipes/tree/master/faketcp 可以直接用 make 编译

为了验证我上面的说法,我写了几个小程序来实现 faketcp,这几个程序可以发起或接受不计其数的 TCP 并发连接,并且不消耗操作系统资源,连动态内存分配都不会用到。

我家里有一台运行 Ubuntu Linux 10.04 的 PC 机,hostname 是 atom,所有的试验都在这上面进行。

家里试验环境的网络配置是:

net

陈硕在《谈一谈网络编程学习经验》中曾提到“可以用 TUN/TAP 设备在用户态实现一个能与本机点对点通信的 TCP/IP 协议栈”,这次的试验正好可以用上这个办法。

试验的网络配置是:

tun

具体做法是:在 atom 上通过打开 /dev/net/tun 设备来创建一个 tun0 虚拟网卡,然后把这个网卡的地址设为 192.168.0.1/24,这样 faketcp 程序就扮演了 192.168.0.0/24 这个网段上的所有机器。atom 发给 192.168.0.2~192.168.0.254 的 IP packet 都会发给 faketcp 程序,faketcp 程序可以模拟其中任何一个 IP 给 atom 发 IP packet。

程序分成几步来实现。

第一步:实现 icmp echo 协议,这样就能 ping 通 faketcp 了。

代码见 https://github.com/chenshuo/recipes/blob/master/faketcp/icmpecho.cc

其中响应 icmp echo request 的函数在 https://github.com/chenshuo/recipes/blob/master/faketcp/faketcp.cc#L57 这个函数在后面的程序中也会用到。

运行方法,打开 3 个命令行窗口:

1. 在第 1 个窗口运行 sudo ./icmpecho ,程序显示

allocted tunnel interface tun0

2. 在第 2 个窗口运行

$ sudo ifconfig tun0 192.168.0.1/24

$ sudo tcpdump -i tun0

3. 在第 3 个窗口运行

$ ping 192.168.0.2

$ ping 192.168.0.3

$ ping 192.168.0.234

发现每个 192.168.0.X 的 IP 都能 ping 通。

第二步:实现拒绝 TCP 连接的功能,即在收到 SYN TCP segment 的时候发送 RST segment。

代码见 https://github.com/chenshuo/recipes/blob/master/faketcp/rejectall.cc

运行方法,打开 3 个命令行窗口,头两个窗口的操作与前面相同,运行的 faketcp 程序是 ./rejectall

3. 在第 3 个窗口运行

$ nc 192.168.0.2 2000

$ nc 192.168.0.2 3333

$ nc 192.168.0.7 5555

发现向其中任意一个 IP 发起的 TCP 连接都被拒接了。

第三步:实现接受 TCP 连接的功能,即在收到SYN TCP segment 的时候发回 SYN+ACK。这个程序同时处理了连接断开的情况,即在收到 FIN segment 的时候发回 FIN+ACK。

代码见 https://github.com/chenshuo/recipes/blob/master/faketcp/acceptall.cc

运行方法,打开 3 个命令行窗口,步骤与前面相同,运行的 faketcp 程序是 ./acceptall。这次会发现 nc 能和 192.168.0.X 中的每一个 IP 每一个 PORT 都能连通。还可以在第 4 个窗口中运行 netstat –tpn ,以确认连接确实建立起来了。如果在 nc 中输入数据,数据会堆积在操作系统中,表现为 netstat 显示的发送队列(Send-Q)的长度增加。

第四步:在第三步接受 TCP 连接的基础上,实现接收数据,即在收到包含 payload 数据 的 TCP segment 时发回 ACK。

代码见 https://github.com/chenshuo/recipes/blob/master/faketcp/discardall.cc

运行方法,打开 3 个命令行窗口,步骤与前面相同,运行的 faketcp 程序是 ./acceptall。这次会发现 nc 能和 192.168.0.X 中的每一个 IP 每一个 PORT 都能连通,数据也能发出去。还可以在第 4 个窗口中运行 netstat –tpn ,以确认连接确实建立起来了,并且发送队列的长度为 0。

这一步已经解决了前面的问题 2,扮演任意 TCP 服务端。

  

第五步:解决前面的问题 1,扮演客户端向 atom 发起任意多的连接。

代码见 https://github.com/chenshuo/recipes/blob/master/faketcp/connectmany.cc

这一步的运行方法与前面不同,打开 4 个命令行窗口。

1. 在第 1 个窗口运行 sudo ./connectmany 192.168.0.1 2007 1000 ,表示将向 192.168.0.1:2007 发起 1000 个并发连接。

程序显示

allocted tunnel interface tun0
press enter key to start connecting 192.168.0.1:2007

2. 在第 2 个窗口运行

$ sudo ifconfig tun0 192.168.0.1/24

$ sudo tcpdump -i tun0

3. 在第 3 个窗口运行一个能接收并发 TCP 连接的服务程序,可以是 httpd,也可以是 muduo 的 echo 或 discard 示例,程序应 listen 2007 端口。

4. 回到第 1 个窗口中敲回车,然后在第 4 个窗口中用 netstat -tpn 来观察并发连接。

有兴趣的话,还可以继续扩展,做更多的有关 TCP 的试验,以进一步加深理解,验证操作系统 TCP/IP 协议栈面对不同输入的行为。甚至可以按我在《谈一谈网络编程学习经验》中提议的那样,实现完整的 TCP 状态机,做出一个简单的 mini tcp stack。

第一道题的答案:

在只考虑 IPv4 的情况下,并发数的理论上限是 2**48。考虑某些 IP 段被保留了,这个上界可适当缩小,但数量级不变。实际的限制是操作系统全局文件描述符的数量,以及内存大小。

一个 TCP 连接有两个 end points,每个 end point 是 {ip, port},题目说其中一个 end point 已经固定,那么留下一个 end point 的自由度,即 2 ** 48。客户端 IP 的上限是 2**32 个,每个客户端IP发起连接的上限是 2**16,乘到一起得理论上限。

即便客户端使用 NAT,也不影响这个理论上限。(为什么?)

在真实的 Linux 系统中,可以通过调整内核参数来支持上百万并发连接,具体做法见:

http://urbanairship.com/blog/2010/09/29/linux-kernel-tuning-for-c500k/

http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/434792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++课堂整理--第一章内容

提前声明: 本文内容为华北水利水电大学研究生C课程,如有 侵权请告知,作者会予以删除 1.C特点 1.历史悠久。2.应用广泛。3.兼容c。4.面向对象。5.适合编写系统程序。6.有助于理解计算机的工作过程,深入理解计算机的原理和概念 …

oauth2 access_denied 不允许访问_OAuth 2 是什么-入门介绍

OAuth 2是什么OAuth 2是一个可以通过浏览器,手机等多种设备进行安全授权的一个标准简单的开源协议。随着互联网的兴起以及普及,越来越多的应用出现在用户的面前。这些应用大部分都是相对独立的以及由不同的公司进行运营的。不同的应用也保存了不同的数据…

基于TCP协议的网络程序(基础学习)

下图是基于TCP协议的客户端/服务器程序的一般流程: 图 37.2. TCP协议通讯流程 服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect(…

一段代码认识C++中const不同位置的用处

#include<iostream> using namespace std ; int main () { const int A 78 ;const int B 25 ;int C 13 ;//---------const在数据类型前-------------------- const int *pi &A ;//*pi 56 ;// 错误, 不能修改所指常量。此时*pi指向的是常量A。 pi &…

wcf系列学习5天速成——第五天 服务托管

今天是系列的终结篇&#xff0c;当然要分享一下wcf的托管方面的知识。 wcf中托管服务一般有一下四种&#xff1a; Console寄宿&#xff1a; 利于开发调试&#xff0c;但不是生产环境中的最佳实践。 winform寄宿&#xff1a; 方便与用户进行交互&#x…

ASP.NET MVC Music Store教程(2):控制器

ASP.NET MVC Music Store教程&#xff08;2)&#xff1a;控制器 转自http://firechun.blog.163.com/blog/static/3180452220110272197830/在传统的Web架构中&#xff0c;URL总是映射到磁盘上的文件。例如&#xff1a;一个类似于“/Products.aspx”或“/Products.php”的URL可能…

C语言:利用泰勒级数计算sinx的值

题目&#xff1a; 代码&#xff1a; #include<stdio.h> #include<math.h> int main(){int sign1,n1;double x3,term,a,sinx0;//scanf("%lf",&x);termx;while(fabs(term)>0.00001){sinxsign*term;nn2;apow(x,n);double b1;for(int i1;i<n;i){…

C++课堂整理--第二章内容

提前声明&#xff1a; 本文内容为华北水利水电大学研究生C课程&#xff0c;如有 侵权请告知&#xff0c;作者会予以删除 1程序控制结构 语句是程序的基本语法成分。程序设计语言的语句按功能可以分成三类&#xff1a;声明语句 指示编译器分配内存&#xff0c;或者提供程序…

我的电脑 III

耳机用的是飞利浦的&#xff0c;和上学那时候的差不多 老的那个睡觉的时候压碎了&#xff0c;我胖了 然后去找音箱 网店&#xff0c;专卖店&#xff0c;电脑城&#xff0c;走了一遍又一遍 没个看的上的 一个月以后&#xff0c;终于决定买飞利浦那个了 白色的&#xff0c;和整个…

mybatis删除成功返回0_你还在用分页?试试 MyBatis 流式查询,真心强大!

转自&#xff1a;捏造的信仰segmentfault.com/a/1190000022478915基本概念流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器&#xff0c;应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。如果没有流式查询&#xff0c;我们想要从数据库取 1000…

C++程序设计--第三章内容

提前声明&#xff1a; 本文内容为华北水利水电大学研究生C课程&#xff0c;如有 侵权请告知&#xff0c;作者会予以删除 1.函数 函数作用 —— 任务划分&#xff1b;代码重用定义形式 类型 函数名 &#xff08; 形式参数表&#xff09;{语句序列}调用形式 函数名&#x…

Linux TCP server系列(5)-select模式下的单进程server

目标&#xff1a; 让服务器退化为单进程模式&#xff0c;但是利用select来提升性能 思路&#xff1a; &#xff08;1&#xff09;服务器 传统的单进程服务器一旦accept了客户端的TCP连接后&#xff0c;就转入客户请求的处理&#xff0c;处理完成后才能再一次的调用…

替换元素_80%的前端会答错的问题:lt;imggt;是什么元素?

前言某天晚上&#xff0c;和几个朋友去撸串&#xff0c;突然就聊到了面试&#xff0c;都在感叹现在的面试题太变态了&#xff0c;其中一个突然很神秘的问我&#xff1a;“你写前端这么久了&#xff0c;那你知道 <img> 是什么元素吗&#xff1f;”于是我结合平时写页面的经…

用指针变量访问数组

一维指针 地址值 a 相当于 & a[ 0 ] a 1 相当于 & a[ 1 ] a 2 相当于 & a[ 2 ] a i 相当于 & a[ i ] 元素值 a [ 0 ] * a a [ 1 ] 相当于 * ( a 1 ) a [ 2 ] 相当于 * ( a 2 ) a [ i ] 相当于 * ( a i ) 二维 用指…

Linux TCP server系列(6)-select模式下的多线程server

目标&#xff1a; 修改上一篇的select模式下的server&#xff0c;让它使用多线程来处理客户端请求&#xff08;多进程的模式已经在上篇中加了注释&#xff09;。 思路&#xff1a; &#xff08;1&#xff09;服务器 我们已经在之前的客户端模型多个并发用户的过程中使用过多线程…

单选按钮_PerlTk教程之按钮Button、复选按钮Checkbutton、单选按钮Radiobutton(附完整代码)...

《Perl-Tk教程之按钮Button、复选按钮Checkbutton、单选按钮Radiobutton》Perl-Tk中有三种不同形式的按钮组件可供选择&#xff0c;它们分别是按钮(Button), 复选按钮(Checkbutton), 和单选按钮(Radiobutton)&#xff0c;如下图所示&#xff1a;这三种按钮看起来是不同的&#…

好奇怪呀后面加什么标点_狗狗吃饭时奇怪的小动作,你知道代表什么吗?做个懂狗的好主人...

狗狗有时候因为一些奇怪的小行为&#xff0c;会让主人觉得很可爱。如果我们希望能够了解狗狗更多一些&#xff0c;那么我们需要透过它们的行为本身&#xff0c;去理解背后所代表的含义&#xff0c;才能和狗狗更亲密的交流。很多狗狗在吃饭的时候&#xff0c;也会表现出一些奇奇…

开机未发现nvidia控制面板_修改这几个选项,就能提升你的开机速度

最近电脑非常卡&#xff0c;有时真的想把它给砸了&#xff0c;慢的自己都受不了&#xff0c;开机几分钟&#xff0c;开机完还要等上好久才能运行软件&#xff0c;都快受不了&#xff0c;要不是看在已经是10前的买的电脑&#xff0c;早就问候产商了&#xff0c;电脑缓慢的开机速…

arcgis mxt模板 创建工具条无法保存_【从零开始学GIS】ArcGIS中的绘图基本操作(二)...

大家好&#xff0c;我是肝教程肝到熊猫眼的三三。本系列教程的发布&#xff0c;受到了很多同学的鼓励&#xff0c;大家在后台或微信上表达出对教程的喜爱&#xff0c;这便是更新教程的最大动力。上回教程讲解了“GIS基本操作”、“创建文档&#xff06;加载数据”、“创建GIS数…

vivado中交织模块_搞定Markdown中的图片,一劳永逸的方法!

经常用markdown写博客的朋友一定都体会过markdown图片的蛋疼之处&#xff0c;并不是说图片的这用引用方式不好&#xff0c;而且图片要放到什么服务器上&#xff1f;以我个人为例&#xff0c;写了一篇markdown&#xff0c;想在不修改任何地方的同时适用于各种平台。刚开始使用ma…