实战!一次网络问题排查( 四 )


客户端收到服务器的确认请求后,此时,客户端就进入FIN_WAIT2(终止等待2)状态,等待服务器发送连接释放报文(在服务端Close_Wiat期间还可以接受服务器发送的最后的数据) 。
服务端发送完最后的数据,向客户端发送FIN 连接释放报文,ACK =1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,ack 和回复ACK报文一致,ack = u+1, 此时,服务器就进入了LAST_ACK(最后确认)状态,等待客户端的确认 。
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME_WAIT(时间等待)状态 。注意此时TCP连接还没有释放,必须经过2 个MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态 。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态 。同样,撤销TCB后,就结束了这次的TCP连接 。
4.异常定位:Wireshark 分析定位Broken Pipe 异常原因4.1 网络包分析上面我们已经看了正常的四次挥手的流程和截图,下面我们来看下线上遇到的 Broken Pipe异常,再次看一眼异常栈,如下图:

实战!一次网络问题排查

文章插图
 
【实战!一次网络问题排查】网络异常栈
我们可以看Wireshark 的包信息如下图,
实战!一次网络问题排查

文章插图
 
可以看到,我们来看一下流程,第一步是区分服务端和客户端:
  • 服务端:IP后缀为132,端口20004是,
  • 客户端:IP后缀为156,端口为4528 。
前三行是三次握手,没有问题,但是最后的四次挥手这里有问题(可以对照着四次挥手的图看):
  • No.189 行:服务端发起FIN 报文希望关闭连接,服务端进入FIN_WAIT1 状态;
  • No.190 行:客户端响应服务端的FIN 报文发送ACK 报文,进入CLOSE_WAIT 状态;
  • No.191 ~ No.197:服务端接收到客户端的ACK 进入FIN_WAIT2 状态,此时服务端是不接收数据传输的,但是我们可以看到Wireshark 191 ~ 196 行客户端还在发送数据报文,正常应该是客户端发送FIN 报文关闭连接,让服务端进入TIME_WAIT 状态,但是客户端没有发送FIN报文,而是向已经准备关闭的连接通道中发送了数据报文,因为服务端不认你客户端的数据,所以发送了RST 信号报文来重置连接 。
下面是正常的四次挥手和异常的四次挥手对照图:
实战!一次网络问题排查

文章插图
 
对比
4.2 问题定位
  1. 供应商接口查看状态让HTTP 请求的被调用方(供应商)查看当前网络状态,linux 命令如下所示:1netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"t",state[key]}'结果如下:image-20200522214047234可以对照着四次挥手流程图看状态:ESTABLISHED: 处于连接建立状态的连接数FIN_WAIT1: 处于连接关闭FIN_WAIT1 状态的连接数FIN_WAIT2: 处于连接关闭FIN_WAIT2 状态的连接数TIME_WAIT:处于 TIME_WAIT 状态的连接数可以看到TIME_WAIT 状态很多,这个是正常的,只要记住,正常四次挥手流程中,主动关闭的一方会经过TIME_WAIT 状态,被动关闭一方会经过 CLOSE_WAIT 状态,这二个状态(TIME_WAIT & CLOSE_WAIT)需要做个区分,如果 CLOSE_WAIT 状态过多可能会有问题,这个我会在扩展阅读详细说,继续分析异常 。
  2. 确认连接方式通过上面Wireshark 异常包可以知道是服务端进入FIN_WAIT2 状态后,客户端继续发送数据包,导致服务端RST 连接 。这里有二个问题:
  3. 为什么服务端主动发起FIN 关闭连接呢?
  4. 为什么客户端在接收到服务端的FIN 并回复ACK 报文之后,为什么没有发送FIN 关闭连接报文呢?不卖关子了,讲了这么多吊足了玩家们的胃口,真实原因都不好意思讲了,其实是因为供应商不支持长连接,但是我们为了资源复用,降低HTTP 连接创造销毁的开销,使用了连接池,连接池的连接是复用的,是长连接,所以才会出现服务端第二次发送数据时不进行三次握手,而是直接传输数据报文的情况 。
5. 代码修复:调整客户端代码5.1 HttpClient 长连接改为短连接
  1. 服务端那边配置的Nginx 是短连接方式,客户端代码使用长连接,客户端HTTP 请求的代码(删除了部分代码),请求结束之后,连接不进行关闭,连接池复用连接 。如下:长连接主要代码如果要支持长连接,服务端Nginx 配置需要做些修改,如下: 1http{


    推荐阅读