引言
什么是 TCP 的三次“握手”?相信大部分人都知道这个面试中常问的面试题,在这篇文章中,我会详细地解释它的细节,到时候你会发现,这是个非常简单的问题。然而,我们学习网络知识并不是为了去准备面试的,由于大部分的网络应用都采用 TCP 作为传输层的协议,因此了解它对于我们写网络程序很重要。在这篇文章中,我会深入很多 TCP 的技术细节,比如它是如何实现可靠数据传输,flow control,和 congestion control 的。如果大家能仔细读完这篇文章,相信一定会对 TCP 有个非常全面的了解。
TCP 连接管理
在这个小节中,我首先会大致介绍 TCP 连接的过程,然后会深入其细节,详细讲解在连接建立的过程中需要设置哪些 bit,初始化哪些状态,和如何关闭一个连接。
TCP 协议只运行在 end systems 上,因此中间的网络元素(比如:routers 和 link-layer switches)并不会去维护 TCP 的连接状态。事实上,中间的 routers 完全不知道有 TCP 连接,它们只看 datagrams. 一个 TCP 连接提供 full-duplex service,假设一个 host 上的进程 A 与 另一个 host 上的进程 B 之间存在一个 TCP 连接,那么应用层数据在从进程 A 到进程 B 的同时,也可以从进程 B 到进程 A.
一旦 TCP 连接建立完成以后,2个进程之间就可以彼此发送数据。如下图所示,进程写一个数据流通过 Socket,然后 TCP 把这些数据放到 TCP send buffer(它在 three-way handshake 期间被设置) 中,不时 TCP 会从这个 buffer 中取一些数据,接着把它们传递到网络层。实际上,Each side of the connection has its own send buffer and its own receive buffer.
现在让我们来看看建立一个 TCP 连接的具体细节。假设一个 client 的进程想与 server 端的一个进程之间建立起一个 TCP 连接,它需要如下三个步骤:
1、client-side TCP 首先发送一个特殊的 TCP segment 到 server-side TCP. 这个特殊的 TCP segment 并不包含任何应用层的数据,但是这个 segment header 中的 SYN bit 被设为1,因此,这个 segment 叫做 SYN segment. 另外, client 也会随机选择一个初始的 sequence number (client_isn),并把它放到 segment 中的 sequence number field. 接着这个 segment 被封装到一个 IP datagram 中,发给 server.
2、一旦 server 端收到了步骤1中发送的 SYN segment,它会分配针对这个连接的 buffers 和变量,之后发送一个 connection-granted segment 给 client TCP,这个 segment 也不包含任何的应用层数据,但是这个 segment 有3个 header 会被设置:(1)SYN bit 设为1. (2)acknowledgment field 设置为 client_isn+1. (3)server 会选择一个属于它自己的初始 sequence number (server_isn),并把它放到 sequence number field. 这个 connection-granted segment 被称作 SYNACK segment
3、根据收到的 SYNACK segment,client 会分配针对这个连接的 buffers 和 变量。然后,client 会向 server 发送一个 segment,这个 segment acknowledges server 的 connection-granted segment(通过把 server_isn+1 放入到 acknowledgment field 中),由于连接已经建立,SYN bit 会设置成 0,这个 segment 也有可能会携带 client-to-server 的数据
从上面的步骤中可以看出,为了建立一个 TCP 连接需要在 host 之间发送3个 segment,因此连接建立的过程通常被叫做 three-way handshake. 整个过程如下图所示:
说完了连接的建立过程,现在让我们继续讨论一下连接关闭的过程吧。一个 TCP 连接中的 2 个进程中的任何一个进程都可以结束连接,当一个连接结束时,2个 host 中的资源(buffers 和 variables)被释放。现在我假设是 client 想要关闭连接,它的整个过程如下图:
client TCP 会发送一个特殊的 TCP segment 到 server 进程,这个 segment 中的 FIN bit 被设为1. 当 server 收到这个 segment 以后,它会发送一个 acknowledgment segment 给 client. 紧接着,server 会发送它自己的 shutdown segment,其中的 FIN bit 也设为1. 最后,client acknowledges server 的 shutdown segment.,此时,2个 host 中的所有都会被释放。
TCP Segment 结构
在这个小节中,我会详细介绍 TCP Segment 中各个 header fields 所表示的含义,下图就是一个 TCP Segment,它包含了很多的 header fields 和 一个 data field. 在详细解释这些 header fields 所表示的含义之前,我先来解释一下 Maximum Segment Size(MSS) 是什么?
MSS 就是可以放在1个 segment 中最大的数据量。 那么问题来了,我们如何来设置 MSS 呢?首先,设置 MSS 是为了确保 TCP segment(当它被封装进 IP datagram 时) 加上 network-layer headers 可以纳入到单个 link-layer frame. 因此,我们通常会找到 path MTU(the largest link-layer frame that can be sent on all links from source to destination),然后基于它来设置 MSS. 大家一定要记住:MSS 是一个 segment 中最大的 application-layer data 的大小,它并不包括 segment 中的 headers.
当 TCP 发送一个大文件时,它通常把文件打碎成大小为 MSS(最后1个 chunk 的大小有可能小于 MSS) 的 chunks,当 TCP 创建一个 segment 时,把其中的1个 chunk 放进 segment 的 data field. 下面我来介绍一下各个 header fields 所表达的含义。
TCP sender 和 receiver 用 sequence number field 和 acknowledgment number field 来实现可靠数据传输。The sequence numbers are over the stream of transmitted bytes and not over the series of transmitted segments. The sequence number for a segment is therefore the byte-stream number of the first byte in the segment. 下面我给大家举例说明一下 TCP 在这2个 fields 中都放入什么值。
假设 Host A 中的一个进程想要发送一个大小为 500000 字节的文件到 Host B 中的一个进程,MSS 设置成1000个字节。Host A 中的 TCP 会为数据流中的每个字节进行编号,第1个字节的编号为0,如下图所示:
第1个 segment 的 sequence number 为0,第2个 segment 的 sequence number 为1000,第3个 segment 的 sequence number 为2000,依此类推,这些 sequence number 被插进相应 segment 的 sequence number field.
接下来让我们来了解 acknowledgment numbers 代表的含义是什么。由于 TCP 是 full-duplex,因此对于同1个 TCP 连接来说,当 Host A 给 Host B 发送数据的同时,它也可以收到 Host B 发给它的数据。下面让我们来一个具体的例子。
Telnet 是一个有名的 application-layer 协议,它用的传输层协议就是 TCP,假设 Host A 发起一个 session 到 Host B,由于是 Host A 发起的 session,所以它是 client,Host B 是 server. 这里我提醒大家一下,client 既可以做 TCP 的 sender,也可以做 TCP 的 receiver,sever 也如此。这里我们只想让 Host A 发送1个字符 ‘c’ 到 Host B,然后 Host B 在把这个字符发送回来。
对于上图来说,我需要强调以下几个方面:
1)在上面的例子中,我把初始的 sequence number 标记为0,但是在实际的 TCP 连接的建立过程中,它是由 TCP 2 端的 Host 随机选择的,在它们进行 three-way handshake 时就已经选好了,因此从上图我们可以看到对于 Host A 来说,初始的 sequence number 为42,对于 Host B 来说,初始的 sequence number 是79,这没什么可争议的。
2)上图的第1步中,acknowledgment number 是79,它所表示的含义是它期望从 Host B 那收到的下一个 segment 的 sequence number
3)上图的第2步中,有2个目的:一个是提供 acknowledgment 表示 server 已经收到了 client 发送的数据,它通过把 acknowledgment number 置为 43 表明我已经收到了 sequence number 为 42 以及 42 以下的所有字节,一,现在我正在等 sequence number 为 43 的字节; 另一个是它携带了数据,即字符 ‘c’,发送回 client. This acknowledgment is said to be piggybacked on the server-to-client data segment.
4)上图第3步中的 segment 只有1个目的,就是 acknowledge 它已经成功地从 server 收到数据。由于这次的 segment 并没有携带数据,因此 receiver 并不会发送任何的 acknowledgment.
接下来,我来继续解释 segment 中各个 headers 的含义。
- 16-bit 的 receive window field 是用于 flow control 的,在下文中我会详细解释的
- 4-bit header length filed 用于指定 TCP header 的长度。由于 TCP 的 options field,TCP header 的长度可能会发生变化,通常情况下 options field 是空的,因此 TCP header 的长度通常是 20 个字节
- The optional and variable-length options field is used when a sender and receiver negotiate the maximum segment size (MSS) or as a window scaling factor for use in high-speed networks. A time-stamping option is also defined
- flag field 包含 6 bits. The ACK bit is used to indicate that the value carried in the acknowledgment field is valid; that is, the segment contains an acknowledgment for a segment that has been successfully received. The RST, SYN, and FIN bits are used for connection setup and teardown. Setting the PSH bit indicates that the receiver should pass the data to the upper layer immediately. Finally, the URG bit is used to indicate that there is data in this segment that the sending-side upper-layer entity has marked as “urgent.” The location of the last byte of this urgent data is indicated by the 16-bit urgent data pointer field. TCP must inform the receiving-side upper-layer entity when urgent data exists and pass it a pointer to the end of the urgent data. (In practice, the PSH, URG, and the urgent data pointer are not used.)
TCP 可靠数据传输
在这个小节中,我将会详细地介绍 TCP 是如何实现可靠数据传输的,但是在读本小节之前,我强烈建议大家先读一下 Reliable Data Transfer Protocol 的原理剖析,理解可靠数据传输的原理之后,我们再来看看 TCP 用到了什么样的技术来实现它的可靠数据传输。
如何设置 retransmit packet 的超时时间
TCP 用 timeout/retransmit 机制来应对底层 channel 可能丢失 packet 的情况,道理其实很简单,但是如果想要实现的这样的机制,我们有一个难题需要解决,也就是 timeout intervals 应该设成多大呢?有一点可以很明确,timeout 应该大于连接的 round-trip time(RTT,即从 segment 的发送到它被 acknowledged 所需要的时间)。那么接下来,就让我们看一下 TCP 是如何估算 sender 与 receiver 之间的 RTT.
TCP 通过 sampling(抽样) 发送到连接中的 segment 的行为,然后 averaging 这些 samples(样本)到一个 “平滑的” RTT 估算,我们暂且把这个估算叫做 EstimatedRTT. 当一个 segment 被发送到 TCP 连接中时,sender 会计时它花费多长时间被 acknowledged,因此会产生一系列地 RTT samples: S(1),S(2),S(3),… ,每得到一个新的 sample S(i) 时,就会用下面的公式更新 EstimatedRTT: