Java程序员进阶三条必经之路:数据库、虚拟机、异步通信。
前言
想玩转异步通信,不懂TCP协议怎么行?tcpdump目前是最好的命令行抓包工具,所以我选择用它来学习TCP协议。
使用方法
tcpdump -i any -X -nn -s0 -S port <port>
tcpdump的参数巨多无比,我只介绍我所掌握的。
- -i any,网络接口,any表示任何接口,也可以监听具体的接口,比如-i eth0。
- -X,列出十六进制 (hex) 以及 ASCII 的数据包內容。
- -nn,不解析域名或端口。比如21端口是FTP端口,我们希望显示21,而非tcpdump自作聪明的将它显示成FTP。
- -s0,不限制捕获的内容大小。
- -S,打印绝对序列号,这个参数极其重要,稍后介绍。
三次握手和四次握手
开启本地服务监听1234端口,调用tcpdump命令,最后启动客户端:
telnet localhost 1234
再退出客户端,
日志输出:
16:17:43.121895 IP 127.0.0.1.54024 > 127.0.0.1.1234: Flags [S], seq 947792371, win 43690, options [mss 65495,sackOK,TS val 5673569 ecr 0,nop,wscale 7], length 0
0x0000: 4510 003c bb92 4000 4006 8117 7f00 0001 E..<..@.@.......
0x0010: 7f00 0001 d308 04d2 387e 29f3 0000 0000 ........8~).....
0x0020: a002 aaaa fe30 0000 0204 ffd7 0402 080a .....0..........
0x0030: 0056 9261 0000 0000 0103 0307 .V.a........
16:17:43.121910 IP 127.0.0.1.1234 > 127.0.0.1.54024: Flags [S.], seq 4032343998, ack 947792372, win 43690, options [mss 65495,sackOK,TS val 5673569 ecr 5673569,nop,wscale 7], length 0
0x0000: 4500 003c 0000 4000 4006 3cba 7f00 0001 E..<..@.@.<.....
0x0010: 7f00 0001 04d2 d308 f058 afbe 387e 29f4 .........X..8~).
0x0020: a012 aaaa fe30 0000 0204 ffd7 0402 080a .....0..........
0x0030: 0056 9261 0056 9261 0103 0307 .V.a.V.a....
16:17:43.121920 IP 127.0.0.1.54024 > 127.0.0.1.1234: Flags [.], ack 4032343999, win 342, options [nop,nop,TS val 5673569 ecr 5673569], length 0
0x0000: 4510 0034 bb93 4000 4006 811e 7f00 0001 E..4..@.@.......
0x0010: 7f00 0001 d308 04d2 387e 29f4 f058 afbf ........8~)..X..
0x0020: 8010 0156 fe28 0000 0101 080a 0056 9261 ...V.(.......V.a
0x0030: 0056 9261 .V.a
16:17:47.187398 IP 127.0.0.1.54024 > 127.0.0.1.1234: Flags [F.], seq 947792372, ack 4032343999, win 342, options [nop,nop,TS val 5674585 ecr 5673569], length 0
0x0000: 4510 0034 bb94 4000 4006 811d 7f00 0001 E..4..@.@.......
0x0010: 7f00 0001 d308 04d2 387e 29f4 f058 afbf ........8~)..X..
0x0020: 8011 0156 fe28 0000 0101 080a 0056 9659 ...V.(.......V.Y
0x0030: 0056 9261 .V.a
16:17:47.187736 IP 127.0.0.1.1234 > 127.0.0.1.54024: Flags [F.], seq 4032343999, ack 947792373, win 342, options [nop,nop,TS val 5674585 ecr 5674585], length 0
0x0000: 4500 0034 3dde 4000 4006 fee3 7f00 0001 E..4=.@.@.......
0x0010: 7f00 0001 04d2 d308 f058 afbf 387e 29f5 .........X..8~).
0x0020: 8011 0156 fe28 0000 0101 080a 0056 9659 ...V.(.......V.Y
0x0030: 0056 9659 .V.Y
16:17:47.187742 IP 127.0.0.1.54024 > 127.0.0.1.1234: Flags [.], ack 4032344000, win 342, options [nop,nop,TS val 5674585 ecr 5674585], length 0
0x0000: 4510 0034 bb95 4000 4006 811c 7f00 0001 E..4..@.@.......
0x0010: 7f00 0001 d308 04d2 387e 29f5 f058 afc0 ........8~)..X..
0x0020: 8010 0156 fe28 0000 0101 080a 0056 9659 ...V.(.......V.Y
0x0030: 0056 9659 .V.Y
对比日志和图片,基本上可以轻松理解三次握手和四次握手的机制,简单补充一些知识。
- 四次握手中的第二次和第三次可以合并成一次。
- 如果tcpdump命令没有-S选项的话,三次握手中的第三次就会变成ack 0。原因是:tcp在收到第一条数据包之后,后续的数据包,是使用之前数据包的偏移来显示的。
- seq是包的序号,用来解决网络包乱序问题。
- ack表示确认收到,用来解决不丢包的问题。
- flags是包的类型,主要是用于操控TCP的状态机的,有如下几种状态:
- S:SYN(同步),开始会话请求。
- A:ACK,应答。
- F:FIN,结束会话。
- R:RST(复位),中断一个连接。
- P:PUSH(推送),数据包立即发送。
- U:URG,紧急。
- E:ECE,显式拥塞提醒回应。
- W:CWR,拥塞窗口减少。
- .:没有状态。
常见瓶颈
TCP网络应用出问题,十有八九是以下两种情况:
- 主动关闭连接方出现大量TIME_WAIT状态。
- 被动关闭连接方出现大量CLOSE_WAIT状态
linux分配给一个用户的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT两种状态如果一直被保持,那么意味着对应数目的通道就一直被占着,一旦达到句柄数上限,新的请求就无法被处理了,接着应用程序可能返回大量Too Many Open Files异常。
下图告诉你这两种状态是如何产生的:
- 主动关闭方在关闭连接后,需要发送ACK,假设ACK丢失了,被动关闭一方会重发它的FIN。主动关闭方必须维持一个有效状态信息(TIMEWAIT状态下维持),以便能够重发ACK。如果主动关闭的socket不维持这种状态而进入CLOSED状态,那么主动关闭的socket在处于CLOSED状态时,接收到FIN后将会响应一个RST。被动关闭一方接收到RST后会认为出错了。这就是为什么socket在关闭后,仍然处于TIME_WAIT状态的第一个原因,因为它要等待以便重发ACK。第二个原因是确保连接复用时没有残存的数据。TCP不允许新连接复用TIME_WAIT状态下的socket。处于TIME_WAIT状态的socket在等待两倍的MSL时间以后,将会转变为CLOSED状态,此时通道内不会存在残存数据。
应用程序无法解决TIME_WAIT问题,我想了一个甩锅的办法是让客户端断开连接,因为谁主动断开谁面临TIMEWAIT。 - CLOSE_WAIT需要重点关注。被动关闭方在发送ACK以后会处于CLOSE_WAIT状态,此时只要调用close方法就会发送FIN包,脱离CLOSE_WAIT状态。
总结
TCP协议过于复杂,属于很重要但是投入产出比并不高的技术领域,netty提供了非常强大、全面的功能以屏蔽协议细节,所以我不打算在协议上耗太多时间。