Linux传输层协议 — UDP协议
Posted 一起去看日落吗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux传输层协议 — UDP协议相关的知识,希望对你有一定的参考价值。
🎇Linux:
- 博客主页:一起去看日落吗
- 分享博主的在Linux中学习到的知识和遇到的问题
博主的能力有限,出现错误希望大家不吝赐教
- 分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持的意义,祝我们都能在鸡零狗碎里找到闪闪的快乐🌿🌞🐾。
✨ ⭐️ 🌟 💫
目录
💫 1. 传输层
在学习HTTP等应用层协议时,为了便于理解,可以简单的认为HTTP协议是将请求和响应直接发送到了网络当中。但实际应用层需要先将数据交给传输层,由传输层对数据做进一步处理后再将数据继续向下进行交付,该过程贯穿整个网络协议栈,最终才能将数据发送到网络当中。
传输层负责可靠性传输,确保数据能够可靠地传送到目标地址。为了方便理解,在学习传输层协议时也可以简单的认为传输层协议是将数据直接发送到了网络当中。
🌟 1.1 再谈端口号
端口号的作用
端口号(Port)标识一个主机上进行网络通信的不同的应用程序。当主机从网络中获取到数据后,需要自底向上进行数据的交付,而这个数据最终应该交给上层的哪个应用处理程序,就是由该数据当中的目的端口号来决定的。
从网络中获取的数据在进行向上交付时,在传输层就会提取出该数据对应的目的端口号,进而确定该数据应该交付给当前主机上的哪一个服务进程。
因此端口号是属于传输层的概念的,在传输层协议的报头当中就会包含与端口相关的字段。
五元组标识一个通信
在TCP/IP协议中,用“源IP地址”,“源端口号”,“目的IP地址”,“目的端口号”,“协议号”这样一个五元组来标识一个通信。
比如有多台客户端主机同时访问服务器,这些客户端主机上可能有一个客户端进程,也可能有多个客户端进程,它们都在访问同一台服务器。
而这台服务器就是通过“源IP地址”,“源端口号”,“目的IP地址”,“目的端口号”,“协议号”来识别一个通信的。
- 先提取出数据当中的目的IP地址和目的端口号,确定该数据是发送给当前服务进程的。
- 然后提取出数据当中的协议号,为该数据提供对应类型的服务。
- 最后提取出数据当中的源IP地址和源端口号,将其作为响应数据的目的IP地址和目的端口号,将响应结果发送给对应的客户端进程。
通过netstat命令可以查看到这样的五元组信息。
其中的Local Address表示的就是源IP地址和源端口号,Foreign Address表示的就是目的IP地址和目的端口号,而Proto表示的就是协议类型。
协议号 VS 端口号
- 协议号是存在于IP报头当中的,其长度是8位。协议号指明了数据报所携带的数据是使用的何种协议,以便让目的主机的IP层知道应该将该数据交付给传输层的哪个协议进行处理。
- 端口号是存在于UDP和TCP报头当中的,其长度是16位。端口号的作用是唯一标识一台主机上的某个进程。
- 协议号是作用于传输层和网络层之间的,而端口号是作用于应用层于传输层之间的。
🌟 1.2 端口号范围划分
端口号的长度是16位,因此端口号的范围是0 ~ 65535:
- 0 ~ 1023:知名端口号。比如HTTP,FTP,SSH等这些广为使用的应用层协议,它们的端口号都是固定的。
- 1024 ~ 65535:操作系统动态分配的端口号。客户端程序的端口号就是由操作系统从这个范围分配的。
🌟 1.3 认识知名端口号
常见的知名端口号
有些服务器是非常常用的,这些服务器的端口号一般都是固定的:
- ssh服务器,使用22端口。
- ftp服务器,使用21端口。
- telnet服务器,使用23端口。
- http服务器,使用80端口。
- https服务器,使用443端口。
查看知名端口号
说明一下:
文件中的每一行对应一种服务,它由4个字段组成,每个字段之间用TAB或空格分隔,分别表示“服务名称”、“使用端口”、“协议名称”以及“别名”。
🌟 1.4 两个问题
- 一个端口号是否可以被多个进程绑定?
一个端口号绝对不能被多个进程绑定,因为端口号的作用就是唯一标识一个进程,如果绑定一个已经被绑定的端口号,就会出现绑定失败的问题。
- 一个进程是否可以绑定多个端口号?
一个进程是可以绑定多个端口号的,这与“端口号必须唯一标识一个进程”是不冲突的,只不过现在这多个端口唯一标识的是同一个进程罢了。
我们限制的是从端口号到进程的唯一性,而没有要求从进程到端口号也必须满足唯一性,因此一个进程是可以绑定多个端口号的。
🌟 1.5 netstat
netstat命令
netstat是一个用来查看网络状态的重要工具。
其常见的选项如下:
- n:拒绝显示别名,能显示数字的全部转换成数字。
- l:仅列出处于LISTEN(监听)状态的服务。
- p:显示建立相关链接的程序名。
- t(tcp):仅显示tcp相关的选项。
- u(udp):仅显示udp相关的选项。
- a(all):显示所有的选项,默认不显示LISTEN相关。
查看TCP相关的网络信息时,一般选择使用nltp组合选项。
而查看TCP相关的网络信息时,一般选择使用nlup组合选项。
如果想查看LISTEN状态以外的连接信息,可以去掉l选项,此时就会将处于其他状态的连接信息显示出来。
🌟 1.6 pidof
pidof命令可以通过进程名,查看进程id。
pidof命令可以配合kill命令快速杀死一个进程。
💫 2. UDP协议
🌟 2.1 UDP协议格式
UDP的位置
网络套接字编程时用到的各种接口,是位于应用层和传输层之间的一层系统调用接口,这些接口是系统提供的,我们可以通过这些接口搭建上层应用,比如HTTP。我们经常说HTTP是基于TCP的,实际就是因为HTTP在TCP套接字编程上搭建的。
而socket接口往下的传输层实际就是由操作系统管理的,因此UDP是属于内核当中的,是操作系统本身协议栈自带的,其代码不是由上层用户编写的,UDP的所有功能都是由操作系统完成,因此网络也是操作系统的一部分。
UDP协议格式
- 16位源端口号:表示数据从哪里来。
- 16位目的端口号:表示数据要到哪里去。
- 16位UDP长度:表示整个数据报(UDP首部+UDP数据)的长度。
- 16位UDP检验和:如果UDP报文的检验和出错,就会直接将报文丢弃。
我们在应用层看到的端口号大部分都是16位的,其根本原因就是因为传输层协议当中的端口号就是16位的。
UDP如何将报头与有效载荷进行分离?
UDP的报头当中只包含四个字段,每个字段的长度都是16位,总共8字节。因此UDP采用的实际上是一种定长报头,UDP在读取报文时读取完前8个字节后剩下的就都是有效载荷了。
UDP如何决定将有效载荷交付给上层的哪一个协议?
UDP上层也有很多应用层协议,因此UDP必须想办法将有效载荷交给对应的上层协议,也就是交给应用层对应的进程。
应用层的每一个网络进程都会绑定一个端口号,服务端进程必须显示绑定一个端口号,客户端进程则是由系统动态绑定的一个端口号。UDP就是通过报头当中的目的端口号来找到对应的应用层进程的。
说明一下:
- 内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号得到对应的进程ID,进而找到对应的应用层进程。
如何理解报头?
操作系统是C语言写的,而UDP协议又是属于内核协议栈的,因此UDP协议也一定是用C语言编写的,UDP报头实际就是一个位段类型。
例如:
UDP数据封装:
- 当应用层将数据交给传输层后,在传输层就会创建一个UDP报头类型的变量,然后填充报头当中的各个字段,此时就得到了一个UDP报头。
- 此时操作系统再在内核当中开辟一块空间,将UDP报头和有效载荷拷贝到一起,此时就形成了UDP报文。
UDP数据分用:
- 当传输层从下层获取到一个报文后,就会读取该报文的钱8个字节,提取出对应的目的端口号。
- 通过目的端口号找到对应的上层应用层进程,然后将剩下的有效载荷向上交付给该应用层进程。
🌟 2.2 UDP协议的特点
UDP传输的过程就类似于寄信,其特点如下:
- 无连接:知道对端的IP和端口号就直接进行数据传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报:不能够灵活的控制读写数据的次数和数量。
注意: 报文在网络中进行路由转发时,并不是每一个报文选择的路由路径都是一样的,因此报文发送的顺序和接收的顺序可能是不同的。
🌟 2.3 面向数据报
应用层交给UDP多长的报文,UDP就原样发送,既不会拆分,也不会合并,这就叫做面向数据报。
比如用UDP传输100个字节的数据:
- 如果发送端调用一次sendto,发送100字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节;而不能循环调用10次recvfrom,每次接收10个字节。
🌟 2.4 UDP的缓冲区
- UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。
- UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃。
- UDP的socket既能读,也能写,因此UDP是全双工的。
为什么UDP要有接收缓冲区?
如果UDP没有接收缓冲区,那么就要求上层及时将UDP获取到的报文读取上去,如果一个报文在UDP没有被读取,那么此时UDP从底层获取上来的报文数据就会被迫丢弃。
一个报文从一台主机传输到另一台主机,在传输过程中会消耗主机资源和网络资源。如果UDP收到一个报文后仅仅因为上次收到的报文没有被上层读取,而被迫丢弃一个可能并没有错误的报文,这就是在浪费主机资源和网络资源。
因此UDP本身是会维护一个接收缓冲区的,当有新的UDP报文到来时就会把这个报文放到接收缓冲区当中,此时上层在读数据的时就直接从这个接收缓冲区当中进行读取就行了,而如果UDP接收缓冲区当中没有数据那上层在读取时就会被阻塞。因此UDP的接收缓冲区的作用就是,将接收到的报文暂时的保存起来,供上层读取。
🌟 2.5 UDP使用注意事项
需要注意的是,UDP协议报头当中的UDP最大长度是16位的,因此一个UDP报文的最大长度是64K(包含UDP报头的大小)。
然而64K在当今的互联网环境下,是一个非常小的数字。如果需要传输的数据超过64K,就需要在应用层进行手动分包,多次发送,并在接收端进行手动拼装。
🌟 2.6 基于UDP的应用层协议
- NFS:网络文件系统。
- TFTP:简单文件传输协议。
- DHCP:动态主机配置协议。
- BOOTP:启动协议(用于无盘设备启动)。
- DNS:域名解析协议。
当然,也包括你自己写UDP程序时自定义的应用层协议。
Linux--网络3(传输层)
目录
一.UDP协议
1.1UDP协议的特性
特性:无连接,不可靠,面向数据报
- 无连接:UDP客户端给服务端发送消息的时候,不需要和服务端先建立连接,直接发送(客户端也是不清楚服务端是否正在在线)
- 不可靠:UDP并不会保证数据是可靠有序到达对端
- 面向数据报:UDP数据不管是和应用层还是网络层传递,都是整条数据交付的
1.2UDP协议段格式
如下图所示,这里的数据又叫做有效载荷(应用层的数据)
- 16位源端口:当前的UDP数据从那一个进程产生
- 16位目的端口:当前的UDP数据想要去往哪一个进程
- 16位UDP长度:UDP数据的最大传输数据大小。它由两部分组成:UDP协议报头+UDP负载数据(这里指应用层递交给UDP数据);它的最大数据长度是2^16,UDP最大的传输大小是65536字节。
- 16位UDP校验和:校验数据在传输的过程是否失真。如果失真了,UDP协议就会将该UDP数据报丢弃;如果没有失真,则当应用层调用recvfrom函数的时候,就会将UDP数据报给到应用层。
常见的面试题:
- UDP传输的数据最大的大小是多少?答:65536字节
- 如果想要传输要比2^16更大的数据应该这样做呢?答:在应用层针对数据进行分片,将超过65536字节的数据包进行拆分,将拆分后不超过65536字节的数据递交给传输层的UDP协议打上UDP包头,此时在传输层存在多个独立的UDP数据包,因此在应用层需要描述分包出来的UDP片段。所以我们在应用层不仅要对数据进行分片还有对其进行描述,在其前面打上应用层包头,是我们自己定义的里面有标识,标志位和片偏移。
1.3UDP缓冲区
- 发送缓冲区:没有真正意义上的发送缓冲区,调用sendto会直接交给内核,内核将该数据传给网络协议进行后续的传输动作,也就是将应用层提交给传输层的应用层数据打上报头之后就提交给网络层继续传输
- 接受缓冲区:去掉UDP报头之后,将数据递交给应用层。它具有接收缓冲区,但不保证数据的可靠,以及有序。
- UDP的socket既能读又能写,这个概念叫做全双工。
1.4UDP常见的应用场景
基于UDP的应用层协议:
- DNS:域名解析
- DHCP:动态主机分配协议,谁上网给谁分配IP,同一个局域网内部可能使用UDP,因为网络稳定
- NFS:网络文件系统
二.TCP协议
2.1TCP的特性
特性:面向连接,可靠传输,面向字节流
- 面向连接:双方在发送网络数据之前必须先建立连接,再进行发送
- 可靠传输:保证数据是可靠并且有序到达对端(有序是针对应用层而言的,TCP能够确保的是该条数据到达对端的应用层时,一定是有序到达的,到达传输层的时候有可能不是)
- 面向字节流:多次发送数据在网络传输当中没有明显的数据边界(对于没有明显间隔的数据橙称之为TCP粘包问题)
2.2面向连接
2.2.1针对三次握手的面向连接
a.双方发送数据包名称
三次握手时双方发送数据包的名称如下图:
客户端调用connect接口发起三次握手,他会给服务端发送SYN数据包(连接数据包),服务端接收到SYN数据包后,通常会回复一个ACK+SYN数据包(ACK在这里的作用是服务端用来确认客户端发送给服务端的SYN数据包服务端接收到了,SYN在这里反映了服务端也想要和客户端进行连接),此时客户端给服务端发送ACK数据包,用来确认服务端发送个客户端的SYN数据包客户端收到了。
b.双方连接状态
如下图所示:
当客户端发出去SYN数据包时,此客户端处于SYN_SENT状态,这是TCP中的一个状态,说明将SYN发送出去了。当服务端接收到SYN数据包时,服务端状态变成SYN_RCVD。当服务端给客户端发送了一个ACK+SYN数据包时,并且客户端收到了这个数据包,此时客户端的状态变成了ESTEBALISHED表示连接已经建立,此时客户端认为连接已经建立,但是此时服务端并不认为连接是已经建立的,换一句话说当前的连接还在内核的未完成连接队列当中,即使此时服务端去调用accept(),也不会将连接从内核中拿出来,除非当客户端给服务端发送ACK数据包时,服务端的状态变成ESTEBALISHED状态,这时当前连接才会从内核的未完成连接队列中放到已完成连接队列中去。此时在调用accept()时才会获取到连接。
根据上面分析,TCP必须握手三次才能建立连接的原因:
- 从双方连接状态的角度,当两次握手时虽然客户端认为连接已经建立,但是服务端此时状态表示连接并未建立,并且该连接此时还在内核当中的未完成连接队列当中,即使调用accept函数页不会获取到连接;
- 从确认客户端接受能力的角度,当客户端此时发送SYN数据包给服务端,此时服务端收到了,说明他的接受能力是没有问题的;服务端发送ACK_SYN数据包给客户端,表示他的发送能力是没有问题的;而此时客户端收到了这个数据包表示了此时客户端的发送与接收能力是没有问题的,但是如果此时没有客户端发送给服务端ACK数据包,那么服务端并不能确定客户端是否收到了ACK_SYN数据包,它是没有办法检测客户端的接受能力的。那么就服务端就不会认为已经建立连接。
2.2.2针对四次挥手的面向连接
四次挥手并不限定于两者谁先断开连接,双方都有可能先断开连接,在这里我们设置双方为注定断开连接方与被动断开连接方。我们在这里简称a方与b方。
a.双方发送数据包名称
a方在这里会主动发起一个FIN报文,b方在收到FIN报文后会回复一个ACK(表示发送的FIN报文b方收到了),紧接着b方再发送一个FIN报文(回应断开连接),然后a方收到后,在对b方发送一个ACK表示确认。此时四次挥手结束。
b.双方连接状态
当a方发送FIN报文给b方时,此时a方会从ESTEBALISHED状态变成FIN_WAIT_1状态;当b方收到FIN报文后会变成CLOSE_WAIT状态,与此同时会给我们a方回复一个ACK,当a方接收到后会从FIN_WAIT_1变成FIN_WAIT_2状态。
b方会在主动给a方发送FIN报文,此时b放的状态会从CLOSE_WAIT状态变成LAST_ACK状态;当a方收到FIN报文后,此a方时状态变成了TIME_WAIT状态,并且给b方回复了一个ACK,然后b方的状态会变成CLOSED状态,而a方会等一段时间(这段时间我们称之为2MSL)变成CLOSED状态。
c.2MSL
在进行理解2MSL之前,我们必须知道,只有主动断开连接方才会有TIME_WAIT状态。MSL指的是报文最大生存时间,即时自发送方发送出去之后,发送方认为该报文的最大生存时间是MSL。我们还需理解当发送方发送一个数据,需要接收方进行确认的机制叫做确认应答机制。但是为什么要等待2MSL呢?如下图所示:
在b方位LAST_ACK状态下给a方发送了FIN报文,此时a方会再给b方回复发送ACK,假设此时ACK并未传输到b方,而是在过程中丢失了,那么b方就不清楚FIN报文是否到达a方。如果此时b方等待很久没有等到回复的ACK,那么它就会再向a方发送FIN报文,那么a方就会重置它的状态,会再进行等待一个2MSL。我们将这种机制称之为超时重传机制。
2MSL由两部分组成一部分是ack数据的MSL+被动断开连接方重传的FIN报文(两种情形,FIN还未到达主动断开连接方就丢失;主动断开连接放回复的ACK丢失了);如果说等待了2MSL的时间,也没有等到重传的FIN报文,则说明ACK数据一定到达了被动断开连接方,那么主动断开连接方状态变成CLOSED。
d.端口重用
处于TIME_WAIT状态的程序,还需要将端口占用着,等待2MSL之后,在释放调用端口。占用端口的原因是防止被动断开连接方重传FIN报文。但是在这里如果当服务端作为主断开连接方,并且服务端进程还退出了想要快速启动服务端程序就会出现如下问题。
- 服务端程序当中端口是写死的27878
- 服务端有可能就陷入到TIME_WAIT状态,等待2MSL才能将端口释放(进程退出了,但是当前的端口还在被网络协议栈占着)
由于上面的这两点,如果想要快速启动服务端程序时就会报错bind:Address areadly in use.因此我们引入端口重用,直接调用下面端口。
int opt = 1;
setsockopt(listenfd, SOL_SOCKER, SO+REUSEADDR, &opt, sizeof(opt));
- listenfd:套接字描述符
- SOL_SOCKER:套接字选项
- SO+REUSEADDR:重用端口
- opt: opt=1
- sizeof(opt):长度
e.CLOSE_WAIT状态
只有被动断开连接方才会存在的状态。处于CLOSE_WAIT一方需要调用close函数关闭新连接的套接字。如果被动断开连接方存在大量的CLOSE_WAIT状态,那么需要check代码当中是否有阻塞,循环导致被动断开连接无法调用到close。在CLOSE_WAIT状态到LAST_ACK状态中,需要被动断开连接方调用close函数,且被动断开连接方还可以给主动断开连接放发送一些数据。
2.2.3面向连接之包序管理
a.网络抓包
- 在win操作系统下:wireshack,针对网卡进行抓包
- 在linux操作系统下:可以抓任意协议下的数据包,并不只能抓TCP下的数据包。命令范式:tcpdump -i any port [抓取数据的端口] -s 0 -w [将抓取的端口写到的文件]
上面的命令如果去掉port [抓取数据的端口],则没有使用端口进行过滤,那么抓出来的数据包比较大。一般将抓出来的文件通过wireshark进行分析。
在这里注意:想要看到三次握手后的过程,需要先开启抓包,再启动程序。
b.三次握手当中三次的包序号
在三次握手发送数据包的过程当中,在TCP中分别维护了两套序号,一套是客户端在维护,一套是服务端在维护。我们现在来分析一下三次握手当中三次的包序号。如下图所示:
客户端个服务端发送SYN数据包,数据包中携带的序号Seq=0;服务端收到后给客户端回复一个ACK+SYN数据包,包中携带的序号ACK=1+seq=0,此时ACK=1服务端就告诉客户端期望下次发送数据是从1号序号开始的,seq=0服务端之后给客户端发送数据的起始序号是从0开始的。紧接着客户端给服务端发送一ACK,携带的序号为seq=1,ack=1,此时的ack=1是用来确认服务端个客户端发送的seq=0,在这里客户端确认了收到了0号数据包且期望下次数据是从1号序号开始的。
紧接着上面的图,我们再对下图进行分析:
在红色标记部分我们发现客户端给服端发送了两次数据,但是他们的seq都是为1,原因时纯的ACK数据包时不消耗序号的,不消耗序号即意味着不需要对方进行确认。也就是说,在三次握手的时候,最后客户端给服务端发送的Seq=1的序号并没有真正被消耗掉,而是在建立连接之后客户端给服务端发送Seq=1的时候,1号序号才被消耗掉,此时需要服务端确认是否收到该标识符。
我们再分析下图:
结论:在这里我们也要注意TCP在发送数据的时候,数据的每一个字节都会进行标识。
总结:
- TCP连接当中维护了两套序号,客户端维护了一套,服务端维护了一套
- 双方再给对方发送数据的时候,是消耗自己维护的序号
- 接收方再确认发送的数据的时候,是以期望下一个序号的方式进行确认
- 双方在三次握手当中协商各自序号的起始位置。需要注意的是,双方的起始位置不一定从0开始,可以从任意位置开始,只需要后续遵守起始位置进行发送即可
- 服务端发送个客户端的数据是由客户端进行确认,客户端发送给服务端的数据是由服务端进行确认
在这里存在的问题:
三次握手建立之后可以双发可以正常发送数据吗?答:不会。因为在三次握手之后在没有调用accpet函数的时候连接还是处在内核当中的已完成练级队列当中,并没有被调用回来。
2.3TCP协议的协议字段
- 16位源端口,16位目的端口
- 32位序号:用来表示TCP数据的起始序号
- 32位确认序号:ack告知对端,自己期望对端下一次发送数据的时候从哪一个序号开始
- 4位首部长度(1111=>15),他计算出来的是一个数值而并非是字节,首部长度占用字节数量=4位首部长度计算出来的数值*4(1111=>15*4=60字节),因此TCP头部最大为60位字节最小为20位字节。
- 标志位:URG:紧急标志位(MSG_OOB紧急数据,带外数据);ACK:确认标志位;PSH:发送数据标志位;RST:重置连接标志位;SYN:发起连接标志位;FIN:断开连接标志位
- 16位窗口大小:牵扯到滑动窗口机制
- 16位紧急指针:配和URG标志位一起来使用,传输紧急数据
- 选项:MSS:最大报文段长度,他是TCP双方在三次握手过程中协商最大报文长度的大小。客户端会告诉服务端有MSS_cli,服务端会告诉客户端MSS_svr。双方在三次握手的过程中都会发送他们最大报文的长度,而双方采用以最小的MSS作为后续传输数据的时候的最大报文长度,min(MSS_cli,MSS_svr)。它的作用是TCP在传输数据的时候,严格按照MSS进行传输,MSS的大小与本地的网卡息息相关。即MSS+ipheader+tcpheader<=MTU;MTU是最大传输单元(数据链路层对网络数据的限制),每一个网卡都有最大的传输单元他是数据链路层对网络数据的限制他是依照传输电器的特性决定的。因此MSS一定小于MTU。TCP在每次发送数据的时候,数据的大小都不会超过MSS。只用TCP有MSS的限制,UDP是没有的(uint_16)。
2.4可靠传输
2.4.1保证可靠
a.确认应答机制
回复确认数据包的工作,在网络协议栈的TCP协议自己就完成了,不需要程序员的介入。而程序员只需要关心的是recv函数从接收缓冲区拿数据。
b.超时重传机制
当发送方发送了一个数据之后,就会开启一个重传计时器,当重传计数器记录的时间超过重传时间,则会重传该报文。
假设发送方给接收方发送了一个消息,在发送的同时会开启一个重传计时器,重传计时器超时的两种可能,一种是发送方发送消息时消息丢失;另一种是接收方在确认数据报在网络中丢失了。不管是那种情况,对于发送方而言,都不能确定有没有到达接收方。超时时间之后则会重传消息。
那么超时重传的时间是固定的吗?并不是。因为网络的转发能力不是一成不变的。
RTO:超时重传时间 RTT:报文往返时间 RTO:2*RTT
RTT(预测本次数据包的报文往返时间) = RTT(prev前一段时间)*0.9 + RTT(pprev前前一段时间)*0.1
2.4.2保证传输效率
a.提高发送方发送的效率&接收方接受的能力
滑动窗口机制
- 前提:TCP在确认应答机制和超时重传机制下,已经可以保证数据时可靠有序到达对端。但是如果单单只有该两个机制,就会导致TCP的发送发在发送下一个数据之前,会等待上一个数据的确认应答,这样的整体发送效率会比较低。
- 机制:滑动窗口机制允许一次性发送多个分组(每个分组的大小不会超过MSS)到网络当中进行传输,当发送方接收到最早分组的确认应答之后,窗口可以向后滑动,包括下一个可以发送的分组。
- 实现效果:滑动窗口机制提高发送方和接收方的吞吐能力,提高了双方传输的效率。
问题:TCP在双方发送数据的时候,TCP发送维护的窗口大小时一成不变的吗?
首先我们理解滑动窗口是指允许一次性丢到网络当中分组的集合,而分组的个数就是窗口的大小。发送方窗口是动态变化的,取决于接收方通告的窗口大小。
- 前提:TCP协议字段当中"16位窗口大小";消息发送方告知消息发送方,消息接收方的接受能力的字段。
- 当TCP的接收方接收到数据之后,会将给数据先缓存在TCP的接收缓冲区当中。TCP的接收缓冲区的大小并不是无限制的,一旦应用层不调用recv从接收缓冲区当中获取数据时,就会导致TCP接受缓冲区随着一直接收发送方发送的数据而接受缓冲区的大小逐渐变小。
结论1:接收方通过窗口大小,告知发送方字节的接受能力;接收方在发送数据的时候,会按照接收方通告的接受能力动态的调整自己的发送数据量(窗口大小)。这就是在滑动窗口地下的流量控制。TCP流量控制(TCP的流控制),就是TCP通过当前协议字段当中的"窗口大小"字段来控制双方发送数据的数据量。
结论2:如果双方的发送数据的两不加以控制,则可能会导致接收方由于接收能力不足,而将发送方发送的数据丢弃掉,触发超时重传机制。如此往复,网络中就会充斥大量的重传数据包,占用宽带资源。导致整个网络的转发能力变差。
0号窗口
如上图所示, 当发送方接收到"0号窗口"之后,就会将滑动窗口当中的窗口大小调整为0,暂时不发送数据到对端。在这里有两种恢复发送方给接收方发送数据。
- 发送方主动给接收方发送窗口探测包,询问接收方的接受能力。注意,窗口探测包的数据的大小位固定的1字节。
- 接收方主动给发送方发送窗口更新通知。
滑动窗口丢包
(1)丢失某个分组的ACK
当主机A分了六组发送数据,但是此时主机B给出的确认应答却在传输中丢失了,如上图在确认序号一个是1001号时的确认应答丢失了,但是下一个确认应答达到对端了,那么2001之前的所有序号数据主机B都受到了,就不需要主机A进 行重传了。当窗口当中某个分组的ACK丢失后,可以通过收到的高确认序列号来确认丢失ACK的分组数据是否到达对端。
(2)丢失某个分组数据
结论:如果数据丢失则一定要重传。 如图所示,接收方的接收缓冲区有一个功能,它会将缺失的数据之后的所有数据缓存到接收缓冲区当中,虽然接收方的接收缓冲区已经拿到这些数据,但在recv()进行调用时不能被拿走。不被拿走是因为当前前面缺失了数据,如果他拿走了这些数据,那么TCP就没有保证数据的有序,那么在这里递交给应用测的数据不是有序的,但是TCP是面向字节流的,就会导致应用层不会知道数据的顺序是什么,那么处理的数据就不是唯一的。
拥塞控制机制:慢启动,拥塞避免,快重传,快恢复
拥塞控制+流量控制:作用TCP的双方对发送数据的速率,提高了TCP的发送速率。
问题:一开始就要按照滑动窗口的大小发送数据吗?
否,没有考虑到网络的转发能力是否能够满足一次性传输(转发)窗口的大小。
- 网络传输的时候,数据实在共享网络当中进行传输的。换句话数,网络被多个网络收发双方所使用。同时在转发多个人数据。
- 网络链路当中的路由器/交换机,转发能力是有上限的。
- 网络链路当中的传输介质(光纤/双绞线)是有传输上线的。
结论:TCP双方在发送数据的时候,为了不是因为网络原因,频繁的重传,则TCP双方在发送数据的时候,也考量网络转发能力。
拥塞控制机制
他本质也是在控制发送方发送数据的量。因此最终发送的数据量=min(发送方的滑动窗口大小(取决于接收方的接受能力),拥塞控制机制当中的拥塞窗口大小(取决于网络拥塞程度))。
- 慢启动:TCP通信双方在建立连接之后,先发送少量的数据(拥塞窗口数据),探测网络的转发能力(根据TCP数据包的往返时间),根据网络转发能力,主动调节拥塞窗口大小。(拥塞窗口的大小位1个分组(1个MSS))。拥塞窗口的大小随着传输轮次的增加呈指数增长。慢开始门限时在拥塞窗口大小小于慢开始门限的时候,是呈指数增长。当拥塞窗口大小超过慢开始门限,则切换为拥塞避免算法。拥塞窗口大小从指数增长到线性增长的原因,往网络中丢入的数据是依次加大的,但是网络的转发能力是有限的,就会导致TCP数据丢包(一旦发现有网络数据丢包了,发送方就会认为网络拥塞了,立即下调拥塞窗口大小)。
- 拥塞避免:随着轮次的增加,拥塞窗口大小呈线性增长(一次交1个分组)
- 快重传:当发送方发送的网络数据包丢失后,发送方还没有触发超时重传机制的时候,由接收方快速的确认丢失报文的起始序号,告知发送方该报文丢失,则发送方不需要等到超时之后再重传。它不会阻塞发送方的发送窗口的向后滑动的过程,也就是不会阻塞发送方发送的速率。
- 快恢复:早期TCP发现网络拥塞之后,是将拥塞窗口调整为1(即一个分组),从慢开始重新增长拥塞窗口的大小,重新调节面开始门限(新的慢开始门限 = 拥塞窗口发生时,拥塞窗口的一半);现在的做法是快恢复,一旦发现网络拥塞,则计算机的慢开始门限从新的慢开始门限执行拥塞避免算法。
b.网络的转发能力
- 延时发送机制:NAGLE算法:如果当前发送方存在少量数据需要发送,则稍微等一下,等待数据量变大,在统一进行发送。但是工业场景中都是禁止使用演示发送的,比如说机械制造业,医疗领域,金融领域。
- 捎带应答机制:运用于双方都想给对方发送数据的场景,快速的进行交互,就将ACK放到PSH数据包当中携带给对方
- 延时应答机制:接收方收到了数据,为了给消息发送通告更大的窗口大小,等待一段时间,如果在这段时间内,应用层将数据从(recv函数)接收缓冲区当中接收走后,接收方在恢复确认的时候,就可以给发送方通告更大的窗口大小了。
- 心跳机制(TCP保活机制):判断空闲连接的双方是否是正常状态。如下图,在连接空闲的时候,就会启动一个保活计时器,记录连接空闲的时间,一旦当连接的空闲时间超过2小时,则则主机BDE TCP协议会主动给主机A发送保活探测包(心跳包),如果主机B一直没有收到探测包的应答,则会每75s发送一次,总共发送10次。如果10次探测包都没有应答,则认为对端已经处于不正常状态,则主动断开连接。如果有应答,则认为连接正常。重新启动保活计时器进行计时。
2.5面向字节流
- 从发送数据的角度理解:应用层的应用程序在发送数据的时候,时调用send函数将应用层数据递交给传输层的TCP协议,掉交给TCP协议之后,数据是暂时保存在发送缓冲区当中。但是一定不要认为TCP在发送数据的时候,是按照应用程序调用send函数的规律来进行发送的。TCP发送的数据一定是小于MSS,有自己发送数据的规律、
- 从接收的角度理解:但数据到达接收方的接收缓冲区之后,接收方的应用程序调用recv函数可以接收任意字节。
面向字节流服务会导致数据之间没有边界,产生TCP粘包问题。这就引出一个问题,如何解决TCP粘包问题。在应用程自定制协议-->应用层头部+应用层数+间隔符。(完整讲解)
以上是关于Linux传输层协议 — UDP协议的主要内容,如果未能解决你的问题,请参考以下文章