概述
运输层位于应用层和网络层之间,主要协议包括TCP和UDP(面向有连接和无连接)。
3.1 概述
运输层协议为不同主机上的应用进程间提供了逻辑通信(logic communication),在应用程序的角度来看,逻辑通信让运行不同进程的主机好像直连一样;这也就将主机间通过无数的链路、路由器相连接这一事实封装了起来,应用进程只需在运输层提供的逻辑通信功能上发送报文,而不用考虑实际上底层物理复杂链路。运输层把应用程序进程接收到的报文转换成运输层分组,又称为报文段(segment),在发送时传递给网络层,网络层将其封装为网络层分组并向目的地发送。运输层和网络层的区别在于,运输层提供的是进程间的逻辑通信,要与具体应用绑定(前文提到过有的应用采取TCP,有的采取UDP),而网络层提供的是主机间的通信(host to host)。
3.1.1 因特网中的运输层
因特网(TCP/IP网络)为应用层提供了两种运输层协议,大名鼎鼎的TCP(传输控制协议)和UDP(用户数据报协议),前者是可靠的,面向连接的,而后者反之。在详细介绍这两种协议之前,要先介绍一下网络层,网络层协议有一个名字叫IP,也就是网际协议。IP为主机间提供了逻辑通信,其服务模型为尽力而为交付服务(best-effort delivery service)。这意味着IP尽它最大的努力在通信主机间交付报文段,但它并不做保证,意思是:它不确保报文段的交付、不保证报文段的按序交付、不保证报文段中数据的完整性。因此,IP又被称作是不可靠服务。每台主机至少有一个网络层地址,也就是被人熟知的IP地址。
在上述的基础上,UDP和TCP的责任是将端系统间IP的交付服务扩展为运行在端系统上的两个进程间的交付服务。将主机间交付扩展到进程间交付被称为运输层的多路复用(transport-layer multiplexing)和多路分解(demultiplexing)。UDP和TCP还能够在报文段首部中通过差错检查字段提供完整性检查,也就是说进程间的数据交付和差错检查是运输层的两个最低限度服务。此外,TCP提供了几种附加服务,比如为人熟知的可靠数据传输(reliable data transfer)、拥塞控制(congestion control),防止任何一条TCP连接用过多流量来淹没通信主机间的链路和交换设备,力求为每个通过一条拥塞网络链路的连接平等地共享网络链路带宽。相反,UDP流量不可调节,应用程序可以以其想要的任意速率发送数据。
3.2 多路复用和多路分解
上文中提到,运输层将网络层的主机间交付扩展到了应用程序的进程间交付,这一过程为多路复用和多路分解。在目的主机,运输层从紧邻其下的网络层接收报文段,运输层负责将报文段中的数据交付给主机上适当的进程。一个进程有一个或多个套接字(socket),它是从网络向进程传递数据或是向网络传递数据的门户。如图一所示,运输层其实并不是将数据直接交给某个进程,而是交付给了一个套接字,每个套接字有唯一的标识符。
运输层是如何将报文段送往适当的套接字的?针对这点,运输层报文段中有几个字段,在接收端,运输层检查这些字段,标识出接受套接字,进而定位到正确的套接字。将报文段中的数据交付到正确的套接字的工作就称为多路分解。相反的,源主机从不同套接字收集数据,为数据添加首部信息从而生成报文段,然后将报文段送往网络层,这些工作称为多路复用。
为了将数据送往指定的套接字,运输层要求套接字有唯一标识符、每个报文段有特殊字段来指示报文要交付给的套接字。这些特殊字段姐是源端口号字段和目的端口号字段,端口号是一个16bit的数,大小在0~65535之间,0~1023范围的端口号为周知端口号(well-known port number),它们是保留给一些已经存在的有名的应用的端口,(如之前介绍的HTTP 80端口)。简单来看,目前为止多路复用和多路分解的过程是:源主机将数据封装到报文段并在其中指定目的端口号,目的主机的运输层接到报文段并通过端口号将数据派发给对应的套接字。
3.2.1 无连接的多路复用与多路分解
UDP中的多路复用和分解比较简单,和上述的一般流程近似,需要注意的是:一个UDP套接字是由一个二元组来标识的,包含一个IP地址和一个端口号。到目前为止源端口号貌似还没有起到作用,实际上它在需要返回值的传输过程中是有用的。比如:A主机给B主机发送了数据,当B主机要返回给A主机一些信息(如确认信息等)时,需要找到A的进程套接字,这时源端口号就起到了作用。
3.2.2 有连接的多路复用与多路分解
TCP套接字和UDP套接字的不同处在于TCP套接字由四元组(源IP地址,源端口号,目的IP地址,目的端口号)来标识的,另一点不同处在于两个具有不同源IP地址或源端口号的到达的TCP报文段,就算目的端口号相同也会被定向到不同的套接字,除非TCP报文段携带了初始创建链接的请求。服务器可以支持很多并行的TCP套接字,每个套接字与一个进程联系,并由四元组标识。
总结一下,在目的IP相同时,UDP报文段在具有相同的目的端口号时就共享一个套接字,而TCP要在源IP、源端口、目的端口都相同的情况下报文段才会共享一个套接字。但并非每个套接字都和进程有一一对应的关系,当下线程和套接字的连接更加密切,许多套接字都可以连接到相同的进程,而实际上负责工作的是线程,这提升了工作的效率。
3.3 UDP
UDP做了运输协议能做的最少工作,几乎只在IP的基础上扩展了多路复用和多路分解,以及少量的差错检测。正如应用层中提到的,DNS是一个使用UDP的应用层协议。通常来说,使用UDP的应用程序是出于以下几点考虑:
- 关于何时、发送什么数据的应用层控制更为精细: 采用UDP时,只要应用程序将数据给UDP,UDP就会将数据打包、送给网络层。而TCP不同,它有拥塞控制机制,而使得进程往往不能在第一时间发出数据。
- 无需连接建立 :TCP在数据传输之前需要三次握手以建立连接,从而消耗一部分时间。而UDP不会引入这方面的时延,因此不需要连接建立的应用程序出于速度考虑会选择UDP
- 无连接状态: TCP需要在端系统中维护连接状态、追踪一系列参数,因此当应用程序运行在UDP上时能支持更多活跃客户。
- 分组首部开销小:TCP报文段有20字节首部,而UDP是8字节。
3.3.1 UDP报文结构
UDP报文结构如下图所示,应用层数据(报文)在最末尾的数据字段,UDP长度指出了UDP报文段的字节数(首部加数据),检验和由接收方使用,用来检验是否发生了差错。
UDP检验和是如何提供差错检测功能的呢?首先,发送方的UDP对报文段中所有16位比特字的和进行反码运算,求和时遇到的所有溢出都被回卷(溢出的加到尾部)。得到的结果放在检验和字段中。 在接收方把所有4哥16位比特加在一起(包括检验和),根据之前的计算,该结果应该是1111111111111111,这16位但凡有一位是0,说明分组已经出错。但是UDP提供的服务仅限于此,它只能发出警告称分组出错,但对恢复工作无能为力。
3.4 面向连接传输:TCP
3.4.1 TCP连接
TCP是面向连接的(connection-oriented),这体现在两个应用进程发送数据之前必须相互“握手”,它提供“全双工服务(full-duplex service)”:进程间的TCP连接能够支持数据双向流动,既可以从A进程流向B进程,也可以从B到A。TCP连接是点对点的,是单个接收方和单个发送方间的连接。关于三次握手,简单地说其过程是:客户首先发送一个特殊的TCP报文段,服务器用另一个特殊的TCP报文段响应,最后客户再用第三个特殊报文段作为响应。前两个报文段不承载有效数据,而第三个可以承载。
在建立起连接之后,应用程序间开始互发数据。客户进程首先将数据送出套接字,接下来数据被TCP接管,首先引导至连接的“发送缓存(send buffer)”里,接下来TCP会时不时从发送缓存中取出一块数据并发送。TCP可从缓存中取出并放入报文段的数据数量受限于最大报文段长度(maximum segment size,MSS)MSS通常根据最初确定的由本地发送主机发送的最大链路层帧长度来设置,从而使MSS保证一个TCP报文段加上TCP/IP首部长度适合单个链路层帧。注意:MSS指报文段里应用层数据的最大长度,而不是包括TCP首部的TCP报文段最大长度。
TCP给每块客户数据配上一个TCP首部,从而形成TCP报文段。该报文段送入网络层封装在IP数据报里,并被送往网络,接收端进程将该报文段数据放入TCP连接的接收缓存里。
3.4.2 TCP报文段结构
TCP报文段也由首部字段和数据字段组成,且MSS限制了数据字段的最大长度。当TCP传输一个大文件时,它通常将文件划分为长度为MSS的若干块。首部部分,与UDP一样,TCP也有源端口号和目的端口号和检验和字段。此外,还包括:
- 32位的符号字段(sequence number field)和32位的确认号字段(acknowledgment number field),这些字段被用于实现可靠数据传输服务。
- 16位的接收窗口字段(receive window field),该字段用于流量控制,指示接收方愿意接受的字节数量。
- 4位的首部长度字段(header length field),指示了以32位的字为单位的TCP首部长度。因为选项字段长度不定,所以TCP首部的长度是可变的(通常选项字段为空而TCP首部典型长度就是20字节)。
- 可选且变长的选项字段。
- 6位标志字段(flag field),URG/ACK/PSH/RST/SYN/FIN分别都是一位,其中ACK用于指示确认字段中的值是有效的,即该报文段包括一个对已被成功接收的报文段的确认。RST、SYN、FIN位用于连接建立和拆除。PSH位被设置时意味着接收方应立即将数据交往上层,最后URG位用来指示报文段中存在被发送端的上层实体置为“紧急”的数据。
接下来详细地对TCP的各部分进行展开
3.4.3 序号和确认号
序号和确认号是首部中两个最重要的字段。TCP把数据看成无结构、有序的字节流,序号是建立在传送的字节流上而不是报文段的序列上,一个报文段的序号是该报文段首字节的字节流编号。假设一个数据流由500000字节组成,MSS是1000字节,那么TCP将把该数据流分为500段,第一个报文段序号为0,第二个是1000,第三个是2000......
TCP是全双工的,因此主机A在给主机B发送数据的同时也可能接收来自B的数据,主机A填充进报文段的确认号就是主机A期望从主机B收到的下一字节的序号。假设主机A收到了来自主机B的编号为0~535字节,同时它要给主机B发送一个报文段,那么该报文段的确认号字段就会是536.
另一个例子,假如主机A收到了主机B发送的包含0~535字节的报文段,以及另一个包含900~1000的报文段,那么从A到B的报文段中的确认号将是536,因为TCP确认流中至第一个丢失字节为止的字节,TCP又被称为提供累积确认(cumulative acknowledgement)
在上述的例子中,初始字节序号都是0,但事实上TCP连接的双方都可以随机选择初始序号。
3.4.4 往返时间估计与超时
- 估计往返时间
TCP采用超时重传机制,首先,要对发送方和接收方间的往返时间进行估计,通过报文段的样本RTT(SampleRTT),即某报文段从被发出到对该报文段的确认被收到间的时间量。TCP大多数情况不为每个发送的报文段都测量一个SampleRTT,而是仅在某一时刻做一次测量。
由于路由器的拥塞和端系统负载的变化,SampleRTT很容易随之波动。因此要从数学方法上对RTT进行平均化,TCP维持一个均值(EstimatedRTT),一旦获取到一个新的SampleRTT,TCP根据以下公式来更新该值: EstimatedRTT = (1-α)·EstimatedRTT+α·SampleRTT。在RFC 6298中给出的α值为0.125.
除此之外,RFC 6298还规定了RTT偏差值DevRTT,估算公式为:DevRTT = (1-β)·DevRTT + β·|SampleRTT - EstimatedRtt|,其中β的推荐值为0.25
- 设置和管理重传超时间隔
在有了估计RTT值和偏差值之后,TCP可以真正设定超时间隔了,该值很明显应该大于EstimatedRTT,这样才能避免不必要的重传,但又不能大太多,否则报文段丢失时需要等好久才能重新传输。因此超时间隔被设为EstimatedRTT加上一定余量,当SampleRTT值波动较大时,余量也稍大,反之就小一点。最终,真正的超时间隔计算公式为:TimeoutInterval = EstimatedRTT + 4·DevRTT,推荐的TimeoutInterval初始量为1s。
3.4.5 可靠数据传输
TCP在IP提供的不可靠但尽力的服务之上建立了可靠数据传输服务,确保一个进程从其接收缓存中拿到的数据流是无损坏、无间隔、非冗余且按顺序达到的。TCP发送方在最简情况下可概况为3个主要事件:从上层应用程序接收数据、定时器超时和收到ACK。流程为:从应用程序接收数据并封装在报文段中,把其交给IP,启动定时器(过期时间为TimeoutInterval),当超时时重传并重启定时器,收到接收方的ACK时,将其值与SendBase比较(SendBase是最早未被确认的字节序号,因此SendBase-1是指接收方已正确按序接收到的数据的最后一个字节的序号。)前面提过TCP采用的是累积确认,所以若ACK的值>SendBase,则该ACK是在确认一个或多个先前未被确认的报文段,发送方要更新其SendBase变量;若当前有未被确认的报文段,TCP要重启定时器。
上述模型是最简版本的TCP实现,但是实际过程中有一些细节上的变化:
- 超时间隔加倍
每当有超时事件发生时,TCP重传具有最小序号的还未被确认的报文段,然后大多数TCP实现会将下一次的超时间隔设为先前值的两倍,而不是用从EstimatedRTT和DevRTT推算出的值。然而当定时器在另两个事件启动时(收到上层应用的数据和收到ACK),超时间隔则又会变成从最新的公式中计算得到的值。
- 快速重传
超时间隔加倍会一定程度上提供拥塞控制(持续重传分组会使拥塞更加严重),但也会造成超时周期长的问题(超时间隔指数上升)。发送方通常可在超时事件发生之前通过注意冗余ACK来检测丢包情况,冗余ACK就是再次确认某个报文段的ACK,而发送方先前已经收到过对该报文段的确认。 因为TCP不使用显示否定确认来告诉发送方数据丢失,因此当接收方收到了一个失序数据(序号>期望序号),则向发送方发送一个ACK,该ACK是针对最后一个按序字节的,也就是告诉发送方数据已经失序。
因为发送方经常一个接一个地发送大量报文段,一旦一个报文段丢失就可能造成许多冗余ACK的产生。如果TCP发送方接收到对相同数据的3个冗余ACK,它就把这当成一种指示,说明跟在这个已经确认过3次的报文段之后的报文段已经丢失。一旦收到3个冗余ACK,TCP执行快速重传(fast retransmit),即在该报文段的定时器过期之前重传丢失的报文段。
3.4.6 流量控制
TCP连接的两侧主机都为该连接设置了接收缓存,当该连接收到正确、按序的字节后,它就将数据放入接收缓存。有时接收方会忙于其它事务而隔很长时间才去拿数据,如果不管制的话这会导致接收缓存填满、溢出。
TCP为应用程序提供了流量控制服务(flow-control service)以消除缓存溢出的可能性,它是一个速度匹配服务,即让发送方的发送速率和接收方的读取速率匹配。TCP让发送方维护一个称为接收窗口(receive window)的变量,接收窗口用于给发送方指示接收方还有多少的缓存空间。因为TCP是全双工通信,所以连接两端都会维护一个发送窗口。假设主机A通过TCP连接向主机B发送一个大文件,主机B为该连接分配了一个接收缓存,并用RcvBuffer来表示其大小。定义以下变量:
- LastByteRead:主机B上的应用进程从缓存读出的数据流的最后一个字节的编号
- LastByteRcvd:从网络中到达的并且已放入主机B接收缓存中的数据流的最后一个字节的编号
为了不让接收缓存溢出,则有不等式必须成立:LastByteRcvd-LastByteRead≤RcvBuffer。接收窗口用rwnd表示,根据缓存可用空间的数量来设置:rwnd=RcvBuffer-[LastByteRcvd-LastByteRead]
由于该空间是随时间变化的,rwnd也是动态的。
规定好了接收窗口的值,下面就要对其进行使用了。 主机B通过把rwnd值放入它发给主机A的报文段接收窗口字段中,通知主机A它在该连接的接收缓存中还有多少可用空间。rwnd初始化为RcvBuffer。主机A轮流跟踪两个变量LastByteSent和LastByteAcked,这两个变量的差值就是主机A发送到连接中但未被确认的数据量,只要保证该值在rwnd以内,就可以确保主机A不会使主机B的接收缓存溢出。则有下式:
LastByteSent-LastByteAcked≤rwnd
方案中有一个小问题是当rwnd=0时,若主机B没有任何数据要发给A,那么B的缓存即使有空间A也不会知道,它会一直阻塞下去,因此TCP规范要求当B的接收窗口=0时,主机A继续发送只有一个字节数据的报文段,报文段最终会被确认并返回一个非0的rwnd值。
3.4.7 TCP连接管理
当客户想与服务器建立起TCP连接时,往往要经过如下的过程:
- 客户端TCP首先向服务器端发送一个特殊的TCP报文段,报文段首部的SYN位置于1,该报文段又因此被称为SYN报文段。客户会选择一个随机的初始序号(client_isn),并将其放置于该起始的SYN报文段的序号字段中。
- SYN报文段抵达服务器,服务器会将其提取出来,为该TCP连接分配缓存和变量,并向该TCP发送允许连接的报文段。该报文段的SYN位也是1,确认号字段被置为client_isn+1,代表是对客户端发来的请求的确认,最后服务器还会选择自己的初始序号server_isn,并将其放在TCP报文段首部的序号字段中。该报文段又被称为SYNACK报文段。
- 收到SYNACK报文段后,客户端也要给该连接分配TCP缓存和变量,并给服务器发送一个报文段来对SYNACK进行确认,由此可见该报文段的确认号应该是server_isn+1,这阶段已经可以开始传输数据,同时因为连接已经建立,所以SYN位置于0.
当数据发送结束,一端想终止TCP连接时,它首先向另一端发出特殊报文段,该报文段的FIN指示位为1,服务器收到这个报文段后回复一个确认报文段并再传送一个自己的终止报文段,同样FIN置为1,当客户端收到该报文段后回复一个确认,连接便被关闭,所有资源被释放。 注意在发送FIN报文段的同时,会启动一个定时器防止报文丢失的情况,一旦定时器超时,同样连接会被终止。
- SYN洪范攻击 : TCP的三次握手中服务器为了响应一个收到的SYN要返回一个SYNACK报文段,然后等待回复的ACK,若客户不发送最终的ACK,服务器会保持半开放状态等待,并在等待一分多钟后终止该连接并回收资源(若持续没收到ACK的话)。然而这种机制提供了攻击的可能性,即SYN洪范攻击,也是最经典的DoS攻击。攻击者发送大量的TCPSYN报文段但不回复最终的ACK,这样服务器会持续打开半开放连接并最终耗尽资源。为抵御这种攻击,一种防御机制SYN cookie应运而生,当服务器接收到一个SYN报文段时,它不给该报文段生成一个半开放的连接,而是生成一个初始TCP序列号,该序列号是SYN报文段的源、目的IP地址与端口号以及仅有该服务器知道的一种散列函数。这种初始序列号就是cookie,服务器发送具有这种cookie的synack分组,服务器不记忆关于cookie或syn的任何状态信息。若客户是合法的,它将返回一个ACK字段,服务器将使用SYNACK报文段中的源、目的IP和端口号以及散列函数验证其合法性并开放连接。
3.4.8 拥塞控制
网络拥塞是指用户对网络资源的需求超出了网络能够提供的最大程度,这时网络传输性能就会下降。拥塞控制的方法分为两种,一种是有网络层支持的拥塞控制,而另一种没有。TCP采取的是没有网络层支持的拥塞控制。
- TCP中的拥塞控制:TCP采取的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率。这种方法有三点需求:1.TCP发送方如何限制其发送速率;2.TCP发送方如何感知拥塞;3.发送方感受到拥塞后用何种方法控制发送速率。
目前已经知道,TCP连接的两端都有接收、发送缓存和一些变量,其中大多数变量之前已经接触过,这里要引入的就是另一个变量:拥塞窗口(congestion window),表示为cwnd,它对一个TCP发送方能向网络发送的速率进行限制,列式为LastByteSent-LastByteAcked≤min{cwnd,rwnd},即一个发送方中未被确认的数据量不会超过cwnd与rwnd的最小值。通过调整cwnd,能够发送的数据量就会相应的变化,从而影响发送速率。至于发送方如何知道网络发生拥塞,当出现过度拥塞时,网络路径上的一台路由器的缓存会溢出,就像水管堵住一样,导致丢包(发送方超时或收到3个冗余ACK),发送方就认为该路径上出现了拥塞。
除此之外,拥塞控制还存在一些问题,如:如何设置cwnd初始值,以什么幅度对其增减...往往TCP会遵循以下原则:
- 一个丢失的报文段意味着拥塞,因此当丢失报文段时应降低TCP发送方的速率。
- 一个确认报文段指示该网络正在向接收方交付发送方的报文段,因此当对先前未确认的报文段确认到达时可以增加发送方的速率。这很好理解,确认的到达说明现在的网络畅通,可以增加一些速率以更高效率地利用带宽。
- 带宽检测:当ACK发生或丢包发生,发送方增加/减少一点速率,当网络中不出现阻塞时停止减少速率。
接下来,TCP提出了拥塞控制算法,该算法包括三个主要部分:慢启动、拥塞避免和快速恢复,其中前两个是强制实现的部分,而最后一个属于锦上添花的推荐部分,对于TCP发送方来说是非必须的。
- 慢启动: 当一条TCP连接开启时,cwnd的值通常设置为一个MSS的较小值,而初始发送速率便是MSS/RTT。大多数发送方的可用带宽都会大于这个值,因此TCP发送方希望尽快找到可用带宽的数量,于是有了慢启动状态。在慢启动状态时,cwnd以1个MSS开始并且每当传输的报文段首次被确认就增加一个MSS。这一个过程每过一个RTT,发送速率就翻番,因此TCP发送速率起始比较慢,但在慢启动阶段以指数增长。这种增长总会停止,慢启动规定一旦遇到由超时指示的丢包(即拥塞发生),TCP发送方就将cwnd重设为1,并重新开始慢启动。每当拥塞发生时它还会将状态变量ssthresh(慢启动阈值)设为当时拥塞窗口的一半(cwnd/2),这也引出了第二种慢启动结束的方式:当cwnd的值等于ssthresh时,结束慢启动并更为谨慎地增加cwnd(进入拥塞避免模式)。当然最后一种结束慢启动的情况是接收到3个冗余ACK,TCP这时执行一种快速重传并进入快速恢复状态。
- 拥塞避免:在慢启动中,cwnd增长到ssthresh时就会减缓增长的速度进入拥塞避免的状态。同样,拥塞避免也有其结束的时刻,当出现超时时,cwnd的值被设为1个MSS。值得注意的是与慢启动不同,收到3个冗余ACK时的丢包事件对拥塞避免的影响没有那么剧烈,它只会让cwnd的值减半,并且当收到3个冗余ACK时将ssthresh的值设为当前cwnd的一半,接下来进入快速恢复状态。
- 快速恢复:当丢包发生时,ssthresh被设为cwnd的一半,而cwnd被设为ssthresh+3(TCP reno的方法)。这样能使速率更快地返回一个较高的值。
最后
以上就是典雅招牌为你收集整理的计算机网络笔记(三):运输层3.1 概述3.2 多路复用和多路分解3.3 UDP3.4 面向连接传输:TCP的全部内容,希望文章能够帮你解决计算机网络笔记(三):运输层3.1 概述3.2 多路复用和多路分解3.3 UDP3.4 面向连接传输:TCP所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复