Linux TCP收包内容总结-----学习笔记

Linux TCP收包内容总结-----学习笔记 ,如果有理解不正确的,欢迎各位拍砖, :)

总揽:数据包在从进入网卡一直到用户程序能读取所经历的几个层级以及各层使用到的工具命令和参数,如下表格:

接收的网络数据依次经1:网卡硬件-->2 内核缓存队列-->3协议层--->4用户recv使用的buff. 也就是说网卡收到数据包后要几经辗转才能被用户程序读取,而在”辗转”的过程中也是会有将数据包丢弃操作的.下面细说.

一:在网卡硬件层丢包

查看网卡硬件层是否有数据包丢弃:

1,ifconfig如果看到overruns 数值不为0,

2或ethtool –S ethX|egrep “discard|error|drop”, (将ethX换成你实际网卡)看到的统计计数不为0, 如果这值一直在增长,则可能在网卡硬件或驱动层面丢包了,原因可能为:

  • Ring buffer (网卡硬件上的缓存) 过小, 队列满了后,内核还没有来得仍取走数据, 新到网卡的数据包被丢弃.此时需要修改ring buffer.后面会详细提到.
  • 网卡硬件有问题,需要联系硬件厂商(更换或升级硬件固件)
  • 网卡驱动有问题,更新驱动
  • 操作系统超级繁忙. 需要分析系统性能.

针对第一种情况,可以修改ring buffer,首先需要网卡硬件以及驱动的支持, 有些较老的网卡及驱动是不支持修改此值的.其次,修改此值的时候可能会造成丢包甚至网络中断,所以最好是在非业务运行时段进行.最后,修改完此值后,下次重启主机不会生效,需要将期加入开机启动,或者加入网卡配置文件启动参数中去.

查看ring buffer的大小:

[root@test ~]# ethtool -g ens32
Ring parameters for ens32:
Pre-set maximums:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 4096
Current hardware settings:
RX: 256
RX Mini: 0
RX Jumbo: 0
TX: 256

当前的接收ring buffer为256, 可以将其调整成比较大的值:命令ethtool -G ens32 rx 4096,修改后:

[root@test ~]# ethtool -g ens32
Ring parameters for ens32:
Pre-set maximums:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 4096
Current hardware settings:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 256

将ring buffer的修改加入开机启动:

 编辑文件/etc/udev/rules.d/50-ethtool.rules 添加下面内容:
 ACTION=="add", SUBSYSTEM=="net", NAME=="ens32", RUN+="/sbin/ethtool -G ens32 rx 4096"
 

二:在内核缓存层面丢包

查看方法:

[root@test ~]# cat /proc/net/softnet_stat 
00b5cb58 00000000 00000038 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00001ba1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000002 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000052 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
  • 每行代表一个cpu core接收数据的情况.其中第一列收到的包总数,
  • 第二列是丢弃的包计数, 此处的丢弃是指包已进行网卡的ring buffer,但在从网卡ring buffer 运输到内核缓存队列时,由于内核缓存队列满了,从而数据包被丢弃.如果第2列数值在增长,那么我们需要修改内核参数,将内核网络缓存队列的长度调大一点.

修改参数net.core.netdev_max_backlog :

可以先修改成原来的2倍大小:原来若为300,此处我们修改为600

echo "net.core.netdev_max_backlog = 600" >> /etc/sysctl.conf

执行sysctl –p 使其生效.

l 如果/proc/net/softnet_stat的第三列在增长,说明是在 从ring buffer 运输到内核网络缓存队列时, 由于”运输小车”没能装下,而导致丢包. 这个运输过程是由中断处理程序的下半部分完成的(即软中断), 但软中断会被调度且有一定的执行时间. 此时我们需要修改 “运输小车”的大小, 内核参数为net.core.netdev_budget , 可先将其修改为原来的2倍大小. 当然,此值不能改的太大,如果太大,可能会造成软中断占用太长cpu时间,而引起其他性能问题.

l 最后来看看这两个参数之间的关系 :

The SoftIRQ runs to pull net.core.netdev_budget packets off the NIC, and stores them in net.core.netdev_max_backlog

三:协议层

协议层的数据放在socket buffer 里, 每个tcp链接都有自己的socket buffer, 查看协议层是否有数据包被"pruned" 或者 "collapsed" ,通过netstat命令查看:

 #netstat –st|grep socket 

在输出中如果有看到"pruned" 或者 "collapsed",说明需要调整协议层的相关参数,至少应该增加tcp socket buffer值,即内核参数的值net.ipv4.tcp_rmem, 该参数有三个值:

  1. 第一个为可申请的tcp socket buffer的最小值(如果应用程序调用setsockopt()调整自己程序的socket buffer时不能小于此值?)
  2. 中间一个为默认值(会覆盖掉net.core.wmem_default的值),
  3. 最后一个为可申请的最大值(会被net.core.rmem_max限制,如果你设置该参数值超过了net.core.rmem_max,则最大为net.core.rmem_max的值)

如果不打算在自己应用程序中调用setsockopt()设置你自己链接的tcp socket buffer的话,那么在这里需要我们设置默认值,默认值对系统所有tcp 链接生效. 如果此值设置太小,会造成, recv()数据时被拆分且循环从recv socket buffer 读入. 而如果设置的太大,而实际接收到的数据量很小, 由于每次读取会读取整个socket buffer空间,这样会造成性能的浪费. 与此同理发送缓存net.ipv4.tcp_wmem也不能设置的太大或太小.

如果这些值重新设置后, 需要重启应用程序, 应用程序的socket buffer才会改变.

理论上说:

  1. 如果主机相同类型网络通信程序,可以直接修改操作系统的tcp socket buffer内核参数.
  2. 如果主机除了自己的业务,还有其他不同种类型的网络通信程序, 建议在程序中调用 setsockopt(SO_RCVBUF)定义自己程序的socket buffer.

四:从协议层到用户层

建议先看看send()以及Recv()函数的阻塞(异步)与非阻塞(同步)模式. 常用的为阻塞模式,即send()将数据写入到 协议层的发送缓冲后便返回. 非阻塞模式下,send()会等待对端 协议层收到数据并回复ack后才返回(对端应用层不一定已经读到数据)

阻塞模式的recv()函数(下面内容转自百度出来的文字.读到recv 居然要等待协议层将发送缓冲的数据发送完才能进行下一步操作,这个我有点被惊到了):

1) recv先等待sockfd的发送缓冲区的数据被协议传送完毕,如果协议在传送sock的发送缓冲区中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR。

2) 如果套接字sockfd的发送缓冲区中没有数据或者数据被协议成功发送完毕后,recv先检查套接字sockfd的接收缓冲区,如果sockfd的接收缓冲区中没有数据或者协议正在接收数据,那么recv就一起等待,直到把数据接收完毕。当协议把数据接收完毕,recv函数就把sockfd的接收缓冲区中的数据copy到buff中(注意:协议接收到的数据可能大于buff的长度,所以在这种情况下要调用几次recv函数才能把sockfd的接收缓冲区中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)。

3) recv函数返回其实际copy的字节数,如果recv在copy时出错,那么它返回SOCKET_ERROR。如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

4) 在unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用 recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

确认socket链接是否有在接收或者发送数据:

#ss –nmp |grep ip:port  把ip:port换成你应用使用的对端ip及端口

第一列为链接的状态, 如果是ESTAB,查看第2列RecV-Q和第3列Send-Q, 如果不为0,说明有数据正在被接收或发送, 下面mem(r,w,f,t)表明了该socket使用内存的情况.也能反映数据是否被应用读取,或是否需要被协议发送. 如果Recv-Q一直有数据,则检查:

1) 是否网络通信质量差或是否对端的socket 接收太慢而导致本端sockfd发送缓冲一直有数据

2) 是否本端recv()函数的第2个参数指定的空间太小,造成一直从sockfd的接收缓冲读取数据

编辑于 2018-12-14