UNIX网络编程——传输层:TCP、UDP 和 SCTP
总图
虽然协议族被称为“TCP/IP”,但除了这两个主要协议外,还有许多其他成员。下图中展示了这些协议的概况:
用户数据报协议(UDP)
UDP是一个简单的传输层协议。
应用程序往一个UDP套接字写入一个消息,该消息随后被封装(encapsulating)到一个UDP数据报,该UDP数据报进而又被封装到一个IP数据报,然后发送到目的地。
UDP不保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数据报只到达一次。
UDP缺乏可靠性支持,应用程序必须实现:确认、超时、重传、流控等。
每个UDP数据报都有一个长度。如果一个数据报正确地到达其目的地,那么该数据报的长度将随数据一道传递给接收端应用程序。
UDP提供无连接服务,因为UDP客户与服务器之间不必存在任何长期的关系。
传输控制协议(TCP)
首先,TCP提供客户与服务器之间的连接(connection)。TCP 客户先与某个给定服务器建立一个连接,再跨该连接与那个服务器交换数据,然后终止连接。
其次,TCP提供可靠性(reliability)。当TCP向另一端发送数据时,它要求对端返回一个确认。如果没有收到确认,TCP就自动重传数据并等待更长时间。在数次重传失败后,TCP才放弃。
UDP提供不可靠的数据报传送。UDP本身不提供确认、序列号、RTT估算、超时及重传等机制。
TCP含有用于动态估算客户和服务器之间的往返时间(round-trip time, RRT)的算法,以便它知道等待一个确认需要多少时间。
TCP通过给其中每个字节关联一个序列号对所发送的数据进行排序(sequencing)。
TCP提供流量控制和拥塞控制。
TCP总是告知对端在任何时刻它一次能够从对端接收多少字节的数据,称为通告窗口(advertised window)。
TCP的连接是全双工的(full-duplex)。这意味着在一个给定的连接上应用可以在任何时刻在进出两个方向上既发送数据又接收数据。
流控制传输协议(SCTP)
SCTP在客户和服务器之间提供关联(association),并像TCP那样给应用提供可靠性、排序、流量控制以及全双工的数据传送。
SCTP中使用“关联”一词取代“连接”只为了避免这样的内涵:一个连接只涉及两个IP地址之间的通信。
一个关联取代两个系统之间的一次通信,它可能因为SCTP支持多宿而涉及不止两个地址。
SCTP是面向消息的。它提供各个记录的按序递送服务。
与UDP一样,由发送端写入的每条记录的程度随数据一道传递给接收端应用。
SCTP能够在所连接的端点之间提供多个流,每个流各自可靠地按序递送消息。
一个流上某个消息的丢失不会阻塞同一关联其他流上消息的投递。
TCP,在单一字节流中任何位置的自己丢失都将阻塞该连接上其后所有数据的递送,直到该丢失被修复为止。
SCTP还提供多宿特性,使得单个SCTP端点能够支持多个IP地址。可以增强应对网络故障的健壮性。
一个端点可能有多个冗余的网络连接,每个网络又可能有各自接入因特网基础设施的连接。当该端点与另一个端点建立一个关联后,如果它的某个网卡或某个跨越因特网的通路发生故障,SCTP就可以通过切换到使用已与该关联相关的另一个地址来归并所发生的故障。
TCP连接的建立和终止
三路握手
建立一个TCP连接时会发生如下情形:
- (1)服务器必须准备好接受外来的连接。
通常通过调用 socket 、bind 和 listen 这3个函数来完成,称之为被动打开。 - (2)客户通过调用 connect 发起主动打开。
客户TCP发送一个SYN(同步)分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号。
通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项。 - (3)服务器必须确认(ACK)客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。
服务器在单个分节中发送SYN和对客户SYN的ACK(确认)。 - (4)客户必须确认服务器的SYN。
这种交换至少需要3个分组,因此称之为TCP的三路握手。
TCP选项
每一个SYN可以含有多个TCP选项。
下面是常用的TCP选项。
-
MSS选项。
发送SYN的TCP一端使用本地选项通告对端它的最大分节大小(即MSS),也就是它在本连接的每个TCP分节中愿意接受的最大数据量。
发送端TCP使用接收端的MSS值作为所发送分节的最大大小。 -
窗口规模选项。
TCP连接任何一端能够通告对端的最大窗口大小是65535,因为在TCP首部中农相应的字段占16位。
这个新选项指定TCP首部中的通告窗口必须扩大(即左移)的位数(0~14),因此所提供的最大窗口接近 1 GB (65535 × 2 14 \times 2^{14} ×214)。
在一个TCP连接上使用窗口规模的前提是它的两个端系统必须都支持这个选项。 -
时间戳选项。
这个选项对于高速网络连接是必要的,它可以放在由失而复现的分组可能造成的数据损坏。
它是一个较新的选项,也以类似于窗口规模选项的方式协商处理。
TCP的大多数实现都支持这些常用选项。
后两个选项有时称为“RFC 1323 选项”,因为它们是在 RFC 1323 中说明的。
高速带宽或长延迟的网络被称为“长胖管道”,这两个选项也称为“长胖管道选项”。
TCP连接终止
TCP终止一个连接则需4个分节。
-
(1)某个应用进程首先调用 close,称该端执行主动关闭。
该端的TCP于是发送一个FIN分节,表示数据发送完毕。 -
(2)接收到这个FIN的对端执行被动关闭。
这个FIN由TCP确认。
它的接收也作为一个文件结束符传递给接收端应用进程(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。 -
(3)一段时间后,接收到这个文件结束符的应用进程将调用 close 关闭它的套接字。这导致它的TCP也发送一个FIN。
-
(4)接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。
TCP状态转换图
TCP涉及连接建立和连接终止的操作可以用状态转换图来说明。
TCP为一个连接定义了11种状态,并且TCP规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态。
观察分组
一个完整的TCP连接所发生的实际分组交换情况,包括连接建立、数据传送和连接终止3个阶段。
一旦建立一个连接,客户就构造一个请求并发给服务器。
上图中,值得注意的是,如果该连接的整个目的仅仅是发送一个单分节的请求和接收一个单分节的应答,那么使用TCP有8个分节的开销。
如果改用UDP,那么只需交换两个分组:一个承载请求,一个承载应答。
TCP提供的另一个重要特性即拥塞控制也必须由UDP应用进程来处理。
许多网络应用是使用UDP构建的,因为它们需要交换的数据量较少,而UDP避免了TCP连接建立和终止所需的开销。
TIME_WAIT状态
分组在网络中“迷途”通常是路由异常的结果。
某个路由器崩溃或某两个路由器之间的某个链路断开时,路由协议需花数秒钟到数分钟的时间才能稳定并找出另一条通道。在这段时间内有可能发送路由循环(路由器A把分组发送到路由器B,而B再把它们发送回A)。
假设迷途的分组是一个TCP分节,在它迷途期间,发送端TCP超时并重传该分组,而重传的分组却通过某条候选路径到达最终目的地。
然而不就后(自迷途的分组开始其旅程起最多MSL秒以内)路由循环修复,早先迷失在这个循环中的分组最终也被送到目的地。
这个原来的分组称为迷途的重复分组或漫游的重复分组。
TCP必须正确处理这些重复的分组。
TIME_WAIT状态有两个存在的理由:
- (1)可靠地实现TCP全双工连接的终止;
- (2)允许老的重复分节在网络中消逝。
SCTP关联的建立和终止
SCTP也是面向连接的,因而也有关联的建立与终止的握手过程。
不过SCTP的握手过程不同于TCP。
四路握手
建立一个SCTP关联的时候会发生下述情形。
- (1)服务器必须准备好接受外来的关联。通常通过调用 socket、bind 和 listen 这3个函数来完成,称为被动打开。
- (2)客户通过调用 connect 或者发生一个隐式打开该关联的消息进行主动打开。
这使得客户SCTP发送一个 INIT 消息(初始化),该消息告诉服务器客户的IP地址清单、初始序列号、用于标识本关联中所有分组的起始标记、客户请求的外出流的数目以及客户能够支持的外来流的数目。 - (3)服务器以一个 INIT ACK 消息确认客户的 INIT 消息,其中含有服务器的IP地址清单、初始序列号、起始标记、服务器请求的外出流的数目、服务器能够支持的外来流的数目以及一个状态 cookie。 状态cookie包含服务器用于确信本关联有效所需的所有状态,它是数字化签名过的,以确保其有效性。
- (4)客户以一个COOKIE ECHO 消息回射服务器的状态 cookie。除此之外,该消息可能在同一个分组中还捆绑了用户数据。
- (5)服务器以一个 COOKIE ACK 消息确认客户回射的cookie是正确的,本关联于是建立。该消息也可能在同一个分组中还捆绑了用户数据。
四路握手过程结束时,两端各自选择一个主目的地址。当不存在网络故障时,主目的地址将用作数据要发送到默认目的地。
关联终止
SCTP,当一端关闭某个关联时,另一端必须停止发送新的数据。
关联关闭请求的接收端发送完已经排队的数据后,完成关联的关闭。
SCTP没有类似于TCP的 TIME_WAIT 状态,因为SCTP使用了验证标记。
SCTP状态转换图
SCTP涉及关联建立和关联终止的操作可以用状态转换图来说明。
观察分组
下图展示了 SCTP关联所发生的实际分组交换情况,包括关联建立、数据传送和关联终止3个阶段。
还展示了每个端点所历经的SCTP状态。
SCTP分组中信息的单位称为块。
块是自描述的,包含一个块类型、若干个块标记和一个块长度。
SCTP选项
SCTP使用参数和块方便增设可选特性。
新的特性通过添加这两个条目之一加以定义,并允许通常的SCTP处理规则汇报未知的参数和未知的块。
当前如下两个对SCTP的扩展正在开发中。
- (1)动态地址扩展,允许协作的SCTP端点从已有的某个关联中动态增删IP地址。
- (2)不完全可靠性扩展,允许协作的SCTP端点在应用进程的指导下限定数据的重传。
端口号
多个进程可能同时使用TCP、UDP和SCTP这3种传输层协议中的任何一种。这3种协议都使用16位整数的端口号来区分这些进程。
当一个客户想要跟一个服务器联系时,它必须标识想要与之通信的这个服务器。
TCP、UDP和SCTP定义了一组众所周知的端口,用于标识众所周知的服务。
另一方面,客户通常使用短期存活的临时端口。这些端口号通常由传输层协议自动赋予客户。
客户通常不关心其临时端口的具体值,而只需确信该端口在所在主机中是唯一的就行。
传输协议的代码确保这种唯一性。
LANA(因特网已分配数值权威机构)维护着一个端口号分配状况的清单。
端口号被划分成以下3段。
- (1)众所周知的端口为 0~1023。这些端口由LANA分配和控制。
- (2)已登记的端口为 1024~49151。这些端口不受LANA控制,不过由LANA登记并提供它们的使用情况清单,以方便整个群体。
- (3)49152~65535是动态的或私用的端口。LANA不管这些端口。它们就是我们所称的临时端口。(49152这个魔数是65536的四分之三。)
要注意上图中以下几点。
- Unix系统有保留端口的概念,指的是小于1024的任何端口。
- 由于历史原因,源自Berkeley的实现(从 4.3BSD开始)曾在1024~5000范围内分配临时端口。
- 有少数客户(而不是服务器)需要一个保留端口用于客户/服务器的认证: rlogin 和 rsh 客户就是常见的例子。
套接字对
一个TCP连接的套接字对是一个定义该连接的两个端点的四元组:本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号。
套接字对唯一标识一个网络上的每个TCP连接。
就SCTP而言,一个关联由一组本地IP地址、一个本地端口、一组外地IP地址、一个外地端口标识。
标识每个端点的两个值(IP地址和端口号)通常称为一个套接字。
TCP端口号与并发服务器
并发服务器中主服务器循环通过派生一个子进程来处理每个新的连接。
一个典型的序列。
首先,在主机 freebsd上启动服务器,该主机是多宿的,其IP地址为12.106.32.254和192.168.42.1。
服务器在它的众所周知的端口上执行被动打开,从而开始等待客户的请求,如下图:
指定本地IP地址的星号称为通配符。
如果运行服务器的主机是多宿的,服务器可以指定它只接受到达某个特定本地接口的外来连接。
服务器不能指定一个包含多个地址的清单。通配的本地地址表示“任意”这个选择。
假设本例中客户主机的TCP为此选择的临时端口为 1500,如下图所示。
图中在该客户的下方标出了它的套接字对。
当服务器接收并接受这个客户的连接时,它 fork 一个自身的副本,让子进程来处理该客户的请求。如下图所示。
至此,我们必须在服务器主机上区分监听套接字和已连接套接字。
注意,已连接套接字使用与监听套接字相同的本地端口(21)。
还要注意在多宿服务器主机上,连接一旦建立,已连接套接字的本地地址(12.106.32.254)随即填入。
下一步,假设在客户主机上另有一个客户请求连接到同一个服务器。
客户主机的TCP为这个新客户的套接字分配一个未使用的临时端口,譬如说1501。 如下图所示。
服务器上这两个连接是有区别的:第一个连接的套接字对和第二个连接的套接字对不一样,因为客户的TCP给第二个连接选择了一个未使用的端口(1501)。
TCP无法仅仅通过查看目的端口号来分离外来的分节到不同的端点。
它必须查看套接字对的所有4个元素才能确定由哪个端点接收某个到达的分节。
缓冲区大小及限制
一些影响IP数据报大小的限制。
- IPv4数据报的最大大小是65535字节,包括 IPv4首部。
- IPv6数据报的最大大小是65575字节,包括40字节的 IPv6首部。
- 许多网络有一个可由硬件规定的MTU。
IPv4要求的最小链路MTU是68字节。
IPv6要求的最小链路MTU为1280字节。 - 在两个主机之间的路径中最小的MTU称为路径MTU。
1500字节的以太网MTU是当今常见的路径MTU。 - 当一个IP数据报将从某个接口送出时,如果它的大小超过相应链路的MTU, IPv4和 IPv6都将执行分片。
这些片段在到达最终目的地之前通常不会被重组。
IPv4主机对其产生的数据报执行分片, IPv4路由器则对其转发的数据报执行分片。
然而 IPv6只有主机对其产生的数据报执行分片, IPv6路由器不对其转发的数据报执行分片。 - IPv4首部的“不分片”位(即DF位)若被设置,那么不管是发送这些数据报的主机还是转发它们的路由器,都不允许对它们分片。
- IPv4和 IPv6都定义了最小重组缓冲区大小,它是 IPv4或 IPv6的任何实现都必须保证支持的最小数据报大小。其值对于 IPv4为576字节,对于 IPv6为1500字节。
- TCP有一个MSS(最大分节大小),用于向对端TCP通告对端在每个分节中能发送的最大TCP数据量。
MSS的目的是告诉对端其重组缓冲区大小的实际值,从而试图避免分片。 - SCTP基于到对端所有地址发现的最小路径MTU保持一个分片点。这个最小MTU大小用于把较大的用户消息分割成较小的能够以单个IP数据报发送的若干片段。
TCP输出
下图展示了某个应用进程写数据到一个TCP套接字中发生的步骤。
每一个TCP套接字有一个发送缓冲区,可以使用 SO_SNDBUF套接字选项来更改该缓冲区的大小。
当某个应用进程调用 write时,内核从该应用进程的缓冲区中复制所有数据到所写陶家庄的发送缓冲区。
如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区中已有其他数据),该应用进程将被投入睡眠。
这一端的TCP提取套接字发送缓冲区中的数据并把它发送给对端TCP,其过程基于TCP数据传送的所有规则。
对端TCP必须却收到的数据,伴随来自对端的ACK的不断到达,本端TCP至此才能从套接字发送缓冲区中丢弃已确认的数据。
TCP必须为已发送的数据保留一个副本,指定它被对端确认为止。
UDP输出
下图展示了某个应用进程写数据到一个UDP套接字中时发生的步骤。
这一端的UDP简单地给来自用户的数据报安上它的8字节的后备以构成UDP数据报,然后传递给IP。
IPv4或IPv6给UDP数据报安上相应的IP首部以构成IP数据报,执行路由操作确定外出接口,然后或者直接把数据报加入数据链路层输入队列(如果适合于MTU),或者分片后再把每个片段加入数据链路层的输出队列。
如果某个UDP应用进程发送大数据报,那么它们相比TCP应用数据更有可能被分片,因为TCP会把应用数据划分成MSS大小的块,而UDP却没有对等的手段。
SCTP输出
下图展示了某个应用进程写数据到一个SCTP套接字中发生的步骤。
从写一个SCTP套接字的write调用成功返回仅仅表示我们可以重写使用原来的应用进程缓冲区,并不表明对端的SCTP或应用进程已接收到数据。
这一端的SCTP提取套接字发送缓冲区的数据并把它发送给对端SCTP,其过程基于SCTP数据传送的所有规则。
本端的SCTP 必须等待SACK,在累积确认点超过已发送的数据后,才可以从套接字缓冲区中删除该数据。
标准因特网服务
下图列出了TCP/IP多数实现都提供的若干标准服务。
表中所有服务同时使用TCP和UDP提供,并且这两个协议所用的端口号也相同。
这些服务通常由Unix主机的inetd守护进程提供。它们还提供使用标准的Telnet客户程序就能完成的简易测试机制。
注意,当连接到daytime服务器时,服务器执行主动关闭,然而当连接到 echo服务器时,客户执行主动关闭。
常见因特网应用的协议使用
下图总结了各种常见的因特网应用对协议的使用情况。
前两个因特网应用 ping 和 traceroute 是使用 ICMP 协议实现的网络诊断应用。
traceroute 自行构造UDP分组来发送并读取所引发的ICMP应答。
紧接着是3个流行的路由协议,它们展示了路由协议使用的各种传输协议。
OSPF通过原始套接字直接使用IP,RIP使用UDP,BGP使用TCP。
接下来5个是基于UDP的网络应用,然后是7个TCP网络应用和4个同时使用UDP和TCP的网络应用,最后5个是IP电话网络应用,它们或者独自使用SCTP,或者选用UDP、TCP或SCTP。
学习参考资料:
《UNIX网络编程 卷1:套接字联网API》 第3版