(计算机网络)传输层协议原理

UDP协议原理

在 TCP/IP 协议中, 用 "源 IP", "源端口号", "目的 IP", "目的端口号", "协议号" 这样一个 五元组来标识一个通信。IP和端口号我们已经了解,协议号则是用来确定该通信使用的是什么协议(TCP/UDP)

下面是UDP报文的格式,其中UDP长度是整个报文(有效载荷+报头)的长度,不难发现,UDP报头长度固定为8字节,可以轻松与有效载荷分离。通过UDP长度即可确定有效载荷的大小,即使缓冲区中存在多个报文,也可以准确提取。

UDP协议没有真正意义上的发送缓冲区,操作系统内核会将数据直接传给网络层协议,进行后续的传输操作。但是UDP协议有接收缓冲区,毕竟接收端不一定有时间处理网络信息,不过接收缓冲区如果满了,后面到达的UDP数据会直接被丢弃。

我们注意到, UDP 协议首部中有一个 16 位的最大长度。 也就是说一个 UDP 能传输的数据最大长度是 64KB(包含 UDP 首部)。然而 64KB 在当今的互联网环境下, 是一个非常小的数字。如果我们需要传输超过 64KB的数据, 就需要在应用层手动分包,多次发送, 并在接收端手动拼装。

报文理解

在操作系统内部,可能会同时存在大量报文,操作系统同样采用了先描述再组织的形式管理这些报文,对应的结构体是sk_buff。用一个链表来管理所有的sk_buff,进而管理报文。不管是传输层、网络层还是数据链路层的报文都是这样管理的。而我们常说的一个报文,通常是指报文本身+管理报文的结构体sk_buff,如下。传递报文时,只需将管理报文的结构体sk_buff交给对应的协议层即可,通过sk_buff就能完成对报文的封装、解包操作。

head指向的是有效载荷的起始位置,不同协议层的有效载荷部分不同,数据在不同协议层之间传递,head的指向也会随之改变。如传输层的有效载荷就是图中的应用层数据,而网络层的有效载荷则是TCP(传输层)协议头+应用层数据。当报文从传输层传递到网络层时,head指针会从应用层数据的起始位置移动到TCP协议头的起始位置。存放协议头的空间并不是后面补充的,在内核为报文的数据分配内存时,会为其预留足够达的空间,用于存放所有的协议头。

TCP协议

报文结构

TCP协议的报文如图所示,标准TCP报头有20字节,其它的只会更长。下图中间的4位首部长度用于表示报头的长度,取值范围为[5,15],即[ 0101,1111 ] 。首部长度的值乘以4再加上20就是报头的长度(单位:字节),所以报头长度区间为[20,60]

序号与确认序号

报头中的序号和确认序号 是用来保证可靠性的。**可靠性就是指能否保证数据送达通信的另一端。**一般而言,只要收到另一端的应答,就可以保证上一条报文一定送到了另一端。没有应答时,发送端无法确认最新的报文是否送达,即无法保证可靠性,所以,接收端受到数据后必须发送应答,发送端收到应答确认数据送达即可,不需要对应答做出应答。

TCP将缓冲区中每个字节的数据都进行了编号,这些编号就是序列号,TCP报头中的序号的值就来自序列号。发送缓冲区本身可以视为一个一维数组,这些编号虽然不一定是每个字节的数据在数组中的下标,但是它也是由数组下标转化来的。

当接收端收到完整的数据时,会提取完整数据的最后一个字节的序号,加1后写入应答报头的确认序号,表示该序号以前的报文已经全部收到,下一次发送端发送数据时,从确认序号开始。

如下图所示,发送端发送的报文中,排在最后的数据的序号为1000,接收端接收后,发送的应答的确认序号就为1001,这样就能表明1001序号以前的所有数据都送达了,即这之前报文都送达了。发送端收到应答后,下一次再发送时,就从1001开始发送。这样不仅保证了通信的可靠性,还可以根据确认序号判断数据的先后顺序。

上面的简图中,各个箭头表示的都是报文 的传递,即使是仅用于确认数送达的应答,也至少包含完整的TCP报头,确认序号填写在TCP报头中。

TCP是全双工的协议,接收端接收到数据时,除了发送确认应答外,可能也会向发送端发送其需要的数据,例如网页。为了提高效率,在接收端恰好有数据要发送回发送端时,TCP协议会将确认信息填入该数据的报头,将数据报文作为确认应答发送。

发送这种捎带应答也是在发送数据,发送数据就要确认可靠性。因此,TCP 报头中既包含本端发送数据的序号,也包含确认对端数据的确认序号。

报头中的窗口大小 用于表示发送端的接收缓冲区剩余空间。 要将报文通过网络送到接收端并不容易,会消耗大量资源,如果接收端缓冲区满了,导致报文被丢弃,这会造成资源浪费。为此,在接收端缓冲区快满的时候,发送端就应该减少发送量。相反,缓冲区空间充足,接收能力较强时,则应该增加发送量,这就是流量控制

标志位基本理解

报头中间"保留"部分右边的字母就是标志位 ,每隔占一位,共6位。TCP报文有不同的类型,前面提到的确认应答就是其中之一。不同类型的报文有不同的处理方式,因此需要通过标志位区分。ACK标志位是否为 1 用于表示确认序号是否有效,也就是该报文是否为确认应答报文。

在通信前需要进行三次握手建立连接,此时就会用到SYN标志位,它是同步标志位,表示建立连接或进行握手。前两次握手时,报文不能携带数据,只能相互发送TCP报头,流量控制在这时候就会开始进行。大致的过程和报文的标志位如图所示,握手相关内容后面会展开。

ACK标志位几乎是一直被设为1的,因为除了客户端第一次发送的握手,正常通信时,一般都是一方发送数据,另一方接收后也发送一份数据,有来有回,发送的数据报文可以作为确认应答,所以报头的ACK标志位通常都是被置为1的

建立连接需要三次握手,断开连接需要四次挥手。因为断开连接时,双方都需要发送断开连接的报文,且需要对方发回应答。标志位FIN为1就表示该报文用于断开连接。

我们知道,当接收端的缓冲区满了之后,发送端就不能再发送数据,但发送端可以只发送TCP报头用来确认缓冲区情况,如果发送端等不了了,可以催促接收端清空缓冲区。PSH标志就是用于提示接收端立刻处理TCP缓冲区中的数据。

RST 标志用于异常终止连接。建立连接不一定能成功。主动建立连接的一方(通常是客户端)发送第三次握手(ACK)后,就认为连接已建立,因为该 ACK 不需要再被应答。而服务端必须收到第三次握手的 ACK 才会认为连接建立完成。

如果第三次握手的 ACK 丢失,客户端可能误以为连接已建立并发送数据,此时服务端并没有完成连接的建立,当其收到不含第三次握手的ACK的报文时,会回复 RST 通知客户端连接异常,需要重新建立连接。在通信的过程中,连接出现任何问题都可以使用RST通知对方出现了异常,需要重新建立连接。

URG用于表示报头中的紧急指针是否有效,紧急指针用于表示当前报文的有效载荷中,特定偏移量处有紧急数据。让数据在缓冲区中优先被取出并处理。紧急数据并不常用,只有一个字节,一般用于设置各种状态码。

超时重传机制

发送数据后,发送端没有收到应答ACK时,只能说无法保证可靠性,即无法保证对方是否收到消息,但不一定是数据丢失,可能是应答丢失了,也可能只是应答还没送达。但是,如果等待了一段特定的时间,一直没有收到应答,就可以认为是出现了丢包的情况,此时就需要发送端重新发送数据。

关键在于超时的时间设为多少才算合适。如果超时时间设的太长,会影响整体的重传效率,如果超时时间设的太短,有可能会频繁发送重复的包。最理想的情况下,是找到一个最小的时间,保证确认应答一定能在这个时间内返回。但是随着网络环境的不同,这个最小时间是有差异的。

TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。Linux中(BSDUnix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。

如果重发一次之后,仍然得不到应答,等待2*500ms后再进行重传。如果仍然得不到应答,等待4*500ms进行重传。依次类推,以指数形式递增。累计到一定的重传次数,TCP会认为网络或者对端主机出现异常,强制关闭连接。

对于接收端而言,如果应答丢失情况较多,可能会收到很多重复数据,此时TCP协议会通过序号丢弃重复的数据。

连接原理

服务器可能会与很多客户端建立连接,每一个TCP通信连接都需要被管理,管理的方式仍然是先描述再组织。连接的状态会随着建立和断开连接的过程而变化,如下图,而这个状态就是由管理连接的结构体内的一个整数成员管理。 从上图我们可以发现,connect和accept接口只是发起握手,真正完成握手的是操作系统。包括收发数据也是如此,read和write只是拷贝缓冲区,真正完成收发操作的也是操作系统

三次握手

建立连接需要进行三次握手,双方共需要发送三次,这么做有两个原因,一是以最少的通信次数验证全双工,即让通信双方都确认对方具有收发数据的能力。二是以最小的成本确认双方通信意愿。

三次握手的过程并不复杂,主动方(一般是客户端)发起连接请求,另一方(一般是服务端)应答并同样发起请求,主动方再应答。简单来看就是 请求-应答、请求-应答,四次操作由于服务端将应答和连接请求一同发送而变为三次。能够将应答和连接请求合在一起是因为服务端的TCP协议栈必须接收客户端的连接请求,至于建立连接后是否给数据由应用层决定。

四次挥手

四次挥手的本质是让双方达成断开连接的共识,和三次挥手一样,双方都要发起断连请求,并收到对方应答。大致过程如下图所示。

挥手需要四次是因为客户端和服务器并不一定想同时断开连接。比如客户端请求断连,但此时服务器的发送缓冲区还有要发给客户端的数据,此时服务端除了应答,还需要将缓冲区的数据发送完才能断连,此时服务器处于CLOSE_WAIT状态,客户端则一直处于FIN_WAIT_2状态。CLOSE_WAIT状态会一直占用文件描述符。

TCP协议规定,主动断连的一方发送完断连应答(第四次挥手)后,要进入TIME_WAIT状态,等待两个MSL的时间(后面解释)才能关闭文件描述符,进入CLOSED状态,完全断连。这是为了防止断连应答丢失,如果客户端发送完应答就直接关闭,服务器由于应答丢包一直没有收到应答,就会重复发送断连请求,而客户端连接已经关闭,无法接收服务器的数据,导致服务器无法断连。

MSL(Maximum Segment Lifetime)是报文最大生存时间时间。发送报文后,超过这个时间都没有收到该报文,就可以认为报文丢失。如果收到了超时的报文,一般直接丢弃,因为此时发送端补发的数据可能已经在路上,甚至已经处理完毕,如果保留会导致数据冲突。

滑动窗口

基本概念

前面提到,对于发送端发送的每一个数据段,接收端都要发送一个应答,如果要等收到确认应答ACK后再发送下一个数据段效率会很低下,因此发送端会一轮发送多条数据,如下图,发送端发送前4000个字节(四个段)时,不需要等待ACK,而是直接发送。

每次一共能发多少数据由窗口 大小决定,上图的窗口大小就是4个段,而窗口大小由接收端决定,后面会讲。我们知道,发送缓冲区可以看成一个一维数据,窗口其实就是这个一维数组上,被两个指针维护的一段区域,如下,随着数据的发送,窗口会向右移动,因此窗口也叫滑动窗口

start指针前面是已经发送并收到应答的数据,可以被新数据覆盖,窗口内的部分就是这一轮可以直接发送的数据,当收到应答时,滑动窗口就会根据报头中的确认序号向右移动(修改start和end指针的指向),跳过确认序号之前的数据。

丢失应对策略

网络通信时,数据和应答都有可能丢失,如果是应答丢失,只要不是确认序号最大的应答丢失,那就不要紧,因为只要收到最大的确认序号,就表示主句B收到了此前的所有报文,如下图,只要主机A能收到确认序号为6001的报文就行,其它的应答丢了无所谓,滑动窗口正常工作

如果是数据丢失,滑动窗口会根据收到的确认序号补发。首先可以明确的是,如果部分数据没有确认已经送达,滑动窗口不能直接跳过,因为处于滑动窗口右边的数据是已经确认送达的,可能会被覆盖。

以下图为例,如果部分数据报文丢失,则主机B返回的是丢失报文中序号最小的报文的起始序号,如下图所示,1001~2000的数据报文丢失,后续所有应答返回的确认序号都是1001。如果收到三次重复的1001确认应答,就可以肯定是1001到2000这段数据丢失。因为如果收到次数少于三次就重传容易误判。

主机A并不知道丢了多少,只会补发1001开始的一段数据,之后再根据主机B返回的确认序号来继续补发,像上图刚好只丢了1001那段,如果2001开头的也丢了,主机B收到补发的1001数据时,就会返回确认序号为2001的应答,让主机A补发。全都补齐后,主机B就会直接返回7001,其实就表示后面的数据已经全部收到。

在这个过程中,滑动窗口的左端如何移动完全由确认序号决定,只要收到了确认序号,就可以跳过确认序号之前的部分,表示该部分数据确认送达。如果丢失的是窗口中间或右边的数据报文,滑动窗口就会通过确认序号移动到丢失数据的起始位置,此时其实就可以视为是最左侧数据丢失,用同样的方式处理即可。

如果是窗口中靠右侧的数据的应答丢失,其实对于发送端来说,这和对应的数据丢失了没什么区别,发送端会重新补发数据,接收端会处理重复,并在应答报头中填写正确的确认序号,发给接收端。

收到三个确认序号相同的应答就进行补发的机制是快重传 ,可以提高效率。如果一直没有收到任何应答,则会进入前面讲的超时重传机制,即发送数据后的一段时间内没有收到应答,则重新发送。

很多TCP的发送缓冲区的实现是使用环形缓冲区,窗口在物理上不连续,逻辑上连续。当窗口右边界到达物理缓冲区末尾时,则窗口右边界可以绕回到缓冲区开头。发送端可以继续填入新数据。或者直接将窗口整个移动到开头。

流量控制

接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制(FlowControl)。

具体实现发送简单来说,就是控制发送端滑动窗口的大小。接收端会根据自己接收缓冲区的剩余空间大小填写 TCP 首部中的"窗口大小"字段,通过 ACK 通知发送端。 窗口大小字段越大,说明网络的吞吐量越高。接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端。发送端接收到之后,就会将滑动窗口的大小设为该值,减慢自己的发送速度。

如果接收端缓冲区满了,就会将窗口大小字段置为 0,让发送方不再发送数据,但是发送方需要定期发送一个窗口探测数据段,让接收端把窗口大小告诉发送端,以确认接收端能否接收数据。

拥塞控制

TCP协议不仅考虑了双方主机的问题,还考虑了网络本身的问题。如果出现大量的丢包,发送方就会判断是出现了网络拥塞的问题。此时不能立即重发数据,因为如果真的是网络拥塞,再发送大量的数据只会雪上加霜。

此时,TCP会进入慢启动机制,先发少量的数据,如果通信顺利再逐渐增加,每次的发送量增加一倍,如图

一台主机减少发送量可能算不了什么,但出现网络拥塞时,所有使用TCP协议的主机都会开始控制发送量,此时网络或许就会重新畅通起来。

控制发送量的方式同样是调整发送缓冲区的滑动窗口大小。滑动窗口大小除了收应答报头的"窗口大小"字段控制,还受拥塞窗口 控制,拥塞窗口是发送方根据感知到的网络拥塞程度,动态维护的一个数值。实际的滑动窗口大小就是两个值取较小者,这样及考虑了网络拥塞问题,由考虑了对方接收能力的问题。

事实上,拥塞窗口的值也不是一直以2倍的速度指数增长的,而是在增长到一定程度后改为线性增长。如下图。虽然说拥塞窗口再大,滑动窗口的值也不会超过应答报文的"窗口大小"字段,但是此时就是因为网络就有些拥堵才控制发送量的,如果网络里的每台主机都以2倍的速度恢复原来的发送量,那网络很快又会开始拥堵了。

开始时的指数增长是为了尽快探测网络情况并恢复网络通信。当拥塞窗口超过一个阈值时就会改为线性增长。当 TCP 开始启动的时候,,慢启动阈值等于窗口最大值。在每次超时重发的时候, 慢启动阈值会变成原来的一半,同时将拥塞窗口置回1。

延时应答

我们知道,接收端主机需要在应答报头中基于缓冲区剩余空间填写窗口大小字段,此时可以等一会,让上层将缓冲区的数据拿走一部分,让缓冲区有更多的空间,此时可以在报文中填写更大的窗口大小,允许发送方提高发送量。

异常情况应对

在使用TCP协议通信过程中,进程被终止时会释放文件描述符,但仍然可以发送FIN断开连接,这与正常关闭没有本质区别。机器正常关机的情况与进程终止相同。

而当其中一端端机器断电或网线断开时,另一端并不能及时获知,仍认为连接仍然存在,当它多次重传报文没有收到应答时,就会认为连接已断开。即使没有发送报文,TCP自身也内置了保活定时器,会定期询问对方是否还在,如果对方已不在,也会释放连接。

此外,应用层的某些协议也具备类似的检测机制,比如HTTP长连接中会定期检测对方的状态,又如QQ在断线后也会定期尝试重新连接。

相关推荐
雨声不在2 小时前
IP路由表(ip rule)修改
网络·网络协议·tcp/ip
Alan Lan3 小时前
通过socket获取和解析udp的导航数据
网络·网络协议·udp
李庆政3704 小时前
OkHttp的基本使用 实现GET/POST请求 authenticator自动认证 Cookie管理 请求头设置
java·网络协议·http·okhttp·ssl
DARLING Zero two♡4 小时前
【计算机网络】简学深悟启示录:udp&&tcp协议
tcp/ip·计算机网络·udp
以太浮标4 小时前
华为eNSP模拟器综合实验之- NATServer 实践配置解析
网络·网络协议·华为·智能路由器·信息与通信
IT WorryFree4 小时前
LLD 自动发现场景 → 对应使用哪种探测方式(SNMP/HTTP/Agent)最优
网络·网络协议·http
北京耐用通信5 小时前
赋能智能制造:耐达讯自动化CC-Link IE转EtherCAT网关的行业价值
人工智能·物联网·网络协议·自动化·信息与通信
头疼的程序员5 小时前
计算机网络:自顶向下方法(第七版)第七章 学习分享(四)
网络·学习·计算机网络
发光小北5 小时前
SG-UHF80 系列超高频 RFID 读写器如何应用?
网络协议