流程理解
create
创建一个用于监听的socket,这个socket不负责连接建立后的数据交互,只负责端口的监听
bind
为socket关联地址和端口,可以不使用;
listen
为端口连接创建了一个队列,当系统检测到带端口有连接请求(3次握手后)时,将该请求放入队列
accept
开始准备接受连接conn, addr = socket.accept()
accept就是不断轮训着去队列中取出连接并建立真正的连接,此时listen继续去监听端口
accept就是不断轮训着去队列中取出连接并建立真正的连接,此时listen继续去监听端口
accept()
返回的socket描述符的端口和listen描述符端口是一样的吗?
一样
- 一个socket是由一个五元组来唯一标示的,即(协议,server_ip, server_port, client_ip, client_port)。
- 只要该五元组中任何一个值不同,则其代表的socket就不同。
(忽略协议的区别,在同一协议的基础上)服务器端的listen socket的端口可以看成(server_ip, server_port, *, *)
,其中*
是通配符,它跟任何一个client_ip, client_port值都不同,可以简单看成是(0,0)对,当然实现不是这样的。这样在服务器端accept之后,返回的连接socket的四元组就是(server_ip, server_port, client_ip, client_port),这里的client_ip,client_port因连接的客户端的不同而不同。
所以accept返回的socket和listen socket是不同的
不同之处就在于四元组中的客户端ip和port,而服务器端的server_ip和server_port还是相同的,也就是accpet()
函数返回的新的socket描述符的端口和listen端口是一样的。可以使用getsockname()
函数来查看它们之间的不同。
accept
是又产生一个Socket端口吗?
如果一个程序创建了一个socket,并让其监听80端口,其实是向TCP/IP协议栈声明了其对80端口的占有。以后,所有目标是80端口的TCP数据包都会转发给该程序(这里的程序,因为使用的是Socket编程接口,所以首先由Socket层来处理)。所谓accept函数,其实抽象的是TCP的连接建立过程。accept函数返回的新socket其实指代的是本次创建的连接,而一个连接是包括两部分信息的,一个是源IP和源端口,另一个是宿IP和宿端口。所以,accept可以产生多个不同的socket,而这些socket里包含的宿IP和宿端口是不变的,变化的只是源IP和源端口。这样的话,这些socket宿端口就可以都是80,而Socket层还是能根据源/宿对来准确地分辨出IP包和socket的归属关系,从而完成对TCP/IP协议的操作封装!
单机最大的TCP连接数及其修改
一个误解: 单个服务器程序可承受最大连接数“理论”上是“65535”
65535这个数字的由来,很多人想当然地将它与port最大值联系起来。的确,TCP的端口数,最大值确实为65535,但最大连接数即
accept
数,与端口号无关;
单机最大TCP连接数由以下介个参数共同决定:
-
最大TCP连接数
可通过配置表等配置,默认为16M -
最大动态端口数
MaxUserPort
TCP客户端和服务器连接时,客户端必须分配一个动态端口,默认情况下这个动态端口的分配范围为 1024-5000;
可通过配置表等配置;
- 最大PCB数
MaxFreeTcbs
系统为每个TCP 连接分配一个TCP 控制块(TCP control block or TCB),这个控制块用于缓存TCP连接的一些参数,每个TCB需要分配 0.5 KB的pagepool 和 0.5KB 的Non-pagepool,也就说,每个TCP连接会占用 1KB 的系统内存
- 最大TCN Hash Table数
MaxHashTableSize
TCB 是通过Hash table 来管理的
这个值指明分配 pagepool 内存的数量,也就是说,如果MaxFreeTcbs = 1000 , 则 pagepool 的内存数量为 500KB
那么 MaxHashTableSize 应大于 500 才行。这个数量越大,则Hash table 的冗余度就越高,每次分配和查找 TCP 连接用时就越少。这个值必须是2的幂,且最大为65536.
- 网络包大小以及内存消耗等
实例
- 单个连接消耗内存
默认大小都是87K和16K, 最低是4K和4K, 最高是2M,2M, 实际使用默认值最低也要保留8K,8K.
1 | /proc/sys/net/ipv4/tcp_rmem #for read |
- 逻辑IO缓冲区
就是比如你监听了recv事件 事件来了 你要有内存可用(一般都是socket建立起就分配好,断开才会释放的).
这个内存是自己写socket程序时候自己控制的, 最低也要4K,4K, 实际使用8K,8K至少.
现在设定一个优化方案和使用场景, 首先假设4G内存全部为空闲(系统和其他进程也要内存的…
-
假如网络包的大小都可以控制在4K以下, 假设所有连接的网络都不会拥堵, 或者拥堵时候的总量在4K以下:
一个连接的内存消耗是4+4+4+4=16K
4G/16K=26.2万并发 -
假如网络包的大小都可以控制在8K以下, 假设所有连接的网络都不会拥堵, 或者拥堵时候的总量在8K以下
一个socket的内存占用介于 24K ~ 32K之间, 保守的按照32K算
4G/32K=13.1万并发, 这个在生产环境作为一个纯网络层面的内存消耗, 是可以作为参考的. -
假如使用默认配置, 假如所有连接的网络都出现严重拥堵, 不考虑逻辑上的发送队列的占用,
使用默认配置是2M+2M+8+8 ~= 4M
4G/4M=1024并发 -
如果考虑到发送队列也拥堵的话 自己脑补.
如果只是为了跑分 为了并发而优化, 没有常驻的逻辑缓冲区 并且socket的网络吞吐量很小并且负载平滑, 把socket buffer size设置系统最低.
那么是
4G/8K = 52.4万并发 这个应该是极限值了.