陈同学
微服务
Accelerator
About
# 抓TCP报文诊断 HTTP Content-Length 问题 本文分享一个 HTTP Content-Length 有误时场的景,以 tcpdump 抓包来做真实演示,同时结合TCP状态进行分析。 关于 Content-Length 的场景,比如提供文件下载的服务,需要设置好 Content-Length 以及断点下载的一些参数。 ## 背景 在做身份证OCR识别时,请求完全卡住。通过Arthas分析,发现从CMS下载文件时卡住,导致线程一直在Running. ![](https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2020/01/29/1.png) ![](https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2020/01/29/2.png) ## 小例子 下面是 Spring Boot 应用中一段代码,设置 Content-Length 为100字节,实际却不返回任何数据。 ```java @GetMapping("demo") public void demo(HttpServletResponse response) { response.setContentLength(100); } ``` 用 curl 测试: ```bash curl -X GET http://localhost:8080/demo ``` 控制台卡住1分钟,然后输出: ``` curl: (18) transfer closed with 100 bytes remaining to read ``` 如果用HTTP客户端(eg: HttpClient)调用,线程会一直处于 **RUNABLE** 状态,下面是 **jstack** 拿到的线程状态,**socketRead0** 是一个 **native** 方法,会使用socket的原生方法读取数据。 ```java java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:170) at java.net.SocketInputStream.read(SocketInputStream.java:141) at org.apache.http.impl.conn.LoggingInputStream.read(LoggingInputStream.java:84) ``` 线程会一直阻塞在这里,如果应用中有大量这样的线程,可能会耗尽应用线程,导致应用无响应。 下面看看TCP报文传输情况。 ## TCP 连接状态 为便于理解,先简述TCP的三次握手、四次挥手,熟悉的可直接跳过。 下面两图, **INITIATOR** 可看作client,**RECEIVER** 看做server。 **三次握手**: <img src="https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2019/08/03/1.png" width="60%"/> * client:你好,我是client。报文含SYN标志位,SYN即同步、请求连接的意思,状态变为 **SYN_SENT** * server:收到,我是server。响应含 SYN+ACK 两个标记的报文,ACK是对 client SYN 的确认,SYN表示请求连接,状态变为 **SYN_RECEIVED** * client:收到。对server的SYN做ACK,然后CS两端状态变为**ESTABLISHED**。 此时,连接便已创建,可以进行通信。 **四次挥手:** <img src="https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2019/08/03/2.png" width="60%"/> * client:再见,我不说话了(不能再发数据)。发送FIN标记的报文(FIN表示finish即结束),状态变为 **FIN_WAIT_1** 即等着 server 说再见 * server:收到。向client发送ACK,server变为 **CLOSE_WAIT** 即等待关闭连接(不急,等自己活干完再关);client 收到ACK后,状态变为 FIN_WAIT_2,等着server结束。 * server:再见,我活干完了。发送FIN标记报文,状态变为LAST_ACK,此时server也不能再发送数据。 * client:收到。状态变为TIME_WAIT,即过一段时间就自动关闭,然后对server的FIN做ACK。server收到后就CLOSED,client 过一会也自行CLOSED。 ## TCP 报文监控 上面介绍了TCP连接状态,现在用 tcpdump 监控网卡8080端口(应用在8080端口)的数据。 ```bash sudo tcpdump -n -i any port 8080 ``` 应用跑在本机,下面是tcpdump的动态输出(为了便于展示,仅摘取了关键字段) > 65241 是client分配的临时端口,8080是应用端口。Flags 表示标记位,S、P、F分别表示SYN、PSH、FIN,代表请求连接、推送数据、结束连接标记位。 **建立TCP连接的三次握手报文, 对应 SYN、SYN+ACK、ACK 三个步骤** ```java ::1.65241 > ::1.8080: Flags [S], seq 2672664932, length 0 ::1.8080 > ::1.65241: Flags [S.], seq 1257336122, ack 2672664933, length 0 ::1.65241 > ::1.8080: Flags [.], ack 1, length 0 ``` **client发送请求数据的报文** 第二行 client 以 **HTTP/1.0 GET** 请求 **/demo**,server 做了ACK表示收到 ```java ::1.8080 > ::1.65241: Flags [.], ack 1, win 6371, length 0 ::1.65241 > ::1.8080: Flags [P.], seq 1:83, ack 1, length 82: HTTP: GET /demo HTTP/1.1 ::1.8080 > ::1.65241: Flags [.], ack 83, win 6370, length 0 ``` client 的报文如下: ```http GET /demo HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.54.0 Accept: */* ``` **server推送数据的报文** server推送带P标记位的报文,报文长度116,client马上做了ACK ```java ::1.8080 > ::1.65241: Flags [P.], seq 1:117, ack 83, length 116: HTTP: HTTP/1.1 200 ::1.65241 > ::1.8080: Flags [.], ack 117, win 6370, length 0 ``` server的报文如下,其中 **Content-Length** 为100。 ```http HTTP/1.1 200 X-Application-Context: application:8080 Content-Length: 100 Date: Sat, 03 Aug 2019 12:52:40 GMT ``` **漫长等待阶段** 由于server告知client HTTP请求体中有100字节要推,实际上server又没有推任何数据。此时, * 脑补 server:client 咋没任何反应,数据都给你了,读完后你倒是断开连接呀。 * 脑补 client:搞啥呢,有100个字节咋还不推过来,我再等等把。 两方就干耗着,一起站着茅坑(占用了TCP连接、端口等资源),文章最上面Java线程的**RUNNABLE**状态就对应在这里。 **结束** 经过1分钟,server 主动发起了FIN报文,终止了连接,下面对应着四次挥手: ```bash ::1.8080 > ::1.65241: Flags [F.], seq 117, ack 83, length 0 ::1.65241 > ::1.8080: Flags [.], ack 118, length 0 ::1.65241 > ::1.8080: Flags [F.], seq 83, ack 118, length 0 ::1.8080 > ::1.65241: Flags [.], ack 84, length 0 ``` 当然,如果client设置 socketTimeout,假设为2秒,那2秒之后,就变成client主动发起FIN报文来中断连接了。 ## 小结 实际工作中,有些问题需要去排查TCP连接的状态甚至TCP报文的情况,本文以一个简单的例子做了分享。 关于RCP的状态流转,可参考 [RFC793](https://tools.ietf.org/html/rfc793)。 ## 参考 * [TCP connection states](https://blog.confirm.ch/tcp-connection-states/) 文中图片来源于此博文
本文由
cyj
创作,可自由转载、引用,但需署名作者且注明文章出处。
文章标题:
抓TCP报文诊断 HTTP Content-Length 问题
文章链接:
https://chenyongjun.vip/articles/119
扫码或搜索 cyjrun 关注微信公众号, 结伴学习, 一起努力