在 浅谈 TCP 介绍了 TCP 基础的理论知识,这篇主要介绍 TCP 的一些应用场景,主要包括(SYN Flood,TCP 长连接和短连接,TCP/IP 实现)。
1. SYN Flood
SYN Flood 又称 SYN 洪水,是利用 TCP 三次握手的漏洞产生的一种攻击方式。
常用攻击方式有两种,最终结果都是造成服务端在等待 ACK。一种是客户端只管发出 SYN,不理会服务端返回的 ACK。一种是客户端构造错误的来源 IP,使服务端返回 ACK 到错误 IP。这两种情况都会导致服务端无法正常收到客户端发来的 ACK。使得 TCP 连接保持在半打开状态,当 SYN 请求量过大时,会出现大量半打开状态的 TCP 连接,当系统资源被消耗尽后,导致新的连接无法被创建,造成正常用户无法访问。
2. TCP 长连接/TCP 短连接
首先需要知道的是 TCP 连接建立需要三次握手,断开连接需要四次,所以每次 TCP 的建立和关闭是需要消耗资源的。
TCP 短连接就是请求完成后关闭连接(一般是客户端主动关闭)。
TCP 长连接是请求完成后仍保持 TCP 连接的状态,可继续传输数据。TCP 保活功能采用定时器来检测连接状态。客户端可能正常运行,已经崩溃,重启或不可达,服务端需要针对检测的情况做出不同反应。
2.1 优缺点:
短连接管理简单,存在的连接都是最近建立和使用的。但每次建立和关闭新连接会有资源消耗,当在大并发请求场景中使用,会出现大量的 TIME_WAIT 状态的连接。导致资源的较大消耗。
长连接需要 TCP 保活定时器来检测连接状态。同时需要考虑不同长连接的负载均衡问题,但是可以在客户端通过连接池得到部分解决,但在不同客户端之间的负载均衡就无法做到。另外因为长连接存在只创建不关闭的情况,所以会导致建立的长连接越来越多。
2.2 短连接在请求量较大时,导致过多的 TIME_WAIT
参考 浅谈 TCP 1.3 中的图,因为 TCP 是双工模式,所以每个方向的连接都需要单独关闭。
主动关闭的一方发送 FIN 后,收到被动关闭一方的 FIN,随后返回 ACK 信号,被动关闭一方完成关闭连接闭环,到达 CLOSED 状态。
而主动关闭一方收到 FIN 后,发出 ACK,挥手已经结束,所以主动关闭一方不清楚被动关闭一方收到 ACK 没有,所以只能等待超出 2MSL(Maximum Segment Lifetime 报文段最大生存时间)后,如没有再次收到 FIN (重连)即可进行关闭。
2MSL 是因为被动关闭一方首先等待 MSL 如果没收到 ACK 则重新发送 FIN,然后主动关闭一方等待 MSL 看是否能收到被动关闭一方重新发送的 FIN,如果没有收到则表示被动关闭一方已成功收到了 ACK。
3. RPC 接口过度担心性能
例如提供:get_topic_token_by_id 还是 get_topic_by_id?
当数据传输长度小于 MSS,数据仍被封装在一个数据包中进行传输,所以不会导致性能的下降。
4. Socket 剖析
4.1 基础 API
socket():创建一个 socket 描述符(socket descriptor)。
bind():将一个地址族中的特定地址与 socket 进行绑定。
listen():服务器通过 listen 来监听请求。
connect():客户端通过 connect 发出连接请求。
accept():内核生成一个全新的描述符,代表与客户唯一的 TCP 连接。
read()/write():从文件描述符中读取数据或写入数据到文件描述符。
close():关闭 socket 描述符(TODO:accept() 和 socket() 创建的描述符都需要关闭吗?)
4.2 blocking 和 non-blocking 区别?
在 Network IO 操作中会涉及到两种系统对象,一个是调用这个 IO 用户进程(user process),一个是系统内核(kernel)。
4.2.1 写阻塞:
当调用 write 操作时,只是将要写的数据复制到 kernel 的发送缓冲区,什么时候发送到网络,什么时候对方接受,系统不进行通知和保证。所以当 kernel 的 send buffer 满了之后就会造成 write 阻塞,即写入的速度大于对方读取的速度。
4.2.2 读阻塞:
当调用 read 操作时,会首先检查 kernel 的 receive buffer 中是否有数据,如果有数据则将数据拷贝到用户进程中,如果没有数据,blocking 和 non-blocking 进行会做出不同的反应。
4.2.3 blocking vs non-blocking
blocking IO 发现 kernel 中无数据,会进行等待直到有数据出现,然后再将数据从 kernel 拷贝到用户进程。
non-blocking IO 发现 kernel 中无数据会直接返回 error。从用户角度,进程不再需要等待,每次 read 操作都会得到一个结果。当进程发现 read 返回 error 后可以再次进行 read 操作。
关于 blocking IO/non-blocking IO 的详细细节可以参考:IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
疑问:(TODO)
长连接导致报错的原因?(TCP keepalive)
当 TCP 数据包被 socket 隔断后如何重新组装?(eg: return big_data)
当 Socket 读写发生错误时,如何进行数据重传?
参考资料:
SYN flood - 维基百科
TCP的 TIME_WAIT 快速回收与重用
Socket 通信原理和实现
IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
TCP/IP 应用程序的通信连接模式