传输层位于OSI七层模型的第四层,TCP/IP四层模型的第三层。
传输层主要是给应用层提供通信服务的,是面向通信部分的最高层。在网络编程中,一般使用的都是传输层暴露出来的接口。
在层次结构这一章可知,传输层负责管理端到端的连接,主要面向终端设备。
这张图想必会造成一个疑问,路由器都没有传输层,那数据是怎么通过路由传输呢?
通过前面两章的学习,我们可知,两个主机进行网络通信,需要经过一些网络和连接这些网络的路由器。因为通信的起点和终点是两台主机,所以通信路径中的所有路由器都是中间节点。那么不管这两个主机采用何种协议(TCP或者UDP)进行数据传输,中间节点的工作内容都是根据路由表来转发IP数据报,这是IP协议的功能,而IP协议在网络层。每个路由器在接收到比特流后,先按照数据帧来接收,再从数据帧中取出IP数据报,根据IP数据报首部的目的IP地址来查询路由表,之后将IP数据报组装成新的数据帧转发出去。
所以传输层工作的本质就是进行进程间的通信。
在操作系统相关的篇章中,我们知道一个计算机可以运行一个或多个进程。对于网络通信,计算机之间是怎么标识进程的呢?这里引入端口(Port)
的概念。端口使用16位比特位(0-65535)来表示。使用IP地址+端口的形式就可以唯一标识进程。
表格中是一些常见协议对应的端口。
协议 | FTP | HTTP | HTTPS | DNS | TELNET |
---|---|---|---|---|---|
端口 | 21 | 80 | 443 | 53 | 23 |
UDP协议
UDP:User Datagram Protocol,用户数据报协议。所谓数据报就是应用层传过来的完整的数据。UDP是一个简单地协议,它不会对数据报做任何处理(比如说合并、拆分之类),所以UDP数据报的长度由应用层传过来的数据决定。
UDP是存放在IP数据报的数据部分的,而IP数据报文是存放在数据帧的数据中的,套娃过程如下:
接着来看下UDP报文的首部,UDP头部信息很少,所以创建UDP报文其头部开销较小。
UDP是无连接的协议,发送数据报文比较随意,想发就发,发了也不关心你收到收不到,所以UDP不能保证可靠的交付数据。
UDP是面向数据报文的协议,应用层来的数据不做处理,直接塞到自己的数据段中。
UDP没有拥塞控制(什么是拥塞控制会在TCP部分讲解)。
可靠传输
TCP能保证数据可靠传输,在讲解TCP之前我们先来了解下可靠传输的概念。
最理想的传输有两个特点:
1、传输信道不产生差错;
2、不管发送方是否降低发送数据的速度,不需要采取任何措施就能够实现可靠传输。
然而实际中的网络并不能够达到这种理想的状态,但我们可以通过使用一些可靠传输的协议去处理这些问题,比如规定如果发现发送的数据发生错误时让发送方重新发送,这样的协议有停止等待协议、连续ARQ协议。
停止等待协议
停止等待协议的主要实现思路就是发送方给接收方发送消息后就会停止发送,等待接收方发过来的确认消息。在收到接收方的消息后又会继续发送消息,接着又会进行等待。
这是最简单的可靠传输协议,但每发送一条消息都需要等待确认信息,所以对信道利用效率不高。
上图一看就知道是理想、无差错的状况。
出错的情况一般为,发送的消息丢失了、确认的消息丢失了、确认的消息很久才收到。
对上述情况的容错处理主要表现为超时重传,发送方每发送一条消息都会设置一个定时器(超时定时器),定时器到期还没收到确认消息,就会重新发送数据。
连续ARQ协议
Automatic Repeat-reQuest,自动重传请求。
停止等待协议是对单条信息进行发送和确认,所以效率不高。连续ARQ协议的思路就是批量发送信息再批量接收,这样就提高了效率。
假设当前批量发送了5条消息。
当收到确认信息后,就会对下一组数据进行批量发送。虽然是批量接收确认信息,但它并不关心确认信息到底确认了哪条信息,而是会进行累计确认,我发送了5条数据,也收到了5条确认信息,即代表本组数据发送成功,开始发送下一组数据。
这种操作被称为滑动窗口
。
TCP协议
TCP协议,即Transmission Control Protocol,传输控制协议,是计算机网络中一个非常复杂的协议。同样的,TCP数据报文也是放在IP数据报中:
TCP是面向连接的协议,可以进行全双工的通信。在进行通信时,需要建立TCP的连接。一个连接有两端,是点到点的通信。TCP不同于UDP,TCP提供可靠的传输服务。
在数据传输方面,TCP是面向字节流的协议。TCP的数据段中存放的是用户数据的整体或部分数据流(TCP协议为了提高数据传输效率会对数据进行一些拆分、合并工作)。
TCP协议的头部数据如下:
除去可选项和填充数据,TCP首部固定20字节。
源端口:起始地址
目的端口:目的地址
序列号:初始值由系统随机生成,随后的值 = 初始值 + 发送的数据在整个数据流中的偏移量,这样的设计可以保证数据传输不乱序。
32位使得其表示范围位0~2^32-1
确认号:表示范围位0~2^32-1,数据发送出去后,接受端给发送端的反馈机制。例如发送端发送了序列号为500,长度为100的数据,
那么接收方会给发送方返回确认号600,表示其希望收到的数据首字节序号为600。简而言之,确认号N表示N-1序号及其之前的数据都已收到。
数据偏移:表示真实的数据偏移首部的位置。占4位,可以表示0~15,单位为32位字,即每一个偏移量位4个字节。
保留字段:占6位,保留为今后使用,目前应设置为0
TCP标记:占6位,分别表示UGR、ACK、PSH、RST、SYN、FIN:
紧急UGR:当UGR置1时,发送应用进程就告诉发送方的TCP有紧急数据要传送。于是发送方的TCP就把紧急
数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。
确认ACK:确认报文段,仅当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。
推送PSH:当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方响应。
在这种情况下,TCP可 以使用PSUH(推送操作)。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,
就尽快(推送)交付给接收应用进程,而不在等整个缓存都填满了再向上交付。
复位RST:当RST=1时,表明TCP连接出现了严重差错,必须释放连接,然后重新建立新运输连接。
RST=1还可以用来拒接一个非法报文段或拒绝打开一个连接。
同步SYN:在建立连接时用来同步序号。当SYN=1,ACK=0时,表明这是一个连接请求报文段;对方若同意连接,则应在相应的报文段中使SYN=1,ACK=1。
因此SYN置1就表示这是一个连接请求或连接接受报文段。
终止FIN:用来释放一个连接,当FIN=1时,表明此报文段的发送方数据已经发送完毕,并要求释放运输连接。
窗口:用于指明允许对方发送的数据量。
校验和:CRC循环冗余检测算法
紧急指针:指定紧急数据(URG=1)在报文的位置。
TCP选项:最多支持40字节,用于支持未来的拓展。
填充:将数据填充成32位的整数倍。
可靠传输
我们知道TCP是可靠传输的,在了解上文的可靠传输基本原理后,我们继续了解TCP协议是怎么保证可靠传输的。
根据上文的TCP报文头不难看出TCP的可靠传输基于连续ARQ协议的,其滑动窗口以字节为单位。TCP首部由于有确认号的信息,这种确认机制使得其窗口的粒度变得很细。
假设现在窗口中的数据为1-5:
TCP协议不必等收到5次确认后再滑动窗口,假设现在收到了1和2的确认,窗口就会滑动(窗口首部收到了确认就会滑动)。
TCP支持选择重传,因此不必重传窗口中所有的数据,只需要按需重传超时的数据即可(指定起始序号和终止序号重传这一段数据)。
流量控制
流量控制是控制发送方的发送速率,使其不要太快(不要超出接收方的最大接收速率),这个机制是使用滑动窗口来实现的。在前文中我们知道们了解到TCP协议的数据头中有一个16位的窗口字段,这个字段就是指明允许对方发送的数据量。
由上图我们可知,接收方在确认数据后,会告诉发送方它还能再接收多少的数据(窗口大小)。接收方回复窗口为0后,会在窗口可用时,给发送方发送消息,告知其窗口大小,这条消息无须确认。那么问题来了,这条消息丢失了怎么办,这不死锁了吗?
这里使用了定时器的机制:当发送方收到窗口为0的消息时,会开启一个定时器(坚持定时器),定时器每隔一段时间向发送方发送窗口探测报文,这样就能知道窗口大小了。
拥塞控制
拥塞:每个通过网络发送的包由于网络中充塞着包而经历极长延迟的情况。类似于堵车。
通过流量控制的介绍我们可知,流量控制关注的是点对点之间的通信。
信息在网络中传输,一条数据链路经过了众多设备,这些设备都有可能成为网络传输的瓶颈。类似木桶的短板效应,一条数据链路的最大传输窗口为这条链路中所有设备的最小窗口。
拥塞控制考虑的是整个网络,是全局性的考虑。对于拥塞控制的判断方法很简单粗暴(不考虑网络故障的情况):我发送一个报文,如果报文超时了,那就代表发生了拥塞。
拥塞控制的算法有慢启动算法和拥塞避免算法。
慢启动算法
这是个比较简单的算法,主要思想是由小到大逐渐增加发送的数据量,即每收到一个报文的确认,下次发送的报文数就再加一。发送的数据量先会以2的指数级增长到达阈值(慢启动阈值 ssthresh)。
拥塞避免算法
慢启动达到阈值后就会使用拥塞避免算法,该算法会维护一个拥塞窗口的变量,只要网络不拥塞,就试探着将拥塞窗口调大。直到将最大拥塞窗口试出来。
举例:拥塞避免算法 - ggjucheng - 博客园 (cnblogs.com)
在该图中,假定当cwnd为32个报文段时就会发生拥塞,最大拥塞窗口为31。
根据慢启动算法可知,在第四次时达到了阈值16。接着使用拥塞避免算法,每次将窗口调大1,窗口调到32时发生了拥塞,此时可知拥塞窗口为31。
三次握手
先来回顾下TCP报文头部的TCP标记字段,TCP建立连接需要三次握手,用到的标记为ACK、SYN、FIN。
三次握手的过程如下:
第1次握手:发送方发出一个SYN报文,报文中有自己的序列号seq = x,发送方进入同步已发送状态;
第2次握手:接收方接收消息后会被动的打开TCP连接,同时发送确认报文。SYN = 1表示这是一个连接请求报文,ACK = 1表示确认号生效,ack = x + 1表示确认号,接收方还会发送自己的序列号seq = y。接收方进入同步已接收状态。
第3次握手:发送方对接收方第二次握手的确认。
通过第2、3次握手两次ACK确认操作,发送方和接收方都知道了各自的序列号seq是多少。
为什么需要第3次握手?
先说结论:第三次握手可以避免已经失效的请求报文传到接收方,引起错误。
假设不需要三次握手,两次握手即可建立连接。那么当接收方发送完确认报文后,即代表连接已建立。
如果发送方发送的请求报文在网络中发生了超时,根据前面的可靠传输部分可知,发送方会重传请求报文。
接收方会对重传的报文进行确认,建立了连接。此时第一次发送的请求报文到达了接收方,接收方会进行确认,然后就又建立了连接。
现在回到三次握手的情况,发送方超时重传了请求报文,接着接收方发送了确认报文,然后发送方发送了确认报文,建立连接。
同样的超时的报文到达了接收方,接收方回复了确认报文,发送方收到确认报文后,由于该报文之前因为超时重传过,发送方不会再给接收方发送确认报文(第三次握手),也就避免了连接的建立。
四次挥手
TCP连接释放需要进行四次挥手,过程如下:
简要的介绍下过程:
数据传输完成,需要释放连接时,发送方会发送一个FIN报文,用于通知接收方它没有数据需要发送了,接收方回复确认。
接收方将数据处理好后,给接收方发送消息,通知发送方它没有数据要处理了,可以释放连接。发送方回复确认,接收方收到后立即关闭连接。发送方回复完确认后会开启等待定时器,在两个最长报文生命周期(MSL,建议设置为2分钟)后,发送方关闭连接。
为什么需要等待2MSL呢?
由图可知发送方发送的最后一个报文是没有确认的,等待主要是为了这个报文能被接收方收到。2MSL时间内接收方没有收到确认就会重传请求报文,等待发送方重新确认。另外就是MSL是报文最长生命周期,等待2MSL是确保所有报文都过期。
定时器
前文中提及了三个定时器:超时定时器、坚持定时器、等待定时器,现在系统的讲解下TCP协议中的定时器。
超时定时器
在截止时间(通常为60秒)到之前,已经收到了对此特定报文段的确认,则撤销计时器;截止时间到了,但没有收到对此特定报文段的确认,则重传报文段,并且将计时器复位。
坚持定时器
主要解决零窗口大小通知可能导致的死锁问题,当接收端的窗口大小为0时,接收端向发送端发送一个零窗口报文段,发送端即停止向对端发送数据。此后,如果接收端缓存区有空间则会重新给发送端发送一个窗口大小,即窗口更新。但接收端发送的这个确认报文段有可能会丢失,而此时接收端不知道已经丢失并认为自己已经发送成功,则一直处于等待数据的状态;而发送端由于没有收到该确认报文段,就会一直等待对方发来新的窗口大小,这样一来,双方都处在等待对方的状态,这样就形成了一种死锁问题。如果没有应对措施,这种局面是不会被打破的。为了解决这种问题,TCP为每一个连接设置了坚持计时器。
当发送端TCP收到接收端发来的零窗口通知时,就会启动坚持计时器。当计时器的期限到达时,发送端就会主动发送一个特殊的报文段告诉对方确认已经丢失,必须重新发送。
等待计时器
TCP关闭连接并不是立即关闭的,在等待期间,连接还处于过渡状态。2MSL时间内接收方没有收到确认就会重传请求报文,等待发送方重新确认,同时等待2MSL可以确保发送&接收两方所有报文都过期。
保活计时器
主要是为了防止两个TCP连接出现长时间的空闲,例如当一方出现状态变化或故障,另一方没有察觉的情况。
假设连接双方在建立连接后,只传输了-些数据,然后就都保持静默了,双方也都没有关闭连接(这种情况经常存在) 。如果这个时候其中一方已经故障, 那么这个连接将会永远被打开,如果被连接的一方是服务端的话, 那将浪费很多服务端的资源。 因此为了解决这个问题,服务端般都会设置1个保活定时器,每次收对方的数据则重置这个定时器,如果定时器超时,服务端则发送探测报文段,探测客户端是否还在线,如果没有收到响应的话,那么则认为客户端已经断开连接了,因此服务端也