linux高性能网络编程摘要

823 阅读10分钟

1.每个 TCP 连接占用多少内存 ?

由于TCP 连接在建立时并不会真的去分配接收缓冲区和发送缓冲区,所以不能通过简单看缓冲区.

那TCP连接最少占多少呢?

从逻辑上说,通过文件描述符 sockfd 找到对应的 tcp_sock 的途径是:

current->files->fdt->fd[sockfd]->private_data->sk。

这里每个 struct 的大小可以通过 /proc/slabinfo 找到,本文以陈硕家某台机器上运行的 Debian 8 x86-64 为例(Linux 内核版本 3.16):

|         struct          | size |  slab cache name      |

| ---------------- | ---- | ------------------     |

|            file            | 256 |           "filp"                 | 

|         dentry         | 192  |         "dentry"             |

|     socket_alloc    | 640  | "sock_inode_cache" |

|        tcp_sock       | 1792 |            "TCP"             |

|      socket_wq       |   64   |        "kmalloc-64"    |

| inet_bind_bucket  |   64  |  "tcp_bind_bucket"  |

|           epitem         |  128  |     "eventpoll_epi"    |

| tcp_request_sock |  256  | "request_sock_TCP" |

因此,每个 TCP socket 占用的内存最少是 256 + 192 + 640 + 1792 + 64 = 2944 字节。后面的实验表明,实际占用的字节数会比这个略大 .(SLAB等因素)

实际测试结果 创建 10000 个 TCP socket 会使用 31552 KB 内存(通过比较 /proc/meminfo 得出),即每个 TCP socket 占用 3.155 KB,这个数字很接近上面考虑 SLAB overhead 之后的计算结果。



2.最大文件描述符数

由于linux一切为文件,所以TCP连接也是一个fd(文件描述符),linux设置了一定的上限,当需要支持高并发的时候,将要修改其上限.


查看用户级文件描述符数限制的方法:

$ ulimit -n


临时设置用户级文件描述符数限制:

$ ulimit -SHn max-file-number 

-H 设置硬资源限制,硬资源限制用于控制软限制。限定一旦设置只有root用户可以增加硬限制,普通用户只能减少自己的硬限制大小。


-S 设置弹性资源限制,弹性限制用于限制具体的用户或者进程。设置后普通用户可以增加,但是不能超过硬限制大小。
如果不指定-S或者-H,那么弹性资源限制和硬限制将同时设置。

soft nofile (软限制)是指Linux在当前系统能够承受的范围内进一步限制用户同时打开的文件数.

hard nofile (硬限制)是根据系统硬件资源状况(主要是系统内存)计算出来的系统最多可同时打开的文件数量 通常软限制小于或等于硬限制.


永久设置用户级文件描述符数限制,需要在/etc/security/limits.conf文件中加入如下两项:

* hard nofile max-file-number

* soft nofile max-file-number

第一行是指系统的硬限制,第二行是软限制. * 表示所有用户,也可以指定具体的用户名.



临时设置系统级文件描述符数限制:

sysctl -w fs.file-max = max-file-number


永久设置系统级文件描述符数限制,需要在/etc/sysctl.conf文件中添加如下一项:

fs.file-max = max-file-number

最后执行sysctl -p 命令使更改生效.


# 查看所有用户打开文件数的最大限制(此值与硬件配置有关)

cat /proc/sys/fs/file-max


# 限制

所有进程打开的文件描述符数不能超过/proc/sys/fs/file-max

单个进程打开的文件描述符数不能超过user

limit中nofile的soft limit nofile的hard limit不能超过/proc/sys/fs/nr_open


#读写缓存限制

在/etc/sysctl.conf中,可以调整优化

fs.file-max = 999999

net.ipv4.tcp_rmem = 4096 4096 16777216

net.ipv4.tcp_wmem = 4096 4096 16777216

他们都需要三个整数参数配置:min、default、max,来对应到每个socket缓存的字节数。可以设置一个宽松的最大值来减少每个socket链接所带来的内存开销。


我们同样修改文件/etc/security/limits.conf允许所有用户打开999,999个文件描述。


3.内核参数

/proc/sys/fs/file-max , 系统级文件描述符数限制(临时).

一般修改/proc/sys/fs/file-max后,应用程序需要把/proc/sys/fs/inode-max设置为新/proc/sys/fs/file-max值的3-4倍,否则导致i节点不够用.

/proc/sys/fs/epoll/max_user_watches , 一个用户能够往epoll内核事件表中注册的事件的总量.它是指该用户打开的所有epoll实例总共能监听的事件数目,而不是单个epoll实例能监听的事件数目.往epoll内核事件表中注册一个事件,在32位系统上大概消耗90字节的内核空间,在64位系统上则消耗160字节的内核空间.所以,这个内核参数限制了epoll使用的内核内存总量.

/proc/sys/net/core/somaxconn , 制定listen监听队列里,能够建立完整连接从而进入ESTABLISHED状态的最大数目.

/proc/sys/net/ipv4/tcp_max_syn_backlog , 指定listen监听队列里,能够转至ESTABLISHED或者SYS_RCVD状态的最大数目.

/proc/sys/net/ipv4/tcp_wmem , 它包含3个值,分别指定一个socket的TCP写缓冲区的最小值,默认值和最大值.

/proc/sys/net/ipv4/tcp_syncookies , 指定是否打开TCP同步标签(syncookie).同步标签通过启动cookie来防止一个监听socket因不停地重复接收来自同一个地址的连接请求(同步报文段),而导致listen监听队列溢出(所谓的SYN风暴).

修改之后都需要执行sysctl -p 命令使更改生效.



4.内核参数sysctl.conf的优化

/etc/sysctl.conf 是用来控制linux网络的配置文件,对于依赖网络的程序(如web服务器和cache服务器)非常重要,RHEL默认提供的最好调整。

推荐配置(把原/etc/sysctl.conf内容清掉,把下面内容复制进去):

net.ipv4.ip_local_port_range = 1024 65536
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_no_metrics_save=1
net.core.somaxconn = 262144
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2


5.参数说明

net.core.netdev_max_backlog = 400000

#该参数决定了,网络设备接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。


net.core.optmem_max = 10000000

#该参数指定了每个套接字所允许的最大缓冲区的大小


net.core.rmem_default = 10000000

#指定了接收套接字缓冲区大小的缺省值(以字节为单位)。


net.core.rmem_max = 10000000

#指定了接收套接字缓冲区大小的最大值(以字节为单位)。


net.core.somaxconn = 100000

#Linux kernel参数,表示socket监听的backlog(监听队列)上限


net.core.wmem_default = 11059200

#定义默认的发送窗口大小;对于更大的 BDP 来说,这个大小也应该更大。


net.core.wmem_max = 11059200

#定义发送窗口的最大大小;对于更大的 BDP 来说,这个大小也应该更大。


net.ipv4.conf.all.rp_filter = 1

net.ipv4.conf.default.rp_filter = 1

#严谨模式 1 (推荐)

#松散模式 0


net.ipv4.tcp_congestion_control = bic

#默认推荐设置是 htcp


net.ipv4.tcp_window_scaling = 0

#关闭tcp_window_scaling

#启用 RFC 1323 定义的 window scaling;要支持超过 64KB 的窗口,必须启用该值。


net.ipv4.tcp_ecn = 0

#把TCP的直接拥塞通告(tcp_ecn)关掉


net.ipv4.tcp_sack = 1

#关闭tcp_sack

#启用有选择的应答(Selective Acknowledgment),

#这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段);

#(对于广域网通信来说)这个选项应该启用,但是这会增加对 CPU 的占用。


net.ipv4.tcp_max_tw_buckets = 10000

#表示系统同时保持TIME_WAIT套接字的最大数量


net.ipv4.tcp_max_syn_backlog = 8192

#表示SYN队列长度,默认1024,改成8192,可以容纳更多等待连接的网络连接数。


net.ipv4.tcp_syncookies = 1

#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;


net.ipv4.tcp_timestamps = 1

#开启TCP时间戳

#以一种比重发超时更精确的方法(请参阅 RFC 1323)来启用对 RTT 的计算;为了实现更好的性能应该启用这个选项。


net.ipv4.tcp_tw_reuse = 1

#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;


net.ipv4.tcp_tw_recycle = 1

#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。


net.ipv4.tcp_fin_timeout = 10

#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。


net.ipv4.tcp_keepalive_time = 1800

#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为30分钟。


net.ipv4.tcp_keepalive_probes = 3

#如果对方不予应答,探测包的发送次数


net.ipv4.tcp_keepalive_intvl = 15

#keepalive探测包的发送间隔


net.ipv4.tcp_mem

#确定 TCP 栈应该如何反映内存使用;每个值的单位都是内存页(通常是 4KB)。

#第一个值是内存使用的下限。

#第二个值是内存压力模式开始对缓冲区使用应用压力的上限。

#第三个值是内存上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。对于较大的 BDP 可以增大这些值(但是要记住,其单位是内存页,而不是字节)。


net.ipv4.tcp_rmem

#与 tcp_wmem 类似,不过它表示的是为自动调优所使用的接收缓冲区的值。


net.ipv4.tcp_wmem = 30000000 30000000 30000000

#为自动调优定义每个 socket 使用的内存。

#第一个值是为 socket 的发送缓冲区分配的最少字节数。

#第二个值是默认值(该值会被 wmem_default 覆盖),缓冲区在系统负载不重的情况下可以增长到这个值。

#第三个值是发送缓冲区空间的最大字节数(该值会被 wmem_max 覆盖)。


net.ipv4.ip_local_port_range = 1024 65000

#表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。


net.ipv4.netfilter.ip_conntrack_max=204800

#设置系统对最大跟踪的TCP连接数的限制


net.ipv4.tcp_slow_start_after_idle = 0

#关闭tcp的连接传输的慢启动,即先休止一段时间,再初始化拥塞窗口。


net.ipv4.route.gc_timeout = 100

#路由缓存刷新频率,当一个路由失败后多长时间跳到另一个路由,默认是300。


net.ipv4.tcp_syn_retries = 1

#在内核放弃建立连接之前发送SYN包的数量。


net.ipv4.icmp_echo_ignore_broadcasts = 1

# 避免放大攻击


net.ipv4.icmp_ignore_bogus_error_responses = 1

# 开启恶意icmp错误消息保护


net.inet.udp.checksum=1

#防止不正确的udp包的攻击


net.ipv4.conf.default.accept_source_route = 0

#是否接受含有源路由信息的ip包。参数值为布尔值,1表示接受,0表示不接受。

#在充当网关的linux主机上缺省值为1,在一般的linux主机上缺省值为0。

#从安全性角度出发,建议你关闭该功能。