[web-029] tcp传输细节和故障分析

1.参考资料
《TCP/IP详解》《UNIX网络编程》《http权威指南》
https://zhuanlan.zhihu.com/p/39048792
https://www.cnblogs.com/huyingsakai/p/9268468.html
https://www.cnblogs.com/leezhxing/p/5329786.html

https://www.cnblogs.com/my_life/articles/5174585.html

https://network.51cto.com/art/201907/600271.htm

2. http连接是基于tcp连接的。建立一个http连接,首先建立tcp连接,然后tcp连接的两端进行http通信,当通信结束后,http连接先结束,然后tcp连接再结束。

3. 建立tcp连接需要三次握手。结束tcp连接需要四次挥手。


4.三次握手


A:客户端,比如浏览器。
B:服务端,比如web服务器。

4.1 初始状态:两端的TCP进程都处于CLOSED关闭状态。
4.2 B的TCP服务器进程创建传输控制块TCB,服务器进程处于LISTEN(收听)状态,等待客户的连接请求。TCB传输控制块Transmission Control Block,存储每一个连接中的重要信息,如TCP连接表,到发送和接收缓存的指针,到重传队列的指针,当前的发送和接收序号。
4.3 第一次握手:A的TCP客户进程创建传输控制块TCB,然后向B发出连接请求报文段,报文首部的同步位SYN=1,初始序号seq=x,然后TCP客户进程进入SYN-SENT/同步已发送状态。其中,x是随机整数。
4.4 第二次握手:B收到连接请求报文段后,如同意建立连接,则向A发送确认,确认报文段SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y,TCP服务器进程进入SYN-RCVD/同步收到状态。其中y是随机整数。
4.5 第三次握手:A的TCP进程收到B的确认后,检查ack是否为x+1和ACK是否为1,检查通过后,向B给出确认报文,确认报文段ACK=1,确认号ack=y+1,序号seq=x+1,A进入ESTABLISHED。当B收到A的确认后,也进入ESTABLISHED状态。至此,TCP连接成功建立。
4.6 解释:随机产生的x和y主要用于分别确认A和B的发送数据初始序号,x是A的序号,y是B的序号,建立连接后,A和B相互发送数据以x和y作为第一个序号依次递增,只要发送数据,救需要分别讲x和y做+1递增,由此,保证数据传输的有序性。

4.7 tcp连接攻击。服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击,SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。防范SYN攻击措施:降低主机的等待时间使主机尽快的释放半连接的占用,短时间受到某IP的重复SYN则丢弃后续请求。

5.四次挥手

假设A发起关闭连接请求。

扫描二维码关注公众号,回复: 8640506 查看本文章

5.1 A向B发出连接释放报文,报文段FIN=1,序号seq=u,进入FIN-WAIT-1/终止等待1状态,等待B的确认。
5.2 B收到连接释放报文段后,向A发出确认报文,报文段(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT/关闭等待状态。
5.3 A收到B的确认后,进入FIN-WAIT-2/终止等待2状态,等待B发出的连接释放报文段。
5.4 如果B还有没发完的数据,继续向A发送数据,直到发完为止。
5.5 B发完所有数据后,B发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),B进入LAST-ACK/最后确认状态,等待A的确认。
5.6 A收到B的连接释放报文段后,发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT/时间等待状态。B收到确认报文后进入CLOSE关闭状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSED状态。
5.7 解释:为什么A在TIME-WAIT状态必须等待2MSL的时间。MSL最长报文段寿命Maximum Segment Lifetime,通常MSL=2分钟。两个理由:保证A发送的最后一个ACK报文段能够到达B;防止“已失效的连接请求报文段”出现在本连接中,这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,B超时重传FIN+ACK报文段,而A能在2MSL时间内收到这个重传的FIN+ACK报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则B无法正常进入到CLOSED状态;在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。

6.故障1
6.1 故障现象:间歇性得出现client向server建立连接三次握手已经完成,但server的selector没有响应到这连接。出问题的时间点,会同时有很多连接出现这个问题。selector没有销毁重建,一直用的都是一个。程序刚启动的时候必会出现一些,之后会间歇性出现。
6.2 故障分析:建立三次握手成功,表明连接没问题。如果运行一定时间,出现大多数连接出错,判断是有点像TCP建连接的时候全连接队列满了。三次握手,在第二次握手,当server收到syn后,把连接信息放入到半连接队列,在第三次握手,server会把连接信息从半连接队列移动到全连接队列。半连接队列也叫syns队列,全连接队列也叫accept队列。对操作系统而言,遇到全连接队列满了,要么将完成4.5的握手ack扔掉,要么返回reset by peer。如果/proc/sys/net/ipv4/tcp_abort_on_overflow的值是0,把ack扔掉,然后过段时间再从半连接队列里取出来连接信息,重走第二次握手,如果client的超时时间比较短,等不到第二次握手就报超时异常了。如果值是1,返回reset by peer。
6.3 验证:执行命令 netstat -s|egrep "listen",能看到类似40939 times the listen queue of a socket overflowed,overflowed表示全连接队列溢出,40939是溢出次数。如果溢出次数在不停增长,表明溢出现象始终在发生,比较严重。执行命令 ss -lnt,能看到
State           Recv-Q           Send-Q                      Local Address:Port                      Peer Address:Port           
LISTEN          9                128                               0.0.0.0:8880                           0.0.0.0:*        

Recv-Q和Send-Q,这两个值在LISTEN状态和非LISTEN状态是不一样的。LISTEN 状态: Recv-Q 表示的当前等待服务端调用 accept 完成三次握手的 listen backlog 数值,也就是说,当客户端通过 connect() 去连接正在 listen() 的服务端时,这些连接会一直处于这个 queue 里面直到被服务端 accept();Send-Q 表示的则是最大的 listen backlog 数值,这就就是上面提到的 min(backlog, somaxconn) 的值。其余状态: Recv-Q 表示 receive queue 中的 bytes 数量;Send-Q 表示 send queue 中的 bytes 数值。

因此,此时Send-Q表示全连接队列最大为128,Recv-Q表示全队列当前使用多少,这个数字是不断变化的,如果Recv-Q数字持续大于Send-Q,表示全连接队列溢出。全连接队列大小取决于min(backlog, somaxconn),backlog是创建的时候传入的,somaxconn是os系统参数。因此,传入一个较大的backlog可以让backlog本身不成为队列长度的瓶颈。半连接队列大小取决于max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog),常见是/proc/sys/net/ipv4/tcp_max_syn_backlog是512,不同os版本会有差别。


7.故障2
7.1 故障现象:客户端调用服务端,偶尔会出现reset by peer的错误。
7.2 故障分析:reset by peer是偶尔出现,这表明在大多数情况下四次挥手没有问题,极少数情况下四次挥手没有完成,在客户端进入LAST-ACK状态之前,是有可能一直在发数据的。从故障现象看,TIME_WAIT数量非常多,形如:

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 
FIN_WAIT2 83
TIME_WAIT 6215
ESTABLISHED 781
FIN_WAIT1 30

TIME_WAIT占用的是五元组(协议,本地IP,本地端口,远程IP,远程端口)。压测时候,WEB是TCP协议,本地IP,本地端口,远程IP,都是固定的,因此能改变的就是远程端口,如果五元组满了,会出现Too Many Open Files错误,因为linux能打开的文件数量是有限制的,如果没有报Too Many Open Files错误,说明这块不是故障原因。那么,服务端突然想Cient发送RST,可能是SO_LINGER问题,如果设置结构体中l_onoff为非0,l_linger为0,那么调用close时TCP连接会立刻断开,TCP不会将发送缓冲中未发送的数据发送,而是立即发送一个RST报文给对方,这个时候TCP连接就不会进入TIME_WAIT状态。可能在某些特殊情况下,会出现这种情况。还有一种情况就是,如果另一方试图连接一个处于TIME_CLOSE的五元组的时候,会立刻收到一个reset by peer,一个IP主机,试图用一个端口,连接到服务端,但这个端口刚好在Server的2MSL的五元组里,也会收到reset by peer,也就是说说,一个五元组(协议,本地IP,本地端口,远程IP,远程端口)正在2MSL等待关闭时,又被客户端连过来。这个问题咋个解决?减少2MSL时间。因为这是单方面的,因此没问题。SO_LINGER是个很可怕的东西,很难用好,尽量少用,遇到reset by peer,忽略重试是最好的。

发布了448 篇原创文章 · 获赞 115 · 访问量 53万+

猜你喜欢

转载自blog.csdn.net/u011539200/article/details/103201344