Linux网络 - UDP/TCP底层

UDP

udp因为无连接,数据报格式发送,不保证可靠性,因此设计的很简单,我们来看看UDP是如何发送上层下来的数据报的。

UDP协议格式

首先我们看看UDP协议如何封装的。封装的也比较精简,因为传输层在网络层之上,此时已经可以通过网络层从这个主机发往另一个主机了,关键需要知道具体主机的哪个进程,因此需要记录端口号。同时记录UDP长度,校验和是用来最后进行数据校验的,如果有错误就会直接将数据报丢弃

16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度;

UDP特点

大家可以就把UDP当做寄信来看待。那么首先就是可以一次性发送完数据,然后就是寄信是不可靠的,可能你寄几次,中途有的先到有的后到,然后就是可能寄信途中信丢了。如果邮箱满了,就满掉出来了。所以就是无连接、不可靠、面向数据报。

面向数据报就是不会对从应用层传下来的数据做分割,而是一次性写到"信"里面,然后发送出去

UDP缓冲区

UDP没有真正意义上的发送缓冲区,因为写入高发送缓冲区的数据进步就是立即发送到网络里面。

UDP是有接收缓冲区的,但是不保证顺序,满了就丢弃新的

UDP注意事项

我们注意到,UDP协议的整个长度是16位,这就导致最大发送长度是64KB,超过这个长度的数据只能用户自己在应用层进行分包。

基于UDP的应用层协议

NFS:网络文件系统

TFTP:简单文件传输协议

DHCP:动态主机配置协议

BOOTP:启动协议(用于无盘设备启动)

DNS:域名解析协议

TCP

TCP协议格式

端口号

不多说了

序号和确认序号

是确认数据是否接收正确的机制,这个我们在下面详说。

4位首部长度

这个是记录除了数据以外,头部的长度。它是记录头部有多少层(一层432位)。我们可以看上面的图片,除了选项,上面5层是固定长度都会有的。而4位最多表示15层,因此返回就是4*5,15即20,60的范围。

6位标志

URG:紧急指针是否有效

ACK:确认号是否有效

PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走

RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段

SYN:请求建立连接;我们把携带SYN标识的称为同步报文段

FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段

16位窗口大小

这个也后面再说

16位校验和

对整个报文的校验,如果不通过这说明数据有问题,就要进行相关的操作

16位紧急指针

标识哪部分的数据紧急

选项

这个暂时也不看

确认应答机制(ACK)

在TCP里面,对发送的报文,对方都必须要给予应答,应答使用ACK的标志。

TCP对发送缓冲区的每个字节都进行了编号,即为序列号。

每个ACK应答都带有确认序列号,意思是告诉发送者,我已经接收到了哪些数据,下一次从哪里开始

我们说详细点,如果ACK的确认序列号是N,则说明N之前的数据都已经接收到了。TCP的每个报文是可以连续发送连续接收的,如果有几个ACK报文丢失了,但是最新的应答没有丢失,且回复的是都收到了,那么之前的ACK报文也可以不管。

如果连续发送了1-1000 1001-2000 2001-3000 ,最后只有1-1000和2001-3000的收到,最后也只会回复1001

捎带应答

为什么有序列号和确认序列号呢?这里是为了在回复的同时可以发送自己的数据报。例如别人再电话里面回答,她说听到了,也可以加上自己的问题。这就叫捎带应答

超时重传机制

A发送给B后,可能因为一些网络原因导致没有发送到B,如果A在一个时间段内没有收到对应的答复就会重新发送这个包

但这里有多种情况,也可能是B收到了,但是在发送ACK的时候,丢包了

此时B会收到很多重复的包,比如上图,收了两次同样的1-1000数据包。此时32位序号就起了去重作用。

重传时间

那么这个超时重传的时间具体是多少?应该找到一个不大不小的时间,如果太长可能导致重传效率太低,如果太短,可能会频繁发送报文。

为了保证最大效果的通信,会动态计算这个值。

连接管理机制

正常情况下,TCP要经过三次握手建立连接和四次挥手机断开连接

三次握手

我们和socket编程结合起来,首先服务器通过socket函数创建监听套接字,通过bind绑定ip和端口,然后启动监听,此时会在内核维护连接队列,并通过accept将已经连接的拿回。客户端通过socket函数直接创建接收套接字,通过connect自动被操作系统分配端口,然后与处于监听状态的服务器做连接。此时先是客户端发送SYN的连接请求,然后服务端正常收到报文后返回ACK+SYN的捎带应答,客户端收到后返回ACK,此时客户端已经知道对方收到连接了,因此这边可以确定建立连接。服务端也需要收到报文ACK才能真正确定建立的连接,否则会超时重传,如果此时还收到对应客户端的报文数据,会直接返回RST的重新建立连接的报文。

这里要再次说一下,listen函数底层会创建一个半链接的队列和连接队列 ,半连接队列是给只收到连接消息但没收到回复的连接管理的,accept是从连接队列里面拿连接的。

那么就有两种情况,服务端没有收到第三次握手的应答,那么就会重发SYN+ACK,并且会对客户端发送的后续报文返回RST ,这两种报文最终到达客户端的顺序是不确定的,如果是SYN+ACK先到,那么客户端就会重新发送SYN+ACK的报文,如果是RST的先到,客户端就会关闭,connect会返回-1,错误码就是连接被重置。如果客户端多次超时重传(Linux下是5次)都没收到报文,就会将这个半连接从半连接队列里面删除。因此根本没有被accept处理过。

四次挥手

如果是客户端主动关闭连接:首先会给服务端发送FIN的终止报文,服务器收到后立马回复,但此时不会进行捎带应答,因为可能此时可能服务器还没有处理好对客户端的数据,等数据处理好后,才会发送给客户端FIN的报文。客户端第一次发送FIN的时长和TCP的重传机制有关,第二次是tcp_fin_timeout的时间,一般是60s,第三次是两倍的2*MSL的时间,MSL是报文在网络里面传递的最长时间,一般MSL=60,如果过去2*MSL还没有收到服务器的重传,就会直接断开。

如果是服务端先关闭连接,其实就是对称的四次挥手,这就不讲了。

正确close

我们可以看到,第三次挥手是需要用户层主动调用close才能释放的,而我们要判断对方关闭连接是用读函数返回值为0判断的,但此时是CLOSE_WAIT的状态,需要用户调用close才行。如果不主动调用close,那么就会导致fd一直被占用不被释放,最终可能导致文件描述符满,不能接收新的连接。

这算是一种资源的泄露,因此关闭close是很重要的事情

服务器关闭立即重启的问题

如果服务器关闭立即重启,可能上次关闭前的报文还在网络中传输,此时系统会把这个端口关闭2*MSL的时间,自动清理网络中的旧报文,是2*MSL的原因是服务端最后关闭前可能发送了报文,进过来回的传递最大是2*MSL。可以使用setsockopt函数将其复用端口开启

滑动窗口

如果是发一个报文回复一个报文,那么最后就会导致效率变得很慢,因此有了连续发连续收的机制

上图效率慢

真正使用如上操作

窗口大小指的是无需等待回复的最大发送数据值,图中是4000,发送这四个段是,无需等待ACK可以直接发送。收到第一个ACK后,窗口会向后滑动,然后继续发送新的报文

而这一系列操作都是在发送缓冲区里面完成的。窗口越大,网络的吞吐率就越高

这种多包发送的情况,也会有一些问题,就是如何重传。

情况一:

数据报递达,ACK丢了。这种情况不必多担心,只要越靠后的ACK没丢,那么就越没事,因为应答序列返回的是下次发送的地点,如果后面报文收到了,说明下次应答序列可以从最新的开始。但是如果是偏前面的报文没丢,后面的丢了,那么就得从前面的开始重传。例如1-1000 1001-2000 2001-3000的,如果最后只收到1001,后面两个都丢了那么就只能从1001开始。如果前面两个丢了,后面3001没丢就从3001开始。

情况二:

数据报直接丢了

如果此时发送的某个包丢了,那么后面的报文都会发送这个丢弃报文起始的应答序列。让A主机从这里重传。例如上图要返回1001,那么如果收到的返回1001的应答小于三次就会超时重传

快重传

如果连续三次收到了同样的会马上进行重传,这叫快重传

流量控制

接收端的接收缓冲区是有限的,如果不加限制的发送,可能就会导致接收缓冲满,此时就会丢掉后面来的包,发送端收不到回复就会重传

接收端将自己能够接收的数据大小放入"窗口大小"的字段,通过ACK发送出去,窗口大小的字段值越大,说明能接收的报文越大,那么就会发更多的报文,如果越小,此时就会减缓报文发送的速度。

如果接收端的窗口大小为零了,此时发送端就不会再发送数据了,但是会是不是发送一个窗口探测包,返回报文告诉发送端实时的窗口大小。

接收端通过16位的窗口大小告诉对端。难道最大的接收缓冲区就只有2^16=65535字节吗?答案并不是,TCP首部还包含一个窗口扩大因子M,实际的窗口大小是窗口大小左移M位。

拥塞控制

如果每次盲目开始就往网络里面发送大量的数据报,可能此时网络拥堵导致堵上加堵。因此TCP采用慢启动的方式,从一次发一个报文开始,按指数形式往上增加

虽然是慢启动,但是增长的速度特别快。当然不能一直这么成倍的增长,会有一个阈值,超过这个阈值就是线性的增长。

ssthresh的初始值是16,之后就是网络拥塞是报数的一半。超过ssthresh就会线性增长,最后在一个阈值停下,这个阈值= min(网络拥塞,对方接收能力)

大家一定要和滑动窗口的长度结合起来,结合到一起。

因此滑动窗口实际的大小是由拥塞控制决定,拥塞控制又受到网络发送能力和对方接受能力的影响。

延迟应答

如果接收数据后立即返回,此时的窗口可能比较小,假设此时窗口剩余500K,总窗口1M,如果上层处理数据很快,10ms内拿了所有数据,那么我们就可以返回1M的窗口剩余。

一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高.我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;

面向字节流

如果发送的字节太长,会拆分成多个报文发送,如果字节太短,可能会等待,等超时了或者长度够了再发送。因此接收端获取的数据并不是一个完整的,可能一下发来了很多个,但是一个报文都没组成。所以TCP不像UDP必须一个sendto对应一个recvfrom,而是可以一个send对应多个recv或者多个send对应一个recv

黏包问题

因为面向字节流的原因,就会产生黏包问题。比如读取的时候刚好有一个报文流加半个其他报文流,那么我们需要自己在上层将两个报文分开。那么我们需要明确两个报文的边界,最简单的做法就是发送报文的时候,还将报文的大小用一个固定字节存储起来,这样就会先解析读取当前报文的长度,那么我们就可以分包了。

TCP异常情况

进程终止:进程终止会释放文件描述符,即发送FIN,和正常关闭没区别

机器重启:和进程终止相同

机器掉电/断网:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset.即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在.如果对方不在,也会把连接释放.

TCP小结

可靠性:

校验和

序列号(按序到达)

确认应答

超时重发

连接管理

流量控制

拥塞控制

提高性能

滑动窗口

快速重传

延迟应答

梢带应答

其他

定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)

基于TCP协议

HTTP HTTPS SSH Telnet 等

TCP和UDP对比

TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;

UDP用于对高速传输和实时性要求较高的通信领域,例如,早期的QQ,视频传输等。另外UDP可以用于广播;

UDP实现可靠传输

在应用层实现TCP的序列、应答序列、超时重传、确认应答、连接管理等,根据不同的场景来实现滑动窗口、延迟应答、快速重传、捎带应答等操作。这种应用层的实现可以直接决定实现哪些,去掉哪些模块来动态分配。

相关推荐
UIU11430 分钟前
递归算法与汉诺塔
c++·学习·算法·c#·递归
Fruit_Caller1 小时前
static
开发语言·c++
律宏阔6 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔6 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴6 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千6 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei376 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei376 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei376 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW6 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux