前面介绍了IP协议的基础知识,在TCP/IP中,除了IP协议,还有另一重要的组成部分——TCP。下面介绍TCP以及与其密切相关的UDP的相关知识。按照先易后难的原则,首先介绍UDP。
Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(User Datagram Protocol,简称UDP)。UDP为应用程序提供了一种无须建立连接就可以发送封装的IP数据包的方法。
UDP所做的工作也非常简单,除了在数据上增加端口功能和差错检测功能外,直接将数据报交给网络层进行封装和发送即可,如图2-20所示。
图2-20
UDP的主要特点如下。
● UDP是无连接的,即发送数据之前无须建立连接。
● UDP使用尽最大努力交付,既不保证可靠交付,同时也不使用拥塞控制。
● UDP是面向报文的,没有拥塞控制,很适合多媒体通信。
● UDP支持一对一、一对多、多对一和多对多的交互通信。
● UDP的首部开销小,只有8字节。
● 发送方的传输层UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。
● 应用层交给UDP多长的报文,UDP都照样发送,一次发送一个报文。
● 接收方UDP对IP层交上来的UDP用户数据报,在去除首部后就原封不动地交付给上层的应用进程,一次交付一个完整的报文。所以应用程序必须选择合适大小的报文。
因为UDP是面向无连接的、尽最大努力交付的协议,所以UDP的首部也非常简单。用户数据报UDP中,有两个字段:数据字段和首部字段。首部字段共8个字节,分为4个字段,每个字段2字节,格式如图2-21所示。
图2-21
计算校验和时,临时把“伪首部”和UDP用户数据报连接在一起,伪首部的主要作用是计算校验和。
和UDP相比较,TCP主要面向可靠的连接。所谓可靠,就是保证数据没有问题的传输。
传输控制协议(Transmission Control Protocol,TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,是为了在不可靠的互联网络上提供可靠的、端到端的交付而专门设计的一个传输协议。
TCP有以下特点。
● TCP连接是一条虚连接,而不是一条真正的物理连接。
● 每一条TCP连接只能有两个端点,TCP连接只能是点对点的(一对一)。
● TCP对应用进程一次把多长的报文发送到TCP的缓存中是不关心的。
● TCP根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少字节(UDP发送的报文长度是应用进程给出的)。
● TCP可把太长的数据块划分短一些再传送。TCP也可等待积累足够多的字节后再构成报文段发送出去。
TCP报文段的格式如图2-22所示,其含义如下。
图2-22
● 源端口和目的端口字段: 各占2字节。端口是运输层与应用层的服务接口。运输层的复用和分用功能都要通过端口实现。
● 序号: 占4字节。TCP连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值指的是本报文段所发送的数据的第一个字节的序号。
● 确认号: 占4字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。
● 数据偏移: 占4位,指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。“数据偏移”的单位是32位(以4字节为计算单位)。
● 保留字段: 占6位,保留为今后使用,但目前应设置为0。
● 紧急URG: 当URG=1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)。
● 确认ACK: 只有当ACK=1时确认号字段才有效。当ACK=0时,确认号字段无效。
● 推送PSH: 接收TCP收到PSH=1的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满后再向上交付。
● 复位RST: 当RST=1时,表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。
● 同步SYN: 同步SYN=1,表示这是一个连接请求或连接接受报文。
● 终止FIN: 用来释放一个连接。FIN=1表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。
● 窗口: 占2字节,用来让对方设置发送窗口的依据,单位为字节。
● 校验和: 占2字节,校验和字段校验的范围包括首部和数据这两部分。在计算校验和时,要在TCP报文段的前面加上12字节的伪首部。
● 紧急指针字段: 占2字节,指出在本报文段中紧急数据共有多少字节(紧急数据放在本报文段数据的最前面)。
● 选项字段: 长度可变。TCP最初只规定了一种选项,即最大报文段长度(Maximum Segment Size,MSS)。MSS告诉对方TCP:“我的缓存所能接收的报文段的数据字段的最大长度是MSS字节。”MSS是TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。
● 填充字段: 这是为了使整个首部长度是4字节的整数倍。
TCP的连接过程经过三个阶段:建立连接、传输数据以及释放连接。TCP连接采用的是C/S模式,即客户端/服务器模式。主动发起连接的是客户端(Client),被动等待连接的应用进程叫服务器(Server)。常说的三次握手、四次断开,指的就是TCP的连接和断开连接的过程。
TCP建立连接一共分为三个过程,如图2-23所示。
图2-23
首先PC1向PC2发出连接请求报文段,此时首部中的同步位SYN=1,同时选择一个初始序号seq=x。TCP规定,SYN报文段不能携带数据,但要消耗掉一个序号。这时,PC1进入SYN-SENT状态(序号指的是TCP报文段首部20字节里的序号,TCP连接传送的字节流的每一个字节都按顺序编号)。
PC2收到请求后,向PC1发送确认。在确认报文段中把SYN和ACK位都为1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。这时PC2进入SYN-RCVD状态。
PC1收到PC2的确认后,还要向PC2给出确认。确认报文段的ACK为1,确认号ack=y+1,而自己的序号seq=x+1。这时,TCP连接已经建立,PC1进入ESTABLISHED状态,PC2收到PC1的确认后,也会进入ESTABLISHED状态。接下来开始正式传输数据。
因为TCP是面向可靠的连接,所以在数据传输结束后,并不会直接停止,而是按照协议的要求进行协商,安全地关闭连接。TCP连接的释放经过了4个过程,如图2-24所示。
图2-24
PC1的TCP进程先向PC2发出连接释放报文段,并停止发送数据,主动关闭TCP连接。释放连接报文段中FIN=1,序号为seq=u,该序号等于最后一个传输的数据字节的序号加1,PC1进入FIN-WAIT-1(终止等待1)状态,等待PC2的确认。
PC2收到连接释放报文段后即发出确认释放连接的报文段,该报文段中,ACK=1,确认号为ack=u+1,自己的序号为v,该序号等于PC2前面已经传送过的数据的最后一个字节的序号加1。然后PC2进入CLOSE-WAIT(关闭等待)状态,此时TCP服务器进程应该通知上层的应用进程,因而PC1到PC2方向的连接就释放了,这时TCP处于半关闭状态,即PC1已经没有数据要发了,但PC2若发送数据,PC1仍要接收,也就是从PC2到PC1这个方向的连接并没有关闭,这个状态可能会持续一段时间。
PC1收到PC2的确认后,进入了FIN-WAIT-2(终止等待2)状态,等待PC2发出连接释放报文段,如果PC2已经没有要向PC1发送的数据了,其应用进程就通知TCP释放连接。这时PC2发出的链接释放报文段中,FIN=1,确认号还必须重复上次已发送过的确认号,即ack=u+1,序号seq=w,因为在半关闭状态PC2可能又发送了一些数据,因此该序号为半关闭状态发送的数据的最后一个字节的序号加1。这时PC2进入LAST-ACK(最后确认)状态,等待PC1确认。
PC1收到PC2的连接释放请求后,必须对此发出确认。确认报文段中,ACK=1,确认号ack=w+1,而自己的序号seq=u+1,而后进入TIME-WAIT(时间等待)状态。这时TCP连接还没有释放,必须经过时间等待计时器设置的时间2MSL后,PC1才进入CLOSED状态,时间MSL叫作“最长报文寿命”,RFC建议设为2min,因此从PC1进入TIME-WAIT状态后,要经过4min才能进入CLOSED状态,而PC2只要收到了PC1的确认,就进入CLOSED状态。二者都进入CLOSED状态后,连接就完全释放了。
这是为了保证A发送的最后一个ACK报文段能够到达B。防止“已失效的连接请求报文段”出现在本连接中。A在发送完最后一个ACK报文段后,再经过2MSL时间,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
TCP依靠重传机制实现了可靠传输,下面介绍实现的方法。
窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过TCP报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其他信息设置自己的窗口大小。
发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送,并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。
接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为{20,19,40},其中{20}按序到达,而{19,40}没有按序到达,因此只对字节20进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。滑动窗口的特点如下。
● 发送方不必发送一个全窗口大小的数据,一次发送一部分即可。
● 窗口的大小可以减小,但是窗口的右边沿却不能向左移动。
● 接收方在发送一个ACK前不必等待窗口被填满。
● 窗口的大小是相对于确认序号的,收到确认后的窗口的左边沿从确认序号开始。
下面介绍在TCP使用滑动窗口实现可靠传输的步骤。
Step 01 A根据B给出的窗口值构建自己的发送窗口,如图2-25所示。
图2-25
Step 02 A开始传输数据,A的滑动窗口如图2-26所示,此时B的滑动窗口如图2-27所示。
图2-26
图2-27
Step 03 A收到新的确认号,发送窗口向前滑动,如图2-28所示,此时B的状态如图2-29所示,一般会先存下来,等待缺少的数据到达。
图2-28
图2-29
Step 04 如果A的窗口内的序号都发送完毕,但仍然没有收到B的确认,那么必须停止发送,如图2-30所示。
图2-30
发送缓存用来存放发送应用程序传送给发送方TCP准备发送的数据,以及TCP已发送出但尚未收到确认的数据,如图2-31所示。接收缓存用来暂时存放按序到达的、尚未被接收应用程序读取的数据,及不按序到达的数据,如图2-32所示。
图2-31
图2-32
除了保证传输的可靠性外,TCP还可以实现流量控制和拥塞控制。
用户总是希望数据传输得更快一些,但如果发送方把数据发送得过快,接收方就可能来不及接收,这样就会造成数据的丢失。流量控制就是让发送方的发送速率不要太快,既要让接收方来得及接收,也不要使网络发生拥塞。接收方发送的确认报文中的窗口字段可以用来控制发送方的窗口大小,从而影响发送方的发送速率。利用滑动窗口机制可以很方便地在TCP连接上实现流量控制。
如A向B发送数据,在建立TCP连接时,进行协商。B告诉A,接收窗口rwnd为400(字节),如图2-33所示。
图2-33
可以用不同的机制来控制TCP报文段的发送时机:TCP维持一个变量,它等于最大报文段长度。只要缓存中存放的数据达到最大报文段长度,就组装成一个TCP报文段发送出去。由发送方的应用进程指明要求发送报文段,即TCP支持的推送(push)操作。发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过最大报文段)发送出去。
在某段时间内,若对网络中某资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏,从而产生拥塞。若网络中有许多资源同时产生拥塞,网络的性能就要明显变坏,整个网络的吞吐量将随输入负荷的增大而下降。
(1)慢开始和拥塞避免。
发送方维持一个叫作拥塞窗口cwnd的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且在动态地变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接受方的接收能力,发送窗口可能小于拥塞窗口。
慢开始算法的思路是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是由小到大逐渐增加拥塞窗口的大小。这里用报文段的个数的拥塞窗口大小举例说明慢开始算法,实时拥塞窗口大小是以字节为单位的。
(2)快重传和快恢复。
快重传要求接收方在收到一个失序的报文段后立即发出重复确认。快重传算法规定,发送方只要一连收到三个重复确认,就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。