Transmission Control Protocol
*Transmission Control * 是 Internet protocol suite 的主要协议之一。 它诞生于最初的网络实现,作为 Internet Protocol (IP) 协议的补充。因此,完整的套件通常引用为 TCP/IP。TCP 提供 reliable ,ordered,和 error-checked 的应用间字节流传输 (应用在 hosts 上运行,通过 IP 网络)。TCP 是 TCP/IP suite 中运输层的一部分。SSL/TLS 运行于 TCP 之上。
TCP 是面向连接的,客户端与服务器之间的连接在数据传输前建立。连接建立前,服务器必须监听 (passive open) 来自客户端的连接请求。Threeway handshake (active open),retransmission,和 error detection 带来了可靠性,但增加了延迟。不需要可靠数据流服务的应用可能使用 User Datagram Protocol (UDP),它提供了无连接的数据报服务,并且即时性优先于可靠性。TCP 使用 network congestion avoidance。然而,TCP 有一些缺陷,包括拒绝服务 (denial of service),连接劫持 (connection hijacking),TCP veto,和 reset attack。
Network function
传输控制协议提供在应用程序和 Internet Protocol 之间的中间层级的通讯服务。它提供 Internet Model 的 transport layer 中主机之间的连接性。应用不需要知道在链接上向另一个主机发送数据的具体机制,例如需要 IP fragmentation 以满足传输媒体的 maximum transmission unit。在运输层,TCP 处理所有握手和传输细节,并向应用展现网络连接的抽象,通常是通过 network socket 接口。
在协议栈底层,因为 network congestion,traffic load balancing,或不可预测的网络行为,IP packets 可能会丢失,复制,或无序递交。TCP 检测到这些问题,请求重新传输丢失的数据,重新排序无序的数据,甚至帮助最大限度地减小网络拥塞来减少其他问题的发生。如果数据依然保持不可递交,将通知本次失败。因此,TCP 从底层地网络细节中抽象出应用的通讯。
TCP 被优化用于精确递交而非即时递交,可能导致相对长的延迟 (以秒为单位),比如等待无序的消息或丢失消息的重传。因此,对于实时应用比如 voice over IP 来说不是特别合适。对于这种应用,推荐使用运行于 User Datagram Protocol (UDP) 之上的 Real-time Transport Protocol (RTP) 之类的协议。
TCP 是一种可靠的字节递交服务,它保证所有接收的字节与发送的字节内容一致且顺序相同。因为通过多个网络的 packets 传输是不可靠的,TCP 使用 positive acknowledgement with retransmission 来实现这一点。这需要接收方在收到数据时回复确认消息。发送方保存每个它发送的 packet 的记录并在 packet 发送时,维护一个定时器。如果定时器超时先于收到来自接收方的确认,则重传该 packet。定时器在 packet 丢失或被损坏时,也需要。
IP 处理实际的数据递交,TCP 追踪 segments,数据传输的独立单元,为了在网络上高效路由,消息被分割为 segments。例如,当一个 HTML 文件从一个 web 服务器发送时,TCP 软件层将文件分割为 segments,并分别转发给 internet layer。Internet layer 软件将 TCP segments 封装为 IP datagram,通过添加包含目的 IP 地址的 header。当目的主机的客户端程序收到数据,TPC 软件将 segments 重新组装,保证它们有序且无错,好像文件内容流向接收的应用。
TCP segment structure
Transmission Control Protocol 从数据流中接收数据,划分为块,并添加一个 TCP header 创建 TCP segment。TCP segment 被封装进 IP datagram,在对端间交换。
TCP segment 由 header 和 data 两部分组成。data 部分在 header 之后,且携带应用的 payload data。data 部分的长度并没有在 segment header 中指定;它能通过从 IP datagram 的总长度中减去 segment header 和 IP header 的长度之和计算得出。
Source port (16 bits)
标识发送端口
Destination port (16 bits)
标识接收端口
Sequence number (32 bits)
扮演两个角色:
- 如果 SYN flag 被设置 (1),那这是初始序列号。 实际第一个数据字节的序列号和对应 ACK 的确认号是这个序列号加 1。
- 如果 SYN flag 没有设置 (0),则它是当前会话中该 segment 的第一个数据字节的累计序列号。
Acknowledgment number (32 bits)
如果 ACK flag 被设置,本字段的之是 ACK 发送者期望的下一个序列号。它确认所有先前收到的字节 (如果有的话)。每一端发送的第一个 ACK 确认另一端的初始序列号本身,而非数据。
Data offset (4 bits)
指定 TCP header 的大小,以 32-bit 的 words 为单位。header 的最小大小是 5 words,最大是 15 words,因此最小大小为 20 字节,最大大小为 60 字节,在 header 中最多允许 40 字节的 options。该字段因为它也是从 TCP segment 起始点到实际数据的偏移量而得名。
Reserved (3 bits)
用于将来使用,应该被设置为 0。
Flags (9 bits)
包含 9 个 1-bit 的 flags (控制位) 如下:
- NS (1 bit):ECN-none - 隐藏的保护
- CWR (1 bit):Congestion window reduced (CWR) flag 由发送 host 设置,表明它接收到一个设置了 ECE flag 的 TCP segment,并且在拥塞控制机制中做出响应。
- ECE (1 bit):ECN-Echo 扮演两个角色,取决于 SYN flag 的值。它表明:
- 如果 SYN flag 被设置 (1),TCP 对端是有 ECN 能力的。
- 如果没有设置 SYN (0),则说明收到了 IP header 中的 Congestion Experience flag 被设置 (ECN = 11) 的 packet。这向 TCP 发送方通知了网络拥塞 (或即将发生的网络拥塞)。
- UGR (1 bit):指示 Urgent pointer 字段有意义。
- ACK (1 bit):指示 Acknowledgment 字段有意义。在初始 SYN segment 后的所有 segments 都必须设置该 flag。一些 flags 和 fields 的含义基于该 flag 改变。
- PSH (1 bit):Push 功能。请求推送缓冲数据到接收应用。
- RST (1 bit):重置连接。
- SYN (1 bit):同步序列号。只有两端的第一个 packet 应该设置这个 flag。一些其他的的 flags 和 fields 的含义基于该 flag 改变,有一些仅当此 flag 设置时,才合法,其他一些则是没有设置此 flag 时合法。
Windows size (16 bits)
receive window 的大小,表示此 segment 的发送方当前期望接收的窗口大小单元的数量。
Checksum(16 bits): 16 位的 checksum 被用于对 TCP header,payload 和 IP pseudo-header 进行错误检查。
IP pseudo-header 保含源 IP 地址,目的 IP 地址,TCP 协议的协议号 (6) 和 TCP header 及 payload 的长度 (以字节为单位)。
Urgent pointer (16 bits)
如果 URG flag 被设置,这一 16-bit 字段用于表示从 Sequence number 开始到最后一个字节的紧急数据的偏移。
Options (Variable 0-320 bits, 以 32 bits 为单位)
Padding
TCP header 填充被用于保证 TCP header 结束,数据开始,在 32-bit 的边界上。填充由 0 组成。
Protocol operation
TCP 协议的操作能被分成 3 个阶段。Connection establishment 是一个多步骤的握手过程,它在 data transfer 阶段前建立了连接。在data transfer 完成之后,connection termination 关闭连接并释放所有分配的资源。
TCP 连接由操作系统管理,管理的对象是 internet socket,一个表示通讯的本地端点的资源。在 TCP 连接的生命周期,本地端点经受一系列的状态改变:
状态 | 端点 | 描述 |
---|---|---|
LISTEN | Server | 等待远程 TCP 端点的连接请求 |
SYN-SENT | Client | 在发送连接请求后,等待匹配的连接请求 |
SYN-RECEIVED | Server | 在接收和发送连接请求后,等待确认连接请求的 acknowledgment |
ESTABLISHED | Server 和 Client | 一个打开的连接,被接收的数据被递交给用户。连接的数据传输阶段的正常状态 |
FIN-WAIT-1 | Server 和 Client | 等待来自远程 TCP 的连接终止请求,或早先发送的连接终止请求的确认 |
FIN-WAIT-2 | Server 和 Client | 等待来自远程 TCP 的连接终止请求 |
CLOSE-WAIT | Server 和 Client | 等待来自本地用户的连接终止请求 |
CLOSING | Server 和 Client | 等待来自远程 TCP 的连接终止请求确认 |
LAST-ACK | Server 和 Client | 等待早先发送向远程 TCP 的连接终止请求的确认 |
TIME-WAIT | Server 或 Client | 等待足够的时间以保证所有连接上的依然存在的包都过期了 |
CLOSED | Server 和 Client | 不再是连接状态 |
Connection establishment
在客户端尝试连接服务器前,服务器必须首先绑定一个端口并在其上监听,以打开端口用于连接;这被称为 passive open。一旦 passive open 建立,客户端可能通过使用三路握手出初始化一个 active open 来建立连接。
- SYN: active open 通过客户端向服务器发送一个 SYN 执行。客户端将 segment 的 sequence number 设置为一个随机值 A。
- SYN-ACK: 响应中,服务器回复一个 SYN + ACK segment。acknowledgment number 为接收到的来自客户端的 SYN segment 中的 sequence number 值 A + 1;sequence number 为另一个随机数 B。
- ACK: 客户端收到 SYN-ACK segment 后,发送一个 ACK segment 对其作出确认。sequence number 被设置为收到的 SYN + ACK segment 的 acknowledgment number A + 1;acknowledgment number 被设置为为收到的 SYN + ACK segment 中的 sequence number 值 B + 1。
第 1,2 步建立在客户端上建立连接,第 2,3 步在服务器上建立连接;双方各自确认对方的连接请求,建立起一个全双工的通讯。
Connection termination
连接终止是一个四路握手过程。期望关闭连接的一端发送一个 FIN segment 请求关闭连接,对端回复一个 ACK segment 确认连接关闭请求,当第一个 FIN segment 的发送方接收到对端的 ACK segment 后,进入连接的半关闭状态。同样,对端也会发送一个 FIN segment 请求关闭它那一端的连接,第一个 FIN segment 发送方接收到该 FIN segment 后,发送一个 ACK segment 以确认连接关闭请求。对端收到 ACK segment 后,对于它来说,双端的连接完全关闭。而最后发送 ACK 的那一端需要等待一段时间,以保证如果最后一个 ACK segment 没有被对端接收,可以重新发送,在此期间该端口上不能建立新的连接。等待的时间由实现决定,一般值为 30s,1 min 和 2min。在等待超时后,等待端进入 CLOSED 状态,端口重新对新的连接可用。
使用三路握手终止一个连接也是可能的,主机 A 发送一个 FIN,主机 B 回复 FIN&ACK,然后主机 A 回复 ACK。
一些操作系统,例如 Linux 和 HP-UX,实现半双工关闭序列。如果主机主动关闭一个连接,但仍然有已到来未读取的数据,主机发送一个 RST 信号,而不是 FIN,并丢弃接收的数据。这保证 TCP 应用知道有数据丢失。
连接可能处于半打开的状态,在此状态下,一端关闭了连接,另一端没有。终止连接的那端不能再发送数据,但能接收数据,未关闭的那端可以继续发送数据,再所有数据发送完成后,再关闭连接。
Resource usage
大部分实现在映射会话到运行的操作系统进程的表中分配一个条目。因为 TCP packets 中不包含会话标识符,双端使用客户端的地址和端口标识会话。当收到一个 packet 时,TCP 实现在该表中查找目标进程。表中的条目被称为 Transmission Control Block 或 TCB。它包含关于 1.终端的信息 (IP 和 port),2.连接的状态,3.关于被交换的 packet 的运行数据,4.发送及接收数据的 buffer。
服务端的会话数量受限于内存,随着新的连接的建立而增长。客户端的会话数量受限于被使用的端口数量,一个 IP 地址能使用的端口数是有限的,当端口数被耗尽后,就不能建立新的连接了。客户端在发送 SYN segment 的前,分配一个 ephemeral port,端口在整个连接期间被占用。
双端也必须分配内存用于存储未确认的 packets 和已读取未向应用递交的 packets。
Data transfer
Transmission Control Protocol 较 User Datagram Protocol 有几点关键不同:
- 有序数据传输:目的主机根据 sequence number 对 segments 进行重新排序。
- 重传丢失的 packets:没有被确认的 segments 将被重传。
- 无错的数据传输:损坏的 segments 被视为丢失,将会重传。
- 流控制:限制发送的数据传输速率以保证接收端能及时处理数据,以提供可靠传输。接收方不停的通知发送方它能接收多少数据,当接收方的 buffer 满了,数据的传输将被暂停,直到接收方的 buffer 中有数据被读取。
- 拥塞控制:丢包 (推测由拥塞引起) 导致数据递交速率降低。
Reliable transmission
TCP 使用 sequence number 标识传输流中的每个字节,同时也隐含了流中每个字节的顺序,以便在数据接收端进行重新排序 (out-of-order delivery, 数据的到达顺序不一定是发送数据)。双端的第一个 sequence number (SYN segment 中的 sequence number) segment 的发送方决定,它应该是一个随机数,且是不可预测的,以防止 TCP sequence prediction attack 。
Acknowledgment (ACK) 表示当前接收方已确认收到的累计数据,以下一个期望收到的字节的 sequence number 表示。ACKs 并不表示数据被递交给了应用,它表示数据的向上递交现在由接收方负责。
可靠性由发送方检测到 数据丢失 并进行 重传 实现。TCP 使用两种主要的技术标识丢失。Retransmission timeout (RTO) 和 duplicate cumulative acknowledgment (DupAcks)。
Dupack-based retransmission
如果某个 segment (假设 sequence number 为 100) 丢失,接收方就不能确认这些 segments,其 sequence number 超过丢失的 segment 的 sequence number,这是因为累计 ACKs 的使用。接收方通过发送当前最新且连续的 acks (sequence number 99),即重复确认,来通知客户端该 segment (sequmence number 100) 的丢失。如果接收方收到 3 个重复的 ack, 它重传上一个未被确认的 segment。需要接收三个重复的 ack 是因为 TCP segment 通过网络到达接收方可能是乱序的,这也会导致重复确认行为,如果不是三个重复的 ack ,可能会造成虚假重传。一些 TCP 实现使用 selective acknowledgemnets (SACKs) 以提供关于所接收到的 segments 的显式回馈。这大大提高了 TCP 重传正确 segments 的能力。
Timeout-based retransmission
当发送方发送一个 segment,它初始化一个定时器,定时器的超时时间为对 acknowledgement 到达时间的保守估计,当定时器超时时,发送方重传该 segment,并将定时器超时时间设置为原超时时间的 2 倍,这形成 exponential bcakoff 行为。通常,初始化定时器的值为 smoothed RTT + max(G, 4xRTT variation),其中 G 为时钟粒度。这避免了可能因为错误的或恶意的行为而导致过度的流量传输,比如 man-in-the-middle denial of servic attackers。
Error detection
sequence number 允许接收方丢弃的重复 packets,并对失序的 packets 进行排序。Acknowledgements 允许发送方确认何时重传丢失的 packets。
TCP checksum 就现代标准看是一个 weak check,通常与 layer 2 的 CRC 完整性校验成对使用。然而跳点之间的 packets 中产生错误,跳点受 CRC 保护,十分常见,16 位的 TCP 检验和能捕获这些错误中的大部分。
Flow Control
TCP 使用端到端的 flow control 协议来避免 TCP 发送端发送数据过快导致接收端无法可靠的接收和处理数据。在具有 不同网络速度的机器 相互通讯的环境中,流控制机制必不可少。例如,如果一个 PC 向一个智能手机发送数据,但智能手机处理数据缓慢,智能手机必须能调节数据流,以免不堪重负。
TCP 使用 sliding window 流控制机制。在每个 TCP segment 中,接收方在 receive window 字段中指定愿意为连接缓存的数据的大小,以字节数为单位。发送方发送数据大小的上限由收到的 segment 中的 receive window 指定,想要发送更多的数据必须等待下一个接收的 segment 的到来以确认对端的 receive window 。
当接收方表示它的 receive window 的大小为 0 时,发送方不在发送数据,并维护一个 persist timer。如果后续的接收方 receive window 大小的更新丢失,发送方就可能永远无法向对方发送数据,但是因为 persist timer 的存在,当定时器超时时,发送方向接收方发送一个小的 packet,以探测接收方当前的 receive window 大小。
如果接收方以小增量处理到来的数据,接收方就会一直通告小的 receive window,那么发送方每次发送的数据 packet 就会很小,TCP header 就会有相对大的开销,这被称为 silly window syndrome。
Congestion control
发送数据的确认或缺乏确认,被发送方用于确认在 TCP 发送方和接收方之间暗示网络条件。配合定时器,TCP 的发送方和接收方能改变数据流的行为。这通常被称为拥塞控制或拥塞避免。
Maximum segment size
Maximum segment size (MSS) 指的是 TCP segment 中包含的数据的最大数量,以字节为单位。MSS 限制了 TCP segment 中的数据大小,其目的是当 TCP segment 被封装为 IP datagram 时,不会进行 IP fragmentation,IP fragmentation 可能造成 packets 丢失和过度重传。对于多大的 TCP segment 需要进行 IP fragmentation,其实取决于链接层的 maximum transmission unit (MTU),因为同样,IP datagram 会被封装为链接层的 frame,而链接层不会进行分片。TCP 可以在连接建立阶段使用 MSS 选项宣布使用的 MSS。TCP 发送方可以使用 path MTU discovery 找出发送方和接收方之间的网络路径中最小的 MTU,并使用该算法动态调整 MSS 以避免网络中的 IP fragmentation。
MSS announcement 也被称为MSS negotiation 但是,严格来说,MSS 是不 negotiated。两个完全独立的 MSS 值是允许的,TCP 连接每一个方向的数据流各自使用不同的 MSS。
Selective acknowledgments
原始 TCP 使用的累计确认方式在丢包时会导致效率低下。例如,TCP 发送方需要发送 sequence number 1000 到 10999 的字节流,它连续发送 TCP segment,每 1000 个字节一个 packet,假设第二个 packet (2000 ~ 2999) 丢失,其他 packet 都被 TCP 接收方正确收到了,依据累计确认机制,TCP 发送方需要发送 sequence number 2000 ~10999 的字节流,即第二个 packet 后的所有 packets。但是实际上,除了第二个 packet,其他的 packets 都正常到达了接收方,没有必要进行重传。
为了解决这个问题,TCP 给出了 selective acknowledgment(SACK) 选项,携带 SACK 选项的 acknowledgment 的 acknowledgment number 依然是累计确认的 sequence number + 1,但它包含几个SACK blocks,SACK block 表示后续正常接收到的连续的数据块,它包含从未确认 sequence number 的数据,即丢失的 packet 后第一个收到的数据字节开始,到该数据块的最后一个数据字节,表该 SACK block 已被接收方确认。发送方仅需重新发送实际未被接收到的丢失的 packet 即可。
TCP 发送方可能会将 segment 的乱序视为 segment 丢失。如果是这样,TCP 方会重传乱序部分的 segments,这会造成连接上的传输速率降低。duplicate-SACK 选项,SACK 的拓展选项用于解决这一问题,TCP 接收方向发送方发送一个 D-ACK 用于表示没有 segment 丢失。
Window scaling
为了更有效的使用一个高带宽的网络,可能需要一个更大的 TCP 窗口大小。16 位的 TCP 窗口大小值将数据流的数据大小限制为 65535 字节。因为不可能将窗口大小字段所占的位数拓展,所以在可选选项中设置了一个扩展因子来对窗口大小值进行调整,它即是 TCP window scale option。它至多能将 TCP 窗口大小拓展为 1 gigabyte。对于 TCP tuning 来说,拓展更大的窗口大小很有必要。
TCP timestamps
TCP timestamps,定义于 RFC 1323,能帮助 TCP 确定 TCP 的 packet 顺序。TCP timestamps 通常不与系统时钟对齐,它以一个随机值开始。很多操作系统随每毫秒增加时间戳;然而,RFC 仅表示时针跳动间隔应该成比例。
时间戳字段有两个:
- 4 字节发送者时间戳 (my timestamp)
- 4 字节 echo reply 时间戳 (接收到的最近的时间戳)
TCP timestamp 被用于算法 Protection Against Wrapped Sequence numbers,或 PAWS。PAWS 在接收窗口跨过 sequence number 的边界时使用。在 packet 可能被重传的情况下,它回答该问题:“sequence number 是在第一个 4GB 中还是第二个”,时间戳被用于解决该问题。
Eifel 检测算法使用 TCP timestamp 来确定是否重传的发生是因为 packet 丢失,还是因为无序。
Out-of-band data
不等待流的完成而是中断流是有可能的。这通过把数据指定为 urgent 完成。它标记传输为 out-of-band data,并告知接收进程立即处理它。当完成时,TCP 通知应用恢复流队列。比如,当 TCP 被用于远程登录会话时,用户可以发送一个键盘组合键来中断远程运行程序,不需要等待程序完成它当前的传输。
urgent point 仅改变远程主机上的处理,不加速网络本身的任何处理。该功能在不同的系统上实现不同,或实现的不好,甚至不支持。
Focing data delivery
通常,TCP 等待 200ms 后发送一个完整的数据包 (Nagle’s Algorithm 尝试将一些小的信息组合成一个 packet)。这一等待产生很小的延迟,但是如果在文件传输中不断重复,延迟就会很严重。例如,一个典型的发送 block 是 4KB,典型的 MSS 是 1460,所以在 10 Mbit/s 的 Ethernet 上发送两个 packets 大约每个花费 1.2ms,但是携带剩余的 1176 字节的第三个 packet 的发送将等待 197ms,因为 TCP 正在等待 buffer 被填满。在 telnet 的情况下,每个用户的键入有服务器返回到其屏幕上。这个延迟将变得十分令人厌烦。
设置 socket 选项 TCP_NODELAY
覆盖默认的 200ms 发送延迟。应用程序使用该 socket 选项来强制输出在单个字符或一行字符写入后被发送。
RFC 定义 PSH
push bit 作为 “消息被 TCP 栈接收后立刻向接收应用发送该数据”。