既然我们已经学习了可靠数据传输的基本原理,我们就可以转而学习 TCP 了。TCP 是因特网运输层的面向连接的可靠的运输协议。

我们在本节中将看到,为了提供可靠数据传输,TCP 依赖于前一节所讨论的许多基本原理,其中包括差错检测、重传、累积确认、定时器以及用于序号和确认号的首部字段。TCP 定义在 RFC 793、RFC 1122、RFC 1323、RFC 2018 以及 RFC 2581 中。

TCP 连接

TCP三次握手连接和报文 - 图1 TCP 三次握手连接

TCP 被称为是面向连接的(connection-oriented),这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互 “握手”,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。作为TCP 连接建立的一部分,连接的双方都将初始化与 TCP 连接相关的许多 TCP 状态变量。

我们现在来看看 TCP 连接是怎样建立的:客户首先发送一个特殊的 TCP 报文段,服务器用另一个特殊的 TCP 报文段来响应,最后,客户再用第三个特殊报文段作为响应。前两个报文段不承载“有效载荷”,也就是不包含应用层数据;而第三个报文段可以承载有效载荷。 由于在这两台主机之间发送了 3 个报文段,所以这种连接建立过程常被称为三次握手(three- way handshake)

三次握手连接

TCP三次握手连接和报文 - 图3 报文段通过套接字送入 TCP 的缓存区域,之后通过链路将报文段发送出去

一旦建立起一条 TCP 连接,两个应用进程之间就可以相互发送数据了。我们考虑一下从客户进程向服务器进程发送数据的情况。

客户进程通过套接字(该进程之门)传递数据流。数据一旦通过该门,它就由客户中运行的 TCP 控制了。如下图所示,TCP 将这些数据引导到该连接的发送缓存(send buffer)里,发送缓存是发起三次握手期间设置的缓存之一。

TCP将数据送入缓存中

接下来 TCP 就会不时从发送缓存里取出一块数据,并将数据传递到网络层。有趣的是,在 TCP 规范中却没提及 TCP 应何时实际发送缓存里的数据,只是描述为 “TCP 应该在它方便的时候以报文段的形式发送数据”。

TCP 为每块客户数据配上一个 TCP 首部,从而形成多个 TCP 报文段(TCP segment)。这些报文段被下传给网络层,网络层将其分别封装在网络层 IP 数据报中。然后这些 IP 数据报被发送到网络中。当 TCP 在另一端接收到一个报文段后,该报文段的数据就被放入该 TCP 连接的接收缓存中。

TCP三次握手连接和报文 - 图5 报文段的大小受限于最大报文段长度,最大报文段长度取决于以太网和链路层。

  • TCP 可从缓存中取出并放入报文段中的数据数量受限于最大报文段长度(Maximum Segment Size,MSS)。
  • MSS 通常根据最初确定的由本地发送主机发送的最大链路层帧长度(即所谓的最大传输单元(Maximum Transmission Unit, MTU))来设置。

TCP三次握手连接和报文 - 图6%20%3D%20%5Ctext%7BTCP%2FIP%7D%E9%A6%96%E9%83%A8%E9%95%BF%E5%BA%A6%20%2B%20%5Ctext%7BTCP%7D%E6%9C%80%E5%A4%A7%E6%8A%A5%E6%96%87%E6%AE%B5%E5%A4%A7%E5%B0%8F(%5Ctext%7BMSS%7D)%0A#card=math&code=%E6%9C%80%E5%A4%A7%E9%93%BE%E8%B7%AF%E5%B1%82%E5%B8%A7%E9%95%BF%E5%BA%A6%28%5Ctext%7BMTU%7D%29%20%3D%20%5Ctext%7BTCP%2FIP%7D%E9%A6%96%E9%83%A8%E9%95%BF%E5%BA%A6%20%2B%20%5Ctext%7BTCP%7D%E6%9C%80%E5%A4%A7%E6%8A%A5%E6%96%87%E6%AE%B5%E5%A4%A7%E5%B0%8F%28%5Ctext%7BMSS%7D%29%0A&id=wpd4w)

MSS 通常根据最初确定的由本地发送主机发送的最大链路层帧长度(即所谓的最大传输单元(Maximum Transmission Unit, MTU))来设置。设置该MSS要保证一个 TCP 报文段(当封装在一个IP数据报中)加上 TCP/IP 首部长度(通常40字节)将适合单个链路层帧。以太网和 PPP 链路层协议都具有 1500 字节的 MTU,因此 MSS 的典型值为 1460 字节。

注意到 MSS 是指在报文段里应用层数据的最大长度,而不是指包括首部的 TCP 报文段的最大长度。

从以上讨论中我们可以看出,TCP连接的组成包括:一台主机上的缓存、变量和与进程连接的套接字,以及另一台主机上的另一组缓存、变量和与进程连接的套接字。如前面讲过的那样,在这两台主机之间的网络元素(路由器.交换机和中继器)中,没有为该连接分配任何缓存和变量。

TCP 报文

之前我们看到两个电脑的进程之间是通过报文段进行通信的,现在来看一下报文段的具体结构。

TCP 报文段由首部字段和一个数据字段组成。数据字段包含一块应用数据。

如前所述,MSS 限制了报文段数据字段的最大长度。当 TCP 发送一个大文件,例如某 Web 页面上的一个图像时,TCP 通常是将该文件划分成长度为 MSS 的若干块(最后一块除外,它通常小于MSS)。然而,交互式应用通常传送长度小于 MSS 的数据块。

文件分成块发送

例如,对于像 Telnet 这样的远程登录应用, 其 TCP 报文段的数据字段经常只有一个字节。由于 TCP 的首部一般是 20 字节 (比 UDP 首部多 12 字节),所以 Telnet发送的报文段也许只有 21 字节长。

下图显示了 TCP 报文段的结构。与 UDP —样,首部包括源端口号和目的端口号,它被用于多路复用/分解来自或送到上层应用的数据。另外,同 UDP —样,TCP
首部也包括检验和字段 (checksum field) 。TCP 报文段首部还包含下列字段:

TCP报文

  • 32 比特的序号字段 (sequence number field) 和 32 比特的确认号字段 (acknowledgment number field) 。这些字段被 TCP 发送方和接收方用来实现可靠数据传输服务。
  • 4 比特的首部长度字段 (header length field),该字段指示了以 32 比特的字为单位的 TCP 首部长度。由于 TCP 选项字段的原因,TCP 首部的长度是可变的。(通常, 选项字段为空,所以 TCP 首部的典型长度是 20 字节。)
  • 16 比特的接收窗口字段 (receive window field),该字段用于流量控制,该字段用于指示接收方愿意接受的字节数量。
  • 可选与变长的选项字段 (options field),该字段用于发送方与接收方协商最大报文段长度 (MSS) 时,或在高速网络环境下用作窗口调节因子时使用。首部字段中还定义了一个时间戳选项。
  • 6 比特的标志字段 (flag field)
    • ACK 比特用于指示确认字段中的值是有效的,即该报文段包括一个对已被成功接收报文段的确认。
    • RST、SYN 和 FIN 比特用于连接建立和拆除
    • 在明确拥塞通告中使用了 CWR 和 ECE 比特
    • 当 PSH 比特被置位时,就指示接收方应立即将数据交给上层
    • URG 比特用来指示报文段里存在着被发送端的上层实体置为 “紧急” 的数据。紧急数据的最后一个字节由 16 比特的紧急数据指针字段指出。当紧急数据存在并给出指向紧急数据尾指针的时候,TCP 必须通知接收端的上层实体。
    • (在实践中,PSH、URG 和紧急数据指针并没有使用。为了完整性起见,我们才提到这些字段。)

1. 序号和确认号

TCP 报文段首部中两个最重要的字段是序号字段和确认号字段。这两个字段是 TCP 可靠传输服务的关键部分。但是在讨论这两个字段是如何用于提供可靠数据传输之前,我们首先来解释一下 TCP 在这两个字段中究竟放置了什么。

TCP三次握手连接和报文 - 图9 序号

在 TCP 传输文件的时候,是将文件分成字节大小,然后对每个字节进行编号,然后将字节聚合成 MSS 大小的报文段。例如,一个文件大小为 500000字节,MSS 为 5000 字节,那么 TCP 报文段的大小如下所示。

给报文段编辑序号

TCP三次握手连接和报文 - 图11 确认号

现在我们考虑一下确认号。确认号要比序号难处理一些。TCP 是全双工的, 因此主机 A 在向主机 B 发送数据的同时,也许也接收来自主机 B 的数据 (都是同一条TCP 连接的一部分)。从主机 B 到达的每个报文段中都有一个序号,该序号用于从 B 流向 A 的数据。主机 A 填充进报文段的确认号是主机 A 期望从主机 B 收到的下一字节的序号。

假设主机 A 已收到了来自主机 B 的编号为 0 ~535 的所有字节,同时假设它打算发送一个报文段给主机 B。主机 A 等待主机 B 的数据流中字节 536 及之后的所有
字节。所以主机 A 就会在它发往主机 B 的报文段的确认号字段中填上 536。

接收方返回的确认号

2. Telnet:序号和确认号的一个学习案例

Telnet 是一个用于远程登录的流行应用层协议。它运行在 TCP 之上,被设计成可在任意一对主机之间工作。许多用户现在更愿意采用 SSH 协议而不是 Telnet,因为在 Telnet 连接中发送的数据(包括口令!)是没有加密的,使得 Telnet 易于受到窃听攻击。