TCP协议详解

📅 2026/8/18 21:11:05
TCP协议详解
引言本篇文章参考书目为《Linux高性能服务器编程》在这个博客系列里面我会努力梳理书本里面的主要内容。TCP服务的特点TCP协议是面向连接字节流可靠传输在实际运用的过程中通信双方是不是需要执行相同的次数读和写答案是否定的。发送端TCP模块先把数据放入TCP缓冲区内当TCP模块真正发送数据的时候发送缓冲区那些等待的数据可能会被封装成一个或者多个TCP报文发出。大家一定要记住TCP是按照字节流发送的所以一个TCP报文里面的上下文可能并不完整的关系因为是按照字节流发出的。所以TCP模块发送的TCP报文数量和应用程序写端的次数没有特别多的关系。当接收端收到一个或者多个TCP报文之后TCP模块会按照TCP报文的编号依次放入TCP缓冲区并通知应用程序读取数据。接收端可以一次性全部读取也可以分段读取这取决于应用程序的缓冲区大小。因此应用程序执行的读操作次数和TCP模块接收到TCP报文的个数也没有固定的数量关系。所以通信双方不需要执行相同的次数读和写。而UDP就不一样了应用程序每执行一次写操作UDP模块会把数据封装成一个报文直接发出去。接受方必须即使针对每一个UDP数据报进行读取否则就会丢包。并且如果用户没有指定足够的应用程序缓存来读取UDP数据那么UDP数据就会被截断。对于UDP来说没有缓冲区的概念主要原因是UDP不用重新发送数据所以不需要存储。TCP头部序号在第一次发送TCP报文的时候会初始化一个ISN后面的所有数据报的序号都是ISN第n个字节比如1025 - 2038 那么就是ISN1025 。当然互相通信的双方ISN是不一样的所以传递过来的序号也是不一样的。ACK上面也说了序号是不一样的所以每个机器只认识自己的那个序号所以当我们发送ACK的时候应该把对方发来的TCP序号1就是我们要发送的ACK这个样子发过去的ACK对方可以完全识别。首部长度4 * 15标志位URG 紧急指针 ACK 确认号是否有效 PSH 提醒接收端应用程序应该立即从TCP缓冲区中读取走数据。 RST 要求对方重新建立连接 SYN 请求建立一个连接 FIN 结束连接16位窗口TCP控制流量的一个方法告诉对方本端的TCP接收缓冲区可以容纳多少字节的数据这个样子对方就可以控制数据的发送速率TCP连接的建立和关闭我们这里不主要讨论三次握手和四次挥手讨论一些其中的问题。TCP状态转移服务器通过listen的系统调用进入LISTEN状态被动等待用户连接当收到用户连接的请求会把连接放入内核等待队列里面并向客户端发送带SYN同步连接标志的确认报文段。此时此链接处于SYN_RCVD状态如果成功接收到了客户端的确认报文那么就转移到了ESTABLISHED状态。这个状态可以互相通信。当用户断开连接的时候用户会先发送带有FIN标志的报文并且进入FIN_WAIT_1的状态当用户收到服务器的ACK标志的报文的时候就会进入FIN_WAIT_2的状态。这个状态下面服务器还可以和客户端进行发送数据处于半关闭状态。当服务器的数据发送完服务器发送带FIN标志的报文此时客户端接受到这个报文之后状态变成TIME_WAIT。当然当客户端发来断开请求的时候服务器进入CLOSE_WAIT状态这个时候服务器可以发送数据但是客户端不可以这个时候也是我们所说的半关闭状态。当服务器发出FIN标志的报文的时候就进入LAST_ACK当接收到客户端的ACK时候服务器就会进入CLOSED状态。当然了如果不是为了接收数据而处于长时间的半关闭状态也不是一件好事情此时客户端连接由内核来接管称为孤儿进程。所以为了解决这个问题我们内核里面有两个参数一个是能接管的孤儿连接数量一个是孤儿连接在内核里面的时间。TIME_WAIT状态这个状态存在的原因我们上面也已经说了当客户端接受到服务器发出的FIN标志的报文时候就会进入这个状态。这个状态存在两个原因1、可靠的终止TCP连接如果客户端最后发送的ACK报文丢失了那么服务器会重新发送一个FIN报文给客户端来重复这个过程。一定不是客户端再重新发一个而是服务器再发一个2、保证让迟来的TCP报文有足够的时间被识别并丢弃。当一个TCP连接处于TIME_WAIT时无门无法使用该端口建立一个新的连接因为一旦使用那么很可能会受到来自原来连接携带应用数据的TCP报文段这显然是不对的所以在TIME_WAIT状态的时候不会让其他应用程序使用这个端口。另外一个TCP报文最大生存时间是MSL那么坚持2MSL的时间的TIME_WAIT可以确保网络上的两个传输方向的报文都已经消失。所以我们写代码的时候也会遇到一个类似的问题当我们关闭测试的端口然后立马重新运行程序这个时候就会报错因为端口被占用了处于TIME_WAIT。而我们常常会设置socket选项SO_REUSEADDR来强制进程立即使用正在处于TIME_WAIT状态的端口。复位报文段复位报文段主要就是通知对方关闭连接或者重新建立连接。因为复位报文段的接受通告窗口时0所以可以预见收到复位报文段的一端应该关闭连接或者重新连接而不能回应这个复位报文段。TCP交互数据流TCP报文段所携带的应用程序数据按照长度分为交互数据流和成块数据。交互数据仅仅包含很少的字节使用交互数据流对应用程序的实时性要求很高。成块数据的长度通常为TCP报文的最大长度使用成块数据对于传输效率的要求很高。对于交互数据流一般来说客户端发送的确认报文段不携带任何的数据而服务器每次发送确认报文段都会携带应用程序数据服务器这种处理方式是延时确认。它不马上确认上一次收到的数据而是隔一段时间看本端有没有数据需要发出如果有那么那么就和确认报文一起发出。这样子可以大大减少TCP报文的数量。由于用户的输入速率明显小于客户端程序的处理速率所以客户端的确认报文段总是不携带任何程序数据。而前文也提到我们在关闭连接的时候可能会延迟收到一些数据因为可能发生了延迟确认。当然这个只在局域网里面有用但是在广域网里面会有很多的延迟。所以Nagle算法要求一个TCP通信双方在任意时刻最多只可以发一个未被确认的TCP报文段当TCP报文段的确认没有到达之前不可以发其他的报文段。另一个方面在等待接受确认报文段的同时收集需要发送的微量数据之后和确认报文段一起发出这样子就极大的减少了微小TCP报文段的数量。该算法的另一优点自适应性到达的越快发送的越快。TCP成块数据流当传输大量数据块的时候发送方会连续发送多个TCP报文段接收方可以一次性确认所有报文段。那么发送方收到上一次的确认后可以发送多少个报文段呢这个是取决于接受的窗口大小。TCP超时重传TCP服务器必须能够重传超时时间内未收到确认的TCP报文段。为此TCP模块为每个TCP报文段都维护了一个重传定时器该定时器在TCP报文第一次发送的时候就启动。如果超时时间之内没有收到对方的应答那么TCP模块将重传TCP报文段并且重置定时器。那么我们应该怎么样设置这个超时重传的时间呢假设执行了5次超时重传那么时间就是0.2s 0.4s 0.8s 1.6s 3.2s当这5次均失败之后IP和ARP就会接管因为这个时候就要检测网络是否可以到达。Linux两个重要的内核参数一个是在底层IP接管之前最少重传的次数一个是连接放弃前TCP最多可以执行的重传次数。拥塞控制我们先介绍几个概念SWND发送窗口CWND拥塞窗口RWND接受窗口SMSS TCP报文的最大长度。发送端需要合理选择SWND窗口如果太小会阻塞信息传递如果太大会使网络堵塞。所以实际就是SWND min(CWND, RWND)慢启动和拥塞避免最开始我们先设定一个窗口的大小1W比较的小然后随着数据不断的发送成功这个窗口逐渐的变大成指数增长为了避免过一会窗口就很大很大所以我们设定了一个阈值当超过这个阈值就变成线性增长速率变慢减缓窗口的扩大。这个让速度变慢的操作就是拥塞避免当拥塞发生的时候有两个判断依据1、超时传输2、接收到重复的确认报文段。第一种情况仍然使用慢启动和拥塞避免第二种情况使用快速恢复和快速重传。如果是第一种情况我们就要缓慢的减小窗口的大小。快速重传和快速恢复当收到重复的数据报我们需要判断是不是真的网络阻塞了具体说是不是TCP报文真的丢失了还是说只是因为网络原因阻塞了。具体做法是发送端如果连续收到三个重复的确认报文就确认是网络拥塞了。重传过程如下1、当收到第三个重复的确认报文重新设置CWND并重新传送丢失的报文。2、每一次收到1个重复的确认的时候重新设置CWND CWND SMSS相当于多了一个可以发送的报文段此时发送端可以发送TCP报文段。3、当收到新数据确认之后重新设置CWND。总结本篇文章到这里就结束了希望可以帮助大家理解本书的内容~~~