传输层的重要功能是为运行在不一样的主机上的进程提供通讯服务,事实上这是基于网络层为不一样的主机提供通讯服务的基础之上实现的。缓存
同时,某些传输层协议,例如TCP,则致力于解决在易于丢失和损坏数据的传输介质之上实现可靠的数据传输,以及如何控制传输速度以防止网络拥塞或者从中恢复。安全
在发送端,传输层从应用层获取数据并将它们分块,为每一个数据块添加一个头部造成一个Segment。以后将Segment递交给网络层,网络层对它进行封装并将其发送到接收端,在中间的传输过程当中并不会碰Segment中的内容。最后接收端的传输层获取Segment并将其中的数据递交给应用层。服务器
传输层是基于网络层实现的,所以传输层能提供的服务每每会受到网络层能提供的功能的限制。好比,网络层不能保证传输数据的带宽和延迟,那么传输层一样不能保证传输数据的带宽和延迟。可是传输层也能提供网络层所不具有的功能,例如可靠的数据传输以及对传输数据进行加密。其实,对于没法实现的功能是网络自己的问题引发的,和分层无关,而传输层实现网络层不具有的功能,也只是由于这些功能在传输层实现更合适而已。网络
拥塞控制不是为调用TCP的应用提供的服务而是一个有益于全局的服务。拥塞控制防止了单个链接用巨大的流量淹没其流经的链路和路由器,从而能让全部链接都共享拥塞的链路,这是经过控制发送端的发送速度实现的。可是UDP并无该限制,使用UDP的应用能够用任何速度发送流量而且能够持续任意长的时间。less
端口是传输层在Demultiplexing Segment时做为依据的标示,端口号从0~65535,其中0~1023是众所周知的端口。通常服务器端须要本身绑定端口而客户端则系统会默认分配一个端口。性能
对于UDP,一个Socket由(目标IP,目标端口)这样的二元组进行标示,只要两个Segment的目标IP和目标端口相等,即便源IP和源端口不相同,也会被发往目标IP所在主机的同一目标进程。加密
对于TCP,一个Socket由(源IP,源端口,目标IP,目标端口)这样的四元组进行标示。所以对于两个源IP或源端口不一样的TCP Segment,它们会被发往不一样的Socket(除了最开始携带创建链接请求的TCP Segment之外)。操作系统
TCP Socket建立的过程以下所示:设计
1)当Server首先会建立一个“Welcoming Socket”,例如在端口12000等待来自Client的创建链接请求进程
2)Client建立Socket而且向Server发送一个创建链接请求,而创建链接请求无非是一个Header中的Connection-Establishment Bit设置为true的TCP Segment
3)Server端的操做系统接收到2)中建立的创建链接请求,根据目的端口,将请求转发至Server,Server根据四元组建立一个Socket,以后全部包含该四元组的Segment都会发往该Socket
UDP是最为简单的传输层协议,除了Multiplexing/Demultiplexing以及一些Error checking以外,它并无在IP之上添加任何其余功能,值得注意的是UDP在发送Segment以前,UDP的接收方和发送方并无握手的过程,所以UDP是Connectionless。既然TCP能提供可靠的数据传输服务,为何还会有应用使用UDP?由于有的应用更适合UDP,缘由以下:
1)能够在应用层面对发送什么数据以及什么时候发送有更好的控制,在传输层使用UDP而且在应用里实现其余必要的功能
2)没有链接创建的过程,由于创建链接会引入很大的延时,这也是DNS使用UDP而不是TCP的缘由
3)UDP不须要维护链接状态,所以一样的Server,UDP可以支持更多的Client
4)更小的头部Overhead,TCP的头部是20字节,UDP的头部是8字节
多媒体应用对于TCP的拥塞控制机制并不适应,所以通常更倾向于使用UDP。可是随着丢包率变低以及一些组织由于安全缘由屏蔽UDP流量时,对于多媒体应用TCP将变得更有吸引力。
基于UDP的多媒体应用是一直存在争议的,由于没有拥塞控制的UDP协议可能致使很是高的丢包率而且对于有拥塞控制的TCP是不公平的。
UDP的头部仅包含四个字段:源端口,目标端口,长度(包括头部和负载部分)以及校验码。
虽然有的二层协议(包括最流行的Ethernet)会提供校验,可是IP协议可能运行于任何二层协议之上,所以UDP依旧须要提供校验。
可靠的数据传输事实上是指将数据无损地,不丢失地,按序地从发送发传输到接收方,为了达到这一目的,事实上不只要传输有效负载,还应该传输一系列的控制数据。
此时咱们的传输协议须要三种能力:
1. Error Detection:经过校验码检验数据在传输过程当中是否发生错误
2. Receiver Feedback:接收方须要给发送方反馈是否正确地收到信息,对于当前状况,只需返回接收成功(ACK)或者接收不成功(NAK)
3. Retransmission:对于接收方发现错误的数据,发送方要可以重传
可是咱们忽略了接收方放回的ACK和NAK也存在损坏的可能,对于这一点,咱们能够引入校验码并从中恢复出正确的信息,另外咱们也能够在接收到NAK或者损坏的反馈信息时都进行重传。可是若是接收方返回的是ACK,可是损坏了,而发送方又进行重传了,则接收方会收到重复的信息。此时咱们就须要为发送数据进行编号,从而防止重复。
事实上,咱们彻底不须要NAK,咱们只须要在ACK以后增长一个编号便可,若接收方接收到符合要求的数据,则ACK对应数据的序号,不然返回上一次正确接收到的数据的序号便可。
经过超时重传便可解决该问题,可是超时的时间难以肯定,通常该时间至少为Round-Trip Delay,不过这实际上也是一个在动态变化的数字,发送者一般须要本身对超时时间进行预估,虽然在预估的时间到达之后仍是不能保证发送的数据或者对应的ACK已经丢失了。
若是基于发送数据并等待ACK以后再发送的Stop-And-Wait方式,则数据的传输效率会很低,事实上彻底能够并行发送多个数据从而提高效率。但此时数据的Range of Sequence Numbers须要增长,同时发送者和接收者须要对数据进行缓存,特别是接收者的缓存会和具体的策略有关。
GBN其实就是一个滑动窗口协议,窗口的大小为N,而流量控制和拥塞控制是限制窗口大小的缘由,对于发送方来讲有如下事件须要处理:
1. 上层须要发送数据:若是窗口未满,则直接发送数据,不然缓存数据或者利用同步机制在窗口有空闲时通知上层
2. 接收到ACK:GBN采用累计确认机制,当收到数据N的ACK时,则表示N以前的数据都已被确认
3. 超时:GBN使用一个统一的时钟,若发生超时,则从新发送全部已发送而未ACK的数据,若收到了一个ACK,则重启时钟,若没有已发送而未ACK的数据,则时钟中止
接收方若收到序号为N的数据,若是以前的数据都已被接收,则将数据传输至上层并返回ACK,若N以前有数据未收到,则直接丢弃序号为N的数据,由于N以前的数据若是丢失了,根据GBN,丢失数据以后的数据都会被重传。可是这样作的缺点是,重传的数据也可能丢失从而再次须要重传。总之,这种已经收到正确传输的数据可是又丢弃的行为是一种浪费。
在GBN中,若是N和带宽都很大,那么将有不少数据存在于信道中,可是其设计机制决定了可能由于一个数据的错误致使大量的重传,从而可能产生性能问题。
为了不GBN的性能问题,SR会对正确但提早收到的数据进行缓存并返回相应的ACK,发送方根据接收到的ACK对相应的数据进行确认,当确认的数据可以链接在一块儿时就对窗口进行滑动。在SR中一样具备超时机制,可是时钟是和每一个数据匹配的,一个时钟超时只会重传对应的数据。
因为数据编号的范围是有限的,当滑动窗口的大小大于数据编号的范围时,接收方就会没法区分收到的数据是新的数据仍是重传的数据。例如数据编号的范围为0,1,2,3,窗口大小为3,开始发送0,1,2三个数据,且接收方全都收到并分别做出应答,若发送方成功接收ACK,则会继续发送3,0,1,若发送方未接收到ACK,则重传0,1,2,由此接收方彻底没法判断,以后发来的数据0和数据1未新发的数据仍是重传的数据。
若窗口大小小于数据编号的二分之一,则在最极端的状况下,接收方指望接收的数据范围和重传的数据范围也永远不会重合。
窗口大小问题对于GBN一样存在,可是它只要求窗口大小不和数据编号的范围相等便可。若是二者相等且接收方的ACK所有丢失,发送方会不断重传,可是接收方仍然可以所有按序接收。
TCP是一个面向链接的协议,所谓的面向链接就是指两个位于不一样主机的进程在通讯以前先要进行“握手” ,而所谓的握手是指在传输数据以前先进行一系列的交互用于创建确保传输正常进行的参数。做为创建链接的一部分,链接的两端都会初始化一些TCP状态参数。
TCP链接的每一端都包含发送缓存和接收缓存以及一系列的参数,TCP从缓存中获取数据,构建一个TCP Segment,每一个Segment中应用数据的大小不能超过Maximum Segment Size(MSS),MSS是由MTU决定的,以太网的MTU为1500,减去IP和TCP的头部各20,最终MSS为1460。
TCP的头部由以下几部分构成:
1. 16位的源端口和目的端口
2. 32位的Sequence Number和Acknowledgment Number,做为字节流的编号,用于可靠的数据传输
3. 4位的头部长度,TCP的头部理论上有可扩展的部分,可是通常都不用,正常状况下都是20字节
4. Flag字段:ACK位设置,代表Acknowledgment Number字段是有效的,说明有相应的Segment被确认
RST,SYN,FIN用于链接的创建和关闭
CWR和ECE主要用于显式的拥塞控制,PSH代表接收方须要马上将数据交给上层,URG表示发送方的应用层将数据标记为"Urgent",紧急数据的起始位置由16位的Urgent Data Pointer字段决定。不过通常PSH,URG以及Urgent Data Pointer都不会用到。
TCP对字节流中的每一个字节而非每一个Segment进行编号,其中Sequence Number是传输的字节块中第一个字节的编号,而Acknowledgment Number则是发送方指望从接收方获取的下一个字节的编号。例如,B从A获取到了字节0~574的Segment,则其中的Sequence Number为0,而A发往B的ACK中的Acknowledgment Number则为575.
TCP采用累计确认的方式,例如A已经收到从B发来的0~535以及900~1000,所以A还在等待来自B的536~899,因此A发往B的下一个Segment的Acknowledgment Number为536。同时对于Out-Of-Order的Segment,TCP的RFC并无明肯定义处理方式,通常为了节省带宽,将它缓存起来是更好的选择。
事实上,链接双方对于初始的Sequence Number都是随机选择的,从而避免以前彻底相同的链接(一样的IP和端口)在网络中遗留的Segment对现有链接的影响。
TCP并不会对每一个Segment的RTT进行测量,它通常每一个RTT就只会测量一个Segment,并且对于重传的Segment也会不会测量。通常对于RTT的预测公式为:
EstimatedRTT = (1 - a) * EstimatedRTT + a * SampleRTT
上述公式代表,TCP更关注当前的SampleRTT,而非以往的Sample。
DevRTT用来测量RTT的波动状况,其生成公式以下:
DevRTT = (1 - b) * DevRTT + b * | SampleRTT - EstimatedRTT |
所以,重传的超时时间为:
TimeoutInterval = EstimatedRTT + 4 * DevRTT
TimeoutInterval初始化为1s,在第一次超时发生时会进行翻倍从而防止过早的超时,由于下一个Segment可能很快就会被ACK。当接收到第一个Segment的时候,EstimatedRTT就会更新,TimeoutInterval就会根据上述公式计算。
为每个传输但还未ACK的Segment配置一个时钟理论上是可行的,可是实际上会形成极大的Overhead,所以TCP仅会使用单个的时钟 ,通常该时钟和Oldest Unacknowledged Segment相绑定。
TCP的接收方收到失序的Segment会将它缓存而且ACK第一个按序未被接收的Sequence Number,若是接收者收到三个及以上的Oldest Unacknowledged Sequence Number的ACK,即便还未超时,也要从新发送Oldest Unacknowledged Sequence Number。事实上,当TCP的接收者在收到一个按序的Segment以后,并不会当即发送相应的ACK,而是会等待500msec以检测可以进行累计确认。
TCP链接的两端都有一个Receive Buffer用于缓存接收到的数据,可是因为应用不必定会及时处理这些数据,所以咱们须要有一种机制来匹配Receive Buffer的空闲状况和发送者的发送速率。
Flow Control会让TCP的发送方维护一个Receive Window,该窗口表示了接收方可用缓存的大小,通常该值都是经过接收者发送给发送者的Segment中的Receive Window字段决定的。
可能出现的一种状况是,接收方的缓存已满,此时发送方获取的Receive WIndow大小为0,发送者将永远再也不向接收者发送数据,可是接收方早晚会清空缓存,而接收者以后并不会通知发送者Receive Window已经不为0了,此时双方的通讯将永远陷入停滞。所以TCP要求当发送者即便在Receive Window为0的状况下,也要持续向接收者发送Segment,其中包含一个字节,从而在接收者的缓存不为0时得以通知发送者。
UDP中并无这种机制,它以后将数据简单地放到缓存中,应用程序会一次性读取缓存中的数据,若是读取不及时,则缓存就会溢出,就会有Segment被丢弃。
TCP创建链接的过程分为如下三步:
1. Client向Server发送一个Segment,其中Flag中的SYN设置为1,随机选取一个Client Sequence Number,咱们将这个Segment称为SYN Segment
---> Client从"CLOSED"状态转换为"SYN_SENT"状态
2. Server获取SYN Segment,为链接分配缓存以及初始化一系列的参数(这是形成SYN Flooding攻击的缘由),再发送给Client一个Segment,SYN和ACK设置为true,随机选择一个Server Sequence Number,ACK Number设置为Client Sequence Number加一,咱们将这个Segment称为SYN-ACK Segment
---> Server开始监听时,状态从"CLOSED"转换为"LISTEN",在步骤2以后,则切换为"SYN_RCVD"
3. Client收到SYN-ACK Segment,一样为链接分配缓存以及初始化一系列的参数,以后Client发送给Server一个Segment,ACK设置为true,ACK Number为Server Sequence Number + 1,SYN设置为false,能够携带数据。由此完成三次握手,链接创建
---> Client从"SYN_SENT"状态转换为"ESTABLISHED"状态,Server从"SYN_RCVD"切换为"ESTABLISHED"
最后,链接双方均可以结束链接,假设Client发起断开链接则:
1. Client向Server发送一个特殊的Segment,它的FIN设置为true
---> Client状态从"ESTABLISHED"切换为"FIN_WAIT_1"
2. Server收到该Segment回复一个ACK Segment
---> Server从"ESTABLISHED"切换为"CLOSE_WAIT",Client状态从"FIN_WAIT_1"切换为"FIN_WAIT_2"
3. Server也会向Client发送FIN Segment
---> Server从"CLOSE_WAIT"切换为"LAST_ACK"
4. Client收到Segment并ACK,链接结束
---> Client状态从"FIN_WAIT_2"切换为"TIME_WAIT"状态,该状态因实现而已,会持续30s,1min或者2min,从而容许在最后一个ACK丢失以后进行重传,Server从"LAST_ACK"切换为"CLOSED"
若是Server并无在相应的端口进行监听,则Server在接收到Client的SYN Segment以后会直接返回一个特殊的Segment,它的RST位设置为true。对于UDP的话,则会返回一个特殊的ICMP Datagram
SYN Cookie能够用于解决SYN Flood Attack,当Server收到SYN Segment时不会当即进行分配内存等初始化操做,而是基于源,目的IP和端口以及Server独有的Secret建立初始的Server Sequence Number,若以后获取到的对应的ACK的ACK Number可以经过验证(验证方法即用一样的方法基于源,目的IP和端口生成一个Number,判断该Number是否为ACK Number - 1),则Server再分配缓存以及进行相应的初始化工做。