秒懂Linux之传输层协议UDP/TCP

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

 

前言

传输层协议UDP

再谈端口号

UDP协议端格式

UDP的特点

面向数据报

UDP的缓冲区

传输层协议TCP

TCP协议段格式

确认应答(ACK)机制

TCP标志位

超时重传机制

连接管理机制

理解TIME_WAIT状态

理解CLOSE_WAIT状态

流量控制

滑动窗口

拥塞控制

延迟应答

面向字节流

粘包问题

TCP异常情况


前言

在前面套接字的讲解中我们已经接触到了UDP/TCP它们各自的使用方法,而本次我们来深入了解它们背后蕴含的原理~

传输层协议UDP

再谈端口号

端口号 (Port) 标识了一个主机上进行通信的不同的应用程序

ip标识互联网中唯一主机,而端口号则帮助我们寻找对应的服务进程~

UDP协议端格式

  • 16UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度;
  • 如果校验和出错,就会直接丢弃;

理解为报头+有效载荷即可~

UDP的特点

  • 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接;
  • 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息;
  • 面向数据报:不能够灵活的控制读写数据的次数和数量;

其实不可靠也是可利用的一点,这意味着其成分简单容易实现~

面向数据报

应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并;
UDP 传输 100 个字节的数据 :
如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节;而不能循环调用10次recvfrom,每次接收10个字 节;

UDP的缓冲区

  • UDP没有真正意义上的发送缓冲区.调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作;
  • UDP具有接收缓冲区.但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃;

———————————————————————————————————————————

最后提出一个问题:在OS内一定会堆积大量暂时未处理的报文,那么我们应如何对这些报文进行管理呢? ——先描述,后组织~

描述报文内部属性,再用数据结构组织起来~

封装:当经过信息经过传输层时就要添上传输层的报头,并通过移动指针填写报头内容~

解包:先提取出内容(udphdr*)head->srcport...  再移动指针head+=sizeof(udphdr)

_____________________________________________________________________________

UPD说实话上面的都是一些喜闻乐见的简单概念吧,而接下来的TCP就有些难度了~

传输层协议TCP

TCP 全称为 " 传输控制协议 (TransmissionControlProtocol"). 人如其名 , 要对数据的传输进行一个详细的控制~

TCP协议段格式

先来讲讲首部长度,它是包含了报头+选项的大小~范围在[0,15]基本单位4字节。即[0,60]字节内,由于报头固定长度为20字节,那么选项的可变范围就是【0,40】字节了。

假设首部长度为20字节,那么换算单位后就是x*4 = 20,x = 5==》0101~

确认应答(ACK)机制

前面了解过TCP要保证可靠性,即保证发出的信息要被应答,这样才能说明对方成功收到了信息~

最开始的应答机制仅仅是为了回应发送端,而这是双方OS都会自动去完成的。但这样有一个弊端,客户端必须得等待应答才可以继续发送,服务端也一样,大大影响通信效率~

后续版本中引入了序号以实现同时发送多条信息~

发送多条信息的时候携带各自的序号,服务端在接收消息作应答时会填入确认序号(在其序号基础上+1),并且客户端在接收应答消息时是根据它们的序号进行排序的~而不是根据处理信息的时间长短~

ps:关于为什么报头要携带序号和确认序号的原因是因为服务端不仅仅要应答,也是要发送信息的~它发送信息肯定也要有它自己的序列号,也要有能让别人应答的确认序号。

另外要稍微补充的一个知识点:发送数据可不仅仅是“你好”这样明文式发送,而是带有协议段格式的结构体类型发送出去的~

TCP 将每个字节的数据都进行了编号 .即为序列号 新的序号 = 确认序号+我要发送的长度~
每一个 ACK 都带有对应的确认序列号 , 意思是告诉发送者 , 我已经收到了哪些数据 ; 下一次你从哪里开始发.其实把序列化对应到缓冲区里就很好理解了~

———————————————————————————————————————————

当有一方既想要做确认应答,又需要发数据的时候二者可以合二为一变成捎带应答~

序号:按序到达,应答和确认对应

确认序号&&序号:同时向对方发数据的同时也在做应答。

TCP标志位

6 位标志位 :
  • URG: 紧急指针是否有效
  • ACK: 确认号是否有效
  • PSH: 提示接收端应用程序立刻从 TCP 缓冲区把数据读走
  • RST: 对方要求重新建立连接; 我们把携带 RST 标识的称为复位报文段
  • SYN: 请求建立连接; 我们把携带 SYN 标识的称为同步报文段
  • FIN: 通知对方, 本端要关闭了, 我们称携带 FIN 标识的为结束报文段

为什么TCP要有标记位呢?其存在的意义是什么?

而标记位置1的这些操作都是由OS自主完成的~

超时重传机制

举例以下两种情况:

主机 A 发送数据给 B 之后 , 可能因为网络拥堵等原因 , 数据无法到达主机 B;

主机 A 未收到 B 发来的确认应答 , 也可能是因为 ACK 丢失了 ;

这种情况会堆积大量重复的数据,不过序号本身有去重的效果所以不用担心。

因为没有收到对方报文而超时的情况,找到一个最小的时间,保证"确认应答一定能在这个时间内返回"TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间.

连接管理机制

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

在学习之前得先确认一点:三次握手不是一定确保能够建立好连接,而是经历完三次握手,CS双方都认为连接建立好了!

这里举例一下可能失败的场景:

当ACK丢失时,客户端发出即认为连接建立成功,因为它没有应答只能默认发出即成功,而服务端这边因为收不到应答是无法建立连接的,CS双方认知产生差异,最后只能靠超时重传来解决问题~

最后由服务端发给客户端一个携带RST标记位的报文,让双方重新进行三次握手~

为什么要三次握手:

需要确保信道(网络)是健康的,三次握手使得双发都会有确定的一次收发,用来确认其全双工机制

确保双方OS是健康且愿意通信的,因为这几次携带标记位的报文中都是OS自主完成的。

ps:其实也可以理解为4次握手,明显中间的SYN+ACK是捎带应答~

为什么要四次挥手的原理跟上面相同~        

最后是关于各个状态码的解释:

服务端状态转化 :
[CLOSED->LISTEN]服务器端调用listen后进入LISTEN状态,等待客户端连接;
[LISTEN->SYN_RCVD]一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文.
[SYN_RCVD->ESTABLISHED]服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了.
[ESTABLISHED->CLOSE_WAIT]当客户端主动关闭连接(调用close),服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT;
[CLOSE_WAIT->LAST_ACK]进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
[LAST_ACK->CLOSED]服务器收到了对FIN的ACK,彻底关闭连接.
客户端状态转化:
[CLOSED->SYN_SENT]客户端调用connect,发送同步报文段;
[SYN_SENT->ESTABLISHED]connect调用成功,则进入ESTABLISHED状态,开始读写数据;
[ESTABLISHED->FIN_WAIT_1]客户端主动调用close时,向服务器发送结束报文段,同时进入FIN_WAIT_1;
[FIN_WAIT_1->FIN_WAIT_2]客户端收到服务器对结束报文段的确认,则进入FINWAIT2,开始等待服务器的结束报文段;
[FIN_WAIT_2->TIME_WAIT]客户端收到服务器发来的结束报文段,进入TIMEWAIT,并发出LAST_ACK;
[TIME_WAIT->CLOSED]客户端要等待一个2MSL(MaxSegmentLife,报文最大生存时间)的时间,才会进入CLOSED状态

理解TIME_WAIT状态

有时候我们主动关闭服务端后再次启动时发现并不能用之前的端口号了,这是因为其处于 TIME_WAIT状态,连接还没完全断开。
以下是规定说明:
TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL的时间后才能回到CLOSED状态.
想一想,为什么是TIME_WAIT的时间是2MSL?
MSL TCP 报文的最大生存时间 , 因此 TIME_WAIT 持续存在 2MSL 的话 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失 (否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据 , 但是这种数据很可能是错 误的);同时也是在理论上保证最后一个报文可靠到达 ( 假设最后一个 ACK 丢失 ,那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了 , 但是 TCP 连接还在 , 仍然可以重发LAST_ACK);
反正核心就是确保之前被堵塞的报文在重启服务器前能完全消失,另一个就是预防ACK丢失时有重新发送的时间。

理解CLOSE_WAIT状态

该状态是一种准备关闭(close)的趋势,当客户端发送断开请求后确认并应答进入该状态。而接下来它也要执行close了~ 
当服务端并没有去关闭close导致服务器上出现大量的CLOSE_WAIT状态,导致四次挥手没有正确完成.这是一个BUG.只需要加上对应的close即可解决问题.

流量控制

接收端处理数据的速度是有限的 . 如果发送端发的太快 , 导致接收端的缓冲区被打满 , 这个时候如果发送端继续发送, 就会造成丢包 , 继而引起丢包重传等等一系列连锁反应 .
因此 TCP 支持根据接收端的处理能力 , 来决定发送端的发送速度 . 这个机制就叫做 流量 控制 (FlowControl) ;
  • 接收端将自己可以接收的缓冲区大小放入TCP首部中的"窗口大小"字段,通过ACK端通知发送端;
  • 窗口大小字段越大,说明网络的吞吐量越高;
  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;
  • 发送端接受到这个窗口之后,就会减慢自己的发送速度;
  • 如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端.
其中窗口推测与窗口更新是同步进行的,保证效率~
这里我们提出一种场景:我们了解到请求在接收缓冲区都是排序排好的,要是某一天我们发送了大量的请求后突然想放弃这些请求时该如何做呢?
我们想让放弃报文作为优先被处理,可以使用URG标志位,它会让16位紧急指针指向1字节大小的紧急数据,然后我们用send等函数去读取其指向的内容,并约定好如内容为1代表放弃上传,内容为2代表暂停上传...

滑动窗口

前面场景中这种串行方式效率低下

所以我们改用并行的方式,可我们如何知道一次性可以发几条呢?要是太多导致缓冲区溢出丢包怎么办?而这正是下面我们要学习的内容——滑动窗口

当然在不考虑网络的情况下,滑动窗口的大小一般是:对方缓冲区中剩余的空间大小,一定是动态变化的,不是像图中保持4000字节大小并且其数据都是无需等待确认应答就可以发送的!

如果遇到超时重传时,滑动窗口会对没有收到应答的报文继续保存,不移动即可直到收到应答才进行滑动。

要建立一个共识,滑动窗口是会随着缓冲区剩余空间大小而为之变化的。

以上是滑动窗口的基本变化流程~

接下来我们来分析一下在丢包情况下滑动窗口的应对情况:

这里我们需要重谈确认序号的意义:确认序号之前的报文全部收到了!

这代表即使我们没有收到1001-2000的应答(应答报文丢失下),但只要它收到了2001-3000的应答,那就一定代表1-3000的发送数据全收到了,因为这就是确认序号的意义所在,它不会做它无法确认的事情。

那么如果是发送报文1001-2000丢失呢?即使后续的发送报文收到了也绝对不敢发送其确认应答,因为从确认序号这里就无法保证,并没有实际收到1001-2000的发送报文。它只敢发送1001的确认序号~

当我们连续发送多个报文出现报文丢失时,会触发高速重发控制(也叫"快重传")机制,发送端主机连续三次收到了同样一个"1001"这样的应答,就会将对应的数据1001-2000重新发送;

三个以下的话就正常超时重传。

所以这里看似三种情况,其实都可以归结于第一种,不管怎样最后都是利用快重传或超时重传一个个补发数据,当然核心仍在于确认序号的意义非凡。

拥塞控制

虽然 TCP 有了滑动窗口这个大杀器 , 能够高效可靠的发送大量的数据 . 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.        
TCP 引入 慢启动 机制 , 先发少量的数据 , 探探路 , 摸清当前的网络拥堵状态 , 再决定按照多大的速度传输数据;

拥塞就是在考虑网络的情况下对滑动窗口进行限制:

滑动窗口 = min(接收方的窗口(接收能力),拥塞窗口)。在网络堵塞情况下,接收方能力再大也得看拥塞窗口,因为其保证数据在网络通道能否顺利传递。同样的在网络良好的情况下也得看接收方能力,网络再好也得看其接收程度。二者共同决定了滑动窗口的大小~

像上面这样的拥塞窗口增长速度,是指数级别的."慢启动"只是指初使时慢,但是增长速度非常快.这肯定是不正常的,因为网络也是会变化的,怎么可能会让拥塞窗口越来越大。

 所以后面引入了更合理的拥塞窗口变化方案:

  • TCP开始启动的时候,慢启动阈值等于窗口最大值;
  • 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;
  • 少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
  • TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案.

延迟应答

如果接收数据的主机立刻返回 ACK 应答 , 这时候返回的窗口可能比较小 .所以有了延迟应答的机制,稍微延迟一会这样缓冲区就会有更大的空间以便于接收后面更多的数据
数量限制 : 每隔 N 个包就应答一次 ;
时间限制 : 超过最大延迟时间就应答一次 ;

面向字节流

由于缓冲区的存在 ,TCP 程序的读和写不需要一一匹配 , 例如: 100 个字节数据时 , 可以调用一次 write 100 个字节 , 也可以调用 100次write, 每次写一个字节 ;
100 个字节数据时 , 也完全不需要考虑写的时候是怎么写的 ,既可以一次read100 个字节 , 也可以一次 read 一个字节 , 重复 100 ;
有缓冲区的辅助下TCP在发送数据与读取数据是很自由的~
顺便引出两个问题:
第一次在不知道对方缓冲区大小的情况下要发送多少数据?
为什么每次都是以1000字节发送的?而不是更多的字节?
在我们进行三次握手的时候是会有报头交换的,这里面就包含了16位窗口大小信息,这样发送端就可以在没发数据前就知道对方的缓冲区大小
而不选择更大的字节发送是因为下面的数据链路层本身就会限制大小,所以1000字节差不多是最合适的发送限度了。

粘包问题

首先要明确 , 粘包问题中的 " ", 是指的应用层的数据包 .
其实就是多个包粘连在一起,对于上层而言它们就是连续的字节数据,所以我们需要有分割的手段,就类似之前学习http协议时对其报文的拆解一样~

TCP异常情况

  • 进程终止:进程终止会释放文件描述符,仍然可以发送FIN.和正常关闭没有什么区别.
  • 机器重启:和进程终止的情况相同.
  • 机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset.即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在.如果对方不在,也会把连接释放.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值