本章面对的两个计算机网基础性问题
1、 两个实体如何才能在一种会丢失或损坏数据的媒体上可靠地通信
2、 如何控制运输层实体的传输速率以避免网络拥塞,或从拥塞中恢复过来
运输层协议是在端系统中而不是在路由器中实现的。因为只有端系统中才有完整的因特网五层协议,路由器只实现了物理层、数据链路层和网络层。
因此,路由器也不会(或者说做不到)检查封装在数据报的运输层报文段的字段。
运输层和网络层的关系
- 运输层(TCP或UDP)为运行在不同主机上的进程之间提供逻辑通信
- 网络层(IP协议)提供了主机之间的逻辑通信
IP协议的服务模型
尽最大努力交付服务。但它并不做任何确保:
1、 不确保报文段的交付
2、 不确保报文段的按序交付
3、 不确保报文段中数据的完整性
所以IP被称为不可靠服务。
UDP和TCP协议的服务模型
将两个端系统间IP的交付服务扩展为运行在端系统上的两个进程之间的交付服务。这种服务被称为运输层的多路复用与多路分解。
UDP和TCP可以通过在其报文段首部中包括差错检查字段而提供完整性检查。进程到进程的数据交付和差错检查是两种最低限度的运输层服务,也是UDP所能提供的仅有的两种服务。
所以,与IP一样,UDP也是一种不可靠的服务,不能保证一个进程所发送的数据能够完整无缺地到达目的进程。
(听起来好像上面两句话有矛盾,提供完整性检查,为什么又说是不可靠的呢?其实不矛盾,完整性检查的对象是一个分组——一个报文段的完整性,而非一个进程发送的所有数据作为一个整体的完整性)
TCP为应用程序提供了几种附加服务
1、提供可靠数据传输。通过使用流量控制、序号、确认和定时器来确保
2、提供拥塞控制
TCP提供更多服务,必然会比UDP复杂。
多路复用和多路分解
考虑一个具体的场景,一台计算机正在运行4个进程:2个Telnet进程、一个FTP进程和一个HTTP进程。这台计算机的运输层从底层的网络层接收数据时,它需要将所接收到的数据定向到这4个进程中的一个。
一个进程会有一个或多个套接字,它相当于从网络向进程传递数据和从进程向网络传递数据的门户。由于在任一时刻,在接收主机上可能有不止一个套接字,所以每个套接字都会有唯一的标识符。(同时,标识符的格式取决于它是UDP还是TCP套接字。)
所以,问题转换成接收主机怎样将一个到达的运输层报文段定向到适当的套接字。为了完成这个任务,每个运输层报文段中会有几个字段。在接收端,运输层检查这些字段,标识出接收套接字,进而将报文段定向到该套接字。
- 多路分解:将运输层报文段中的数据交付到正确的套接字的工作
- 多路复用:在源主机从不同套接字中收集数据块,并为每个数据块封装上首部信息(用于在以后分解)从而生成报文段,然后将报文段传递到网络层的工作
可以看出,分解和复用是两个相反的操作
- 复用像是导游为旅游团登记酒店入住时收集所有游客身份证件,再交给酒店的行为。
- 分解像是导游给酒店完成登记后,重新把身份证件发回各位手中的行为。这个过程需要身份证件与人的名字相貌等资料匹配。
端口号
套接字的标识符其实就是端口号
端口号是一个16比特的数,其大小在0 65535之间。其中,01023范围的端口号是周知端口号,是受限制的,它们保留给诸如HTTP(端口号80)和FTP(端口号21)之类的周知应用层协议来使用。
常用服务及其端口号
当我们开发一个新的应用程序时,必须为其分配一个端口号。
(思考:那么应用程序这么多,端口号是否容易产生冲突呢?如果产生冲突,如何解决?)
不同主机中具有相同端口号的应用程序向同一个服务器进行通信的例子:
主机A向Web服务器B发起一个HTTP会话,主机C向服务器B发起两个HTTP会话。主机A、主机C和服务器B都有自己唯一的IP地址,它们分别是A、C、B。主机C为其两个HTTP连接分配了两个不同的源端口号(26145和7532)。主机A选择源端口号时与主机C是互不相干的,因此它也可以将源端口号26145分配给其HTTP连接。
这没有任何问题,服务器B仍然能够正确地分解这两个具有相同源端口号的连接,因为这两条连接有不同的源IP地址。
UDP的多路复用和多路分解
如果应用程序开发者所编写的代码实现的是一个“周知协议”的服务器端,那么开发者就必须为其分配一个相应地周知端口号。通常,应用程序的客户端让运输层自动地(并且是透明地)分配端口号,而服务器端则分配一个特定的端口号。
一个UDP套接字由一个二元组来全面标识,该二元组包含一个目的IP地址和一个目的端口号。
因此,如果两个UDP报文段有不同的源IP地址和/或源端口号,但具有相同的目的IP地址和目的端口号,那么这两个报文段将通过相同的目的套接字被定向到相同的目的进程。
TCP的多路复用和多路分解
一个TCP套接字由一个四元组来标识。包含源IP地址,源端口号,目的IP地址,目的端口号。
因此,当一个TCP报文段从网络到达一台主机时,该主机使用全部4个值来将报文段定向(分解)到相应地套接字。
特别与UDP不同的是,具有不同源IP地址或源端口号,但具有相同目的IP地址或目的端口号的两个到达TCP报文段将被定向到两个不同的套接字;除非TCP报文段携带了初始创建连接的请求。
- 首先解释分号前一句。因为TCP是面向连接的,需要经过“三次握手”建立连接。服务器每收到一个客户进程的连接请求,都要建立一个新的连接——包括创建一个新的服务器进程与客户进程通信,与一个相应的套接字。
更具体地说,客户进程1与服务器的TCP连接,客户进程2与服务器的TCP连接是不同的两个TCP连接。客户进程1会与服务器进程1进行TCP连接,客户进程2会与服务器进程2进行TCP连接,这2个服务器进程也需要有2个不同的套接字。有n个客户进程就至少需要n个套接字。所以源不同,就会被定向到不同的套接字。 - 分号后一句。服务器进程在TCP连接中,会有一个“欢迎套接字”用于建立与客户进程之间的TCP连接。这个“欢迎套接字”是唯一的,仅在“三次握手”时会收到报文。所以如果当两个客户进程同时发出请求与服务器进程建立连接的报文段,它们会被定向到同一个套接字,即“欢迎套接字”。
事实上,当今高性能的Web服务器通常只使用一个进程,但是会为每个新的客户连接创建一个具有新连接套接字的新线程。
选择使用UDP的原因
1、 关于何时、发送什么数据的应用层控制更为精细
2、无需连接建立
3、无连接状态
4、分组首部开销小
流行的因特网应用及其所使用的运输层协议
虽然UDP无法实现可靠数据传输,但要说明的一点是,使用UDP的应用也可以实现可靠数据传输。这可以在应用程序自身中建立可靠性机制来完成(如增加确认与重传机制)。但这不容易,它会使应用开发人员长时间忙于调试。
UDP报文段结构
简要说一下检验和:假设数据有3个16比特的字,即3个16比特的数字相加,(有溢出时,就回卷。回卷就是把溢出的高位部分,加到最低位。)最后求反码,就得到了检验和。
在接收方,我们再度把着3个16比特的数字跟检验和相加,如果得到全1的数字,证明在传输过程没有差错。反之,若有某些位为0,说明有差错。
UDP提供检验和进行差错检测的原因是不能保证源和目的之间的所有链路都提供差错检测。
但是,虽然UDP提供差错检测,但它对差错恢复无能为力。UDP的某种实现只是丢弃受损的报文段;其他实现是将受损的报文段交给应用程序并给出警告。
构造可靠的数据传输协议
version1: rdt1.0 (假设情况:经完全可靠信道的可靠数据传输)
这个版本的传输协议几乎不用做任何事情,因为下层已经是可靠的。
version2: rdt2.0(假设情况:经具有比特差错信道的可靠数据传输)
增加自动重传请求(Automatic Repeat reQuest, ARQ)协议。接收方会在报文中添加肯定确认(ACK)和否定确认(NAK)标识来识别哪些被正确接收,哪些因为有比特差错要重新传送。
需要使用到ARQ协议的三种功能
1、 差错检测(在报文段中添加额外的比特,如使用像UDP中的检验和等机制)
2、 接收方反馈(发送回复报文“ACK”和NAK)
3、 重传(发送方重传有差错的分组)
version3: rdt2.1(假设情况:同上,且考虑ACK和NAK报文也会出现比特差错的情况)
为数据分组添加一个新字段,让发送方对其数据分组编号,即将发送数据分组的序号放在该字段。接收方只需要检查序号即可确定收到的分组是否是一次重传。
现在的情况是这样的,当接收到正确的分组时,接收方对所接收的分组发送一个ACK;如果收到受损的分组,则接收方将发送一个NAK。
这样好像很完美很合理了。但是细想,我们实际上只用ACK就够了。
因为每个分组都有序号,如果发送方发送了一个分组0,接收方收到正确的分组,发送ACK0给发送方,发送方就知道发送成功了。如果发送方发送下一个分组1,接收方收到受损的分组,还是发送ACK0给发送方(因为之前已经接收过ACK0,所以这个ACK0是冗余ACK),发送方就知道分组受损了,准备重传。所以只需要ACK就能完成这个任务。rdt2.2就是对此进行了优化,只保留ACK标识。
version4: rdt2.2(假设情况:同上,且考虑ACK和NAK报文也会出现比特差错的情况)
在有比特差错信道上实现一个无NAK的可靠数据传输协议,与rdt2.1的差异也在上一段描述了。
final version: rdt3.0(假设情况:经具有比特差错的丢包信道的可靠数据传输)
比特差错的问题已经在rdt2.2中得到解决。rdt3.0主要关注丢包问题。
如何判断一个分组已经丢失?显然发送方至少需要等待这样长的时间:发送方与接收方之间的一个往返时延(可能还包括中间路由器的缓冲时延)加上接收方处理一个分组所需的时间。
当然最坏情况下的最大时延很难估算,假设我们可以明智地选择一个恰当的时间值,以判定可能发生了丢包(尽管不能确保)。如果在这个时间内没有收到ACK,则重传该分组。所以我们也会预想到这么一种情况,就是一个分组可能因为网络拥塞等因素经历了一个很大的时延,发送方还是可能会重传该分组,即使这个数据分组及其ACK都没有丢失。这会引入冗余数据分组,但是我们在rdt2.2中已经可以解决冗余ACK,所以解决冗余数据分组也不是问题了(即已经在rdt2.2中有相关功能来处理冗余数据分组)。
对发送方来说,重传是“万能药”。无论是一个数据分组丢失,还是一个ACK丢失,或者只是该分组或ACK过度延时。都会发送重传。
重传机制是基于时间的,所以需要一个倒计数定时器。发送方需要做到
1、 每次发送一个分组(包括第一次分组和重传分组)时,便启动一个定时器。
2、 响应定时器中断(采取适当的动作)
3、 终止定时器
可靠数据传输要点
1、 检验和
2、 序号
3、 定时器
4、 肯定和否定确认分组
对rdt3.0的优化
虽然rdt3.0是一个功能正确的协议,但它的性能无法让人十分满意。这个性能问题的核心在于它是一个停等(stop-and-wait)协议。它造成了发送方信道极低的利用率。因为每个分组要排队发送,发送方把第一个分组发送到达接收方,并且还要收到接收方的ACK后,才能继续发送第二个分组。
解决办法自然就是不用停等方式运行,允许发送方发送多个分组而无需等待确认。这种技术称为流水线技术。
流水线技术需要解决的问题
- 必须增加序号范围,因为每个输送中的分组(不计算重传的)必须有一个唯一的序号,而且也许有多个在输送中未确认的报文。
- 协议的发送方和接收方两端也许必须缓存多个分组。发送方最低限度应当能春冲那些已发送但没有确认的分组。接收方或许也需要缓存那些已正确接收的分组。
- 所需序号范围和对缓冲的要求取决于数据传输协议如何处理丢失、损坏及延时过大的分组。解决流水线的差错恢复两种基本方法:回退N步和选择重传
回退N步协议(又称滑动窗口协议)
这个协议中,发送方分组被分成四个类型
1、已发送并已被确认的分组
2、已发送但还未确认的分组
3、可用,但还未发送的分组
4、不可用的分组。
其中第二种和第三种分组在滑动窗口内。
回退N步发送方必须响应的三种类型的事件
1、上层的调用
2、收到一个ACK
3、超时事件
回退N步会造成单个分组出错就引起重传大量分组的问题,但许多分组其实没有必要重传,所以可以使用“选择重传”。
选择重传
这个协议中,发送方分组也是上面的四个类型,不同的是第一种分组(已发送并已被确认的分组)也会出现在滑动窗口内。
接收方的滑动窗口也有三种类型的分组
1、失序(已缓存),但未被确认的分组
2、可接受的分组
3、期待,还未收到的分组
可靠数据传输机制及其用途的总结
TCP
TCP连接总是点对点的,即在单个发送方与单个接收方之间的连接。所谓“多播”,即在一次发送操作中,从一个发送方将数据传送给多个接收方,对TCP来说这是不可能的。对TCP而言,两台主机是一对的。
TCP的“三次握手”中,前两次的报文段不承载“有效载荷”,也就是不包含应用层数据,第三次报文段可以承载有效载荷。
TCP连接的组成包括:一台主机上的缓存、变量和与进程连接的套接字,以及另一台主机上的另一组缓存、变量和与进程连接的套接字。在这两台主机之间的网络元素(路由器、交换机和中继器)中,没有为该连接分配任何缓存和变量。
TCP报文段结构
- 源端口号和目的端口号:用于多路复用/分解
- 序号和确认号:被TCP发送方和接收方用来实现可靠数据传输服务
- 接收窗口:用于流量控制。指示接收方愿意接受的字节数量。
- 首部长度:由于TCP选项字段,TCP首部的长度是可变的。(通常,选项字段为空,所以TCP首部的典型长度就是20字节)
- 选项:用于发送方和接收方协商最大报文段长度(MSS)时,或在高速网络环境下用作窗口调节因子时使用
- 6比特的标志字段:ACK(成功接受报文段的确认)、RST-SYN-FIN(用于TCP连接的建立和拆除)、PSH(接收方应立即把数据交给上层)、URG(略)
流量控制
一条TCP连接每一侧主机都为该连接设置了接收缓存。当该TCP连接收到正确、按序的字节后,它就将数据放入接收缓存。
相关联的应用进程会从该缓存中读取数据,但不必是数据刚一到达就立即读取。事实上,接收方应用也许正忙于其他任务,甚至要过很长时间后才去读取该数据。如果某应用程序读取数据时相对缓慢,而发送方发送得太多、太快,发送的数据就会很容易使该连接的接收缓存溢出。
所以TCP为它的应用程序提供了流量控制服务以消除发送方使接收方缓存溢出的可能性。流量控制是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读取速率相匹配。
TCP通过让发送方维护一个称为接收窗口的变量来提供流量控制。通俗地讲,接收窗口用于给发送方一个指示——该接收方还有多少可用的缓存空间。因为TCP是全双工通信,在连接两端的发送方都各自维护一个接收窗口。
SYN洪泛攻击
这种攻击针对的是TCP的“三次握手”。
在正常的三次握手中,服务器为了响应一个收到的SYN,分配并初始化连接变量和缓存。然后服务器发送一个SYN ACK进行响应,并等待来自客户的ACK报文段。如果某客户不发送ACK来完成该三次握手的第三步,最终(通常在一分多钟之后)服务器将终止该半开连接并回收资源。
在SYN洪泛攻击中,攻击者发送大量的TCP SYN报文段,而不完成第三次握手的步骤。随着这种SYN报文段纷至沓来,服务器不断为这些半开连接分配资源(但从未使用),导致服务器的连接资源被消耗殆尽。
有效应对SYN洪泛攻击的防御系统称为SYN cookie。
TCP拥塞控制
TCP采用的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率。如果一个TTCP发送方感知从它到目的地之间的路径上没有什么拥塞,则TCP发送方增加其发送速率;如果发送方感知沿着该路径有拥塞,则发送方就会降低其发送速率。
TCP连接的每一端都是由一个接收缓存、一个发送缓存和几个变量组成。运行在发送方的TCP拥塞控制机制跟踪一个额外的变量,即拥塞窗口。通过调节拥塞窗口的大小,可以调整发送方向连接发送数据的速率。
TCP拥塞控制算法
三个部分
1、慢启动(拥塞窗口在一开始以指数级增长,直至丢包——拥塞)
2、拥塞避免(出现拥塞时,拥塞窗口变为拥塞时的一半,并改为线性增长)
3、快速恢复