TCP是面对字节流的,发送数据就要先将报文写到tcp发送缓冲区发送缓冲区本质也是数组,因此每个字节天然的就有了数组的下标序号本质上就是发送的材料块的最后一个字符的下标我们一次性发送了多个材料,服务器也会回我们多次响应,那我们如何区分响应是对应了哪个素材呢–确认序号确认序号,填充的是,收到报文的序号+1,比如一个报文的序号是1000,那么应答的确认序号就是1001表示确认序号之前的

文章目录

一、UDP协议

1.1UDP协议端格式

HTTP协议的报头呈以下格式

在这里插入图片描述

  1. UDP将报头和有效载荷做分离采用的是定长报头
  2. 根据目的端口号可以知晓向上交付给哪一个进程
  3. 数据少缺等问题UDP通过16位UDP检验和来校验
  • 16位UDP长度,表示整个数据报(UDP首部+UDP数据的最大长度)
  • 如果检验和出错,就会直接丢弃

1.2UDP的特点

UDP传输的过程类似于寄信

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

1.3面向数据报

应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并

用UDP传输100字节的数据:

  • 如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节,而不能循环调用10次recvfrom,每次接收10个字节

1.4UDP的缓冲区

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

UDP的socket既能读,也能写,这个概念叫全双工

1.5UDP使用注意事项

UDP协议有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64K(包括UDP首部)

  • UDP不保证数据报的送达顺序,应用层需要处理乱序问题
  • 缺乏流量控制和拥塞控制,大量发送可能导致网络拥塞
  • 在高可靠性要求的场景中,需要应用层实现确认机制和重传策略

1.6基于UDP的应用层协议

  • NFS:网络文件系统
  • TFTP:简单文件传输协议
  • DHCP:动态主机配置协议
  • BOOTP:启动协议(用于无盘设备启动)
  • DNS:域名解析协议

还包括自定义的程序时的应用层协议

二、TCP协议

TCP称为"传输控制协议",对数据的传输进行一个详细的控制

  1. TCP在传输层拥有发送缓冲区和接收缓冲区,所有TCP是全双工的,允许通信双方在同一时刻既能发送数据也能接收数据,两个方向上的数据传输互不影响
  2. 我们在代码里写的buffer缓冲区本质就是用户级缓冲区
  3. 调用系统调用写入接口本质就是将用户级缓冲区的数据拷贝到传输层的发送缓存区当中
  4. 至于数据什么时候发,发多少,出错了怎么办由TCP协议自主决定

2.1TCP协议段格式

在这里插入图片描述

2.1.1 4位首部长度

  1. TCP报头的前20字节叫做TCP的标准报头
  2. 4位首部长度表示报头的总长度是多少(包含选项),4位首部长度的范围就是[0,15],4位首部长度计算的时候,有基本的大小单位:4字节,大部分我们不填选项的话,4位首部长度就是5(0101)
  3. 报头和有效载荷做分离的情况就是先读取前20个字节,根据20个字节里的4位首部长度算出来实际的总大小再减去20,剩下的就是选项大小–固定长度(20个字节)+自描述字段

2.1.2 16位窗口大小

在这里插入图片描述

  1. 客户端和服务器基于TCP协议进行通信互发消息的时候,发送的是完整的TCP报文携带完整的TCP报头
  2. 接收缓冲区是有固定大小的,当应用层因为某些原因没有把数据从接收缓冲区读取上来时,客户端一直给服务器发消息,服务器来不及接收,进而会导致数据的大面积丢包的情况
  3. 当服务器缓冲区剩余空间很小甚至没有的时候,我们就需要控制客户端发送速度,规避大面积丢包–流量控制
  4. 虽然TCP在面对丢包的时候有超时重传的机制,但是我们还是要规避正常数据发送可能会出现丢包问题的情况(外卖送丢了虽然会再赔一份,但是重要的是你还饿着)
  5. 我们先知道我们发送给对方的数据对方有没有接收是会给我们发送一个应答的(TCP报文)
  6. 对于发送方来讲,发送速度由对方的接收缓冲区中剩余空间的大小决定
  7. 对方的剩余空间大小体现在对方发来的应答的TCP报文的16位窗口大小
  8. 16位窗口大小–填写的是自己的接收缓冲区中剩余空间的大小

2.1.3 序号和确认序号

2.1.3.1可靠性保证机制
  1. 当我们向对方询问了一个消息,对方给我们了一个应答我们就能保证我们发送给对方的消息对方接收到了,如果对方还想收到刚刚发消息的应答的话这里就会出现一直请求应答的死循环了
  2. 没有应答的数据,我们无法保证可靠性,正如上面所写,最新的一条消息,是没有应答的,无法保证发出去的消息是100%可靠的
  3. 虽然最新的消息我们无法证明是可靠的,但是最新消息之前的消息我们可以知道是可靠的,代表一方收到了另一方的消息
2.1.3.2双向可靠性

在这里插入图片描述

  1. 客户端在向服务器发送数据的时候,只要客户端收到了应答,就能保证从客户端到服务器方向上数据的可靠性
  2. 服务器在向客户端发送数据的时候,只要服务器收到了应答,就能保证从服务器到客户端方向上数据的可靠性
  3. 只要自己知道自己的数据有没有被对方收到就好了,没必要再让对方知道自己收到了应答(外卖小哥只需要知道自己的外卖送到客户手中,客户点击收到按钮了,外卖小哥没必要再向对方发送我知道你收到了的信息,浪费时间)
  4. 客户端怎么知道发出去的数据有没有被服务器接收呢呢,一段时间如果没有收到应答,客户端认为数据丢失,就会重传
  5. 事实上先应答再发送数据的做法实在是太蠢了,这二者本质可以合为一条,我问你今天要出去吃吗,你先给我回好的,再给我回吃什么,这本质可以合为好的,那我们要吃什么–捎带应答
2.1.3.3批量发送与乱序处理

在这里插入图片描述

  1. 如果客户端发一个数据就要等服务器应答再发数据的话那就太慢了,所以客户端会一次发送一批数据,只需要接收到每一批数据的应答就好了
  2. 发出去的数据在网络中要经过大量的中间设备等原因,先发出去的数据不一定先被服务器收到,就会导致数据包乱序问题,乱序本身也是不可靠的一种
  3. 所以就有了序号,对方在收到了报文之后就会按照序号来进行排序

序号–保证数据的按序到达

2.1.3.4序号定义

在这里插入图片描述

  1. TCP是面对字节流的,发送数据就要先将报文写到tcp发送缓冲区
  2. 发送缓冲区本质也是数组,所以每个字节天然的就有了数组的下标
  3. 序号本质上就是发送的数据块的最后一个字符的下标
  4. 我们一次性发送了多个数据,服务器也会回我们多次响应,那我们如何区分响应是对应了哪个数据呢–确认序号
  5. 确认序号,填充的是,收到报文的序号+1,比如一个报文的序号是1000,那么应答的确认序号就是1001
  6. 确认序号的意义:表示确认序号之前的数据,我已经全部收到了,下一次发送,请从确认序号指定的数字开始发送,比如客户端发送数据的序号是2000,服务器给它的应答是2001,代表2001之前的数据已经全部收到了,下次发数据请从2001以后开始发送
  7. 如果我一次性发送了序号1000,2000,3000序号的报文,但是收到的应答1001和2001却丢了,只收到3001,但3001就够了,代表3001之前的报文我已经收到了,这就允许了应答有少量的丢失

在这里插入图片描述

服务器不再报文里面的序号复用应答的序号的原因之一:服务器也需要给我们发送数据,它在应答的同时也可以向客户端发送数据,发送数据就要有对应的序号,双方都需要给对方发消息和应答,所以序号和确认序号要分开

2.1.4 标记位

在这里插入图片描述

2.1.4.1标记位作用
  1. TCP通信分为三个阶段,一是建立连接,二是正常的数据通信,三是断开连接,有些请求是建立连接的等等,所以TCP收到的报文一定是有各种类型的,不同的类型决定了服务器要做不同的动作
  2. 标记位存在的意义:区分tcp报文的类型
2.1.4.2各标记位详解
  • ACK:确认序号是否有效,就是应答里面这个标记位必须置1
  • SYN:请求建立连接,我们把携带SYN标识的称为同步报文段,服务器为了区分连接还是通信的客户端,就要看SYN的标记位
  • FIN:通知对方,本端要关闭了
  • PSH:提示接收应用程序立即从TCP缓冲区把数据读走,比如说服务器太忙了一直不把缓冲区的数据取走,一种方法是客户端不停的问,假设对方就是一直不拿走,就需要把PSH标记位置1,提醒对方尽快把数据读走,一般不携带数据(TCP协议栈在收到PSH包后,通常会打破原有的等待策略,立即将数据提交给应用进程)
  • RST:对方要求重新建立连接,我们把携带RST标识的称为复位报文段,TCP保证通信过程是可靠的,但是不保证异常情况,tcp允许建立连接失败
2.1.4.3三次握手与RST

TCP在建立连接的时候是要建立三次握手的

在这里插入图片描述

  1. 经历三次握手一般要经历三个阶段一是客户端向服务器发送SYN请求连接,二是服务器会向客户端发送建立连接并且应答,三是客户端向服务器发送应答,这是两个方向上的
  2. 客户端在三次握手中,认为只要把三次握手中的第三次报文发出,就认为连接已经建立好了
  3. 万一第三次报文的ACK丢失了,服务器就会认为这个连接并没有建立好,因为报文的发出是要经过网络,所以双方在报文的接收上就会有一个时间差,在连接的建立上就会有认知不一致,客户端认为建立好了,服务器认为没建立好,客户端认为建立好了就会直接向对方发送数据,服务器因为没收到连接就收到了报文
  4. 此时服务器就会想客户端认为自己已经把连接建立好了,服务器在向客户端的应答里面就会将RST标志位设置为1,客户端收到RST之后就会把内部维护连接的管理结构这条给释放掉,重新向对方发起建立连接的请求
  5. 连接出异常的情况就需要使用RST重新建立连接
2.1.4.4URG紧急指针

在这里插入图片描述

  • URG:紧急指针是否生效,有些情况下我们就想让我们的数据进行插队进行优先处理
  • 将URG置为1就可以动用报头里面的16位紧急指针的字段了,紧急指针一般存放的是数据当中紧急部分所储位置的偏移量,这个数据就是需要被插队被高优先级处理的数据
  • 一般在tcp当中紧急数据只允许你携带一个字节
  • 当客户端不怎么收到服务器的响应的时候,向对方发起询问,询问操作也是需要排队的,就需要紧急数据来处理,服务器也会读取紧急数据+软件功能提供状态编号,向客户端发回紧急数据提供服务器状态信息

2.2超时重传机制

在这里插入图片描述

2.2.3丢包处理机制

如果主机A在向主机B发送数据的过程中数据发送丢包,那么主机B就不知道主机A给它发送过数据,就不会发送应答,主机A就不知道报文有没有被对方收到

  • 主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B
  • 如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发

在这里插入图片描述

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

主机对于发出去的报文,是否丢失,无法判定

必须通过规定,表征是否要重传

主机B就有可能会收到主机A的重复报文,就需要去重,报文有序号

2.2.4超时时间确定机制

2.2.4.1动态超时策略

如果网络很好数据一来一回的速度比较快,为了提高效率,重传时间不能设的太长,如果网络特别糟糕,重传时间不能设置太短,发一个报文需要100ms,重传时间50ms等于发一个数据就要重传了

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

特定的时间间隔是动态的,和网络状况相关

2.2.4.2具体实现
  • Linux中,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍
  • 如果重发一次之后,仍然得不到应答,等待2*500ms后再进行重传,如果仍然等不到应答,等到4 *500ms进行重传,以此类推,以指数形式递增
  • 累积到一定的重传次数,TCP认为网络或者对端主机出现异常强制关闭连接

2.3连接管理机制

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

在这里插入图片描述

2.3.1三次握手的原因

  • 可靠验证全双工

三次握手能够保障服务器和客户端都至少经历了一次收发,验证全双工通路是否通畅

  • 奇数次握手,可以确保一般情况握手失败的连接成本是嫁接在客户端的
  1. 如果是单次握手的话,客户端一次性发送给服务器大量的SYN建立连接请求的话,服务器的连接资源就很容易被打满了–SYN洪水
  2. 如果是两次握手的话,一定是要求服务器先把连接建立好,然后客户端再建立连接,其实和单次握手有相同的问题,如果客户端异常了,服务器向客户端发送的ACK丢包了,服务器还要维持这个连接一段时间,优先让服务器建立连接的动作,服务器对客户端是一对多的,所以不能够让服务器承担接收风险
  3. 三次握手的话第三次发送报文的时候是客户端先建立的连接,如果ACK失败了,此时服务器就会认为建立连接失败,建立失败的成本就嫁接到了客户端的身上,可以保持服务器的稳定性
  4. 三次是维持全双工的最小次数

2.3.2四次挥手的原因

断开连接的本质:
没有数据给对方发了,发送数据双方都可能发,所以必须断开两次

  1. 客户端发向服务器一次FIN断开连接,服务器回一次ACK的本质就是客户端告诉服务器我不想给你发送数据了,服务器说可以
  2. 服务器可能还有数据想给对方发送,所以不将发给客户端ACK和FIN合并在一起,服务器到客户端之间的ACK代表了服务器说我不给你发送数据了,客户端说可以

2.3.3三次握手的状态变化

在这里插入图片描述

  • 客户端向服务器发送SYN请求连接状态设置为SYN_SENT
  • 服务器收到SYN后状态从LISTEN变为SYN_RCVD
  • 客户端收到SYN-ACK后状态变为ESTABLISHED
  • 服务器收到客户端的ACK请求后状态也变为ESTABLISHED

2.3.4四次挥手的状态变化

  • 客户端主动断开连接的一方状态设置为FIN_WAIT_1
  • 服务器收到FIN状态变成CLOSE_WAIT
  • 客户端收到ACK之后状态变成FIN_WAIT_2,这种状态下客户端不会再向对方发送数据,下面那个ACK是管理报文
  • 服务器想跟客户端断开连接的状态设置为LAST_ACK
  • 客户端收到FIN状态变成TIME_WAIT,要求服务端等待一段时间
  • 服务器收到ACK之后状态变成CLOSED

2.3.5三次握手与连接队列

2.3.5.1连接队列机制
  • 连接建立成功和上层有没有accept没有关系

我们做实验可以发现当服务器不做accept等任何操作的时候,netstat查询服务器和客户端都是ESTABLISHED的状态,从第三个开始客户端建立连接状态变成ESTABLISHED,服务器的状态却变成了SYN_RECV

  • listen的第二个参数:backlog+1表示底层已经建立好的连接队列的最大长度
int listen(int sockfd, int backlog);
2.3.5.2全连接队列详解
  1. 当我们不使用accept的时候,代表服务器还没有把连接拿上去,如果服务器允许你把连接建立好,那么服务器一定会存在大量的建立好的连接并没有被上层accept上去,所以服务器就要对已经建立好的连接起到临时维护的效果
  2. 先描述再组织,使用队列的形式进行管理,三次握手本质就是把连接放到队列中管理起来,然后accept再从队列当中拿走–全连接队列
  3. 已经三次握手成功的入队列
  4. 当accept没有取走连接的时候,此时backlog是1,允许的连接队列最大长度是2
  5. 全连接队列满的情况即便收到ACK都无法将状态从SYN_RECV变为ESTABLISHED,会将ACK进行丢弃
  6. 为什么全连接不能太长,因为如果服务器太忙全连接队列太长的话,全连接队列还要资源维护,还有相当一部分不能被处理占用资源
  7. 为什么不能没有全连接,如果服务器正在忙于其他业务无法及时调用accept,这个新连接就会直接被丢弃或拒绝,这将导致服务器负载较高时,无法建立任何新连接,比如你火锅店吃饭店里客人坐满了,来了客人你就叫他走,这导致有空闲的时候并不能充分的利用资源进行处理,所以一般要在门口摆几个板凳
2.3.5.3半连接队列与SYN洪水
  • 服务器端,不会长时间维护SYN_RECV,被建立连接的一方,处于SYN_RECV,这种状态我们称为半连接,如果全连接队列满了,服务器就要开始维护半连接队列(有长度限制),半连接的节点,不会长时间维护,会自动释放

先进入全连接队列首先要进入半连接队列,半连接也有限制长度,可能存在非法半连接打满的情况,那么正常的连接就可能进不来了–SYN洪水

  • 客户端和服务器连接建立不一致的问题,比如服务器断开连接,客户端继续发送数据

2.3.6四次挥手与TIME_WAIT

2.3.6.1TIME_WAIT状态问题
  • 主动断开连接的一方,在4次挥手完成之后,要进入time_wait状态,等待若干时长一般是30-60s不等可以设置,之后自动释放
2.3.6.2实际问题
  1. 如果是服务器方主动断开连接就要进入time_wait状态,连接没有被彻底断开,ip和port正在被使用,所以新起进程就无法bind这个ip和port,就会出现bind失败的问题
  2. 比如双十一有大量的客户端访问服务器,又来了一个新连接服务器资源被打满服务器直接挂掉了,操作系统无法维持自身基本运行和正常服务分配必要的资源,服务器就变成了主动断开连接的一方,服务器处于time_wait状态的话就无法立即重启,可能就会导致大量损失
2.3.6.3解决方案
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

这个函数我们用来设置套接字属性,允许可以进行地址复用

int sockfd,           // 【输入】要设置的套接字描述符
int level,            // 【输入】选项定义的协议层级,一般是SOL_SOCKET这一层
int optname,          // 【输入】要设置的选项名称
const void *optval,   // 【输入】指向包含新选项值的缓冲区,设置为真还是假
socklen_t optlen      // 【输入】`optval` 缓冲区的大小(字节数)
int opt=1;
setsockopt(_listensock,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt));

这样服务器当处于time_wait状态照样可以重新启动,照样可以重新连接

2.3.6.4MSL机制
  • TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL的时间后才能回到CLOSED状态

一个数据包在网络传输当中存活的最大时长叫做MSL,可能断开连接的时候历史上发送了很多的数据

可靠终止确保被动关闭方能够收到最终的 ACK。如果丢失,TIME_WAIT 状态允许主动方处理重传的 FIN 并重新发送 ACK。
清除旧报文确保本次连接产生的所有报文都在网络中消失,避免它们与未来新的、复用相同四元组的连接发生混淆。

为什么是2倍的MSL

  1. 第一段 MSL:等待主动关闭方发送的 最后一个 ACK 到达对方的最长时间。如果它丢失了,需要这段时间来让被动方察觉并重传 FIN。
  2. 第二段 MSL:等待被动关闭方因未收到 ACK 而 重传的 FIN 报文到达主动方的最长时间。

就是我客户端最后一段挥手ACK服务器最大需要一个MSL来等,如果一个MSL没收到,服务器会再次发送一个FIN,客户端最大需要一个MSL来等

2.3.6.5系统配置
  • 我们使用ctrl+C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口
  • MSL在RF1122中规定为两分钟,但是各操作系统的实现不同,在centos7上默认配置的值是60s
  • 可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值

2.4流量控制

2.4.1流量控制概念

接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应

因而TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制

2.4.2实现机制

第一次的时候,三次握手双方也交换了报文,已经协商了双方的接受能力

第三次的时候可以携带数据

  • 接收端将自己可以接收的缓冲区大小放到TCP首部中的窗口大小字段,通过ACK端通知发送端
  • 窗口大小字段越大,说明网络的吞吐量越高
  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端
  • 发送端接受到这个窗口之后,就会减慢自己的发送速度
  • 如果接收端缓冲区满了,就会将窗口置为0,这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端
  • 如果一端的缓冲区由满到缓冲区被读走,一端的主机就会立即向对方发送窗口更新的通知,一方会定期问,一方更新会立即告诉对方,这样就能保证在网络异常时导致单向通信异常提高了容错性,双方如果都出异常重传一段时间后,就会把连接关掉了

2.4.3窗口大小扩展

TCP首部中,16位窗口字段,存放了窗口大小信息,表示窗口最大就是60KB吗

实际上TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位

流量控制既有可靠性,又要效率

2.5滑动窗口

2.5.1滑动窗口概念

已经发出去,但是暂时没有收到应答的报文,要被TCP暂时保存起来

已经发出去,但是暂时没有收到应答,可能会在发送方存在多个

那么已经发出去,但是在暂时没有收到应答的多个报文,会被保存到哪里

在这里插入图片描述

2.5.2滑动窗口机制

  1. TCP是有自己的发送缓冲区的,所以就不需要再其他地方再保存一份了
  2. 可以分为三个区域分别是已发送已确认,已发送未确认,待发送
  3. 已发送已确认是可以被上层的数据拷贝覆盖的,就叫做被tcp缓冲区当中移除
  4. 已发送未确认的区域当收到ACK应答之后,就可以让已发送未确认的区域的start向后移,尚未确认的区域我们也叫做滑动窗口,滑动窗口是发送缓冲区的一部分
  5. 待发送区域如果发送之后只需要让已发送未确认的区域的end向后移即可
  6. 因为有滑动窗口区域,我们才可以一次向对方发送大量的tcp报文,没有滑动窗口的话需要等对方确认才可以给对方继续发送
  7. 滑动窗口的范围大小,是对方的接收窗口大小(目前)
  8. 区域划分,就是通过指针/下标来进行区分即可,只需要使用双指针来对滑动窗口的区域进行动态变化的控制就可以了,窗口滑动,本质就是指针右移

2.5.3应答丢失处理

如果丢包了怎么理解滑动窗口?

在这里插入图片描述

在这里插入图片描述

  1. 如果2001-3001数据的ack没收到,别的都收到了
  2. 有确认序号这东西,确认序号事x,代表x之前的全都收到了,主机B收到了全部,只不过是应答丢了几个
  3. 主机B在填写确认序号的时候就填的是5001代表5001之前的报文全都收到了
  4. 5001丢了其他都收到了就填4001,5001的报文就静等它进行超时补发就可以了

2.5.4数据丢失处理与快重传

在这里插入图片描述

如果是2001-3001的数据丢了呢

  1. 根据确认序号的定义,确认序号之前的我们都收到了,现在是3001我们没有收到,那么4001和5001的确认序号我们就不能填写4001或5001,只能填2001
  2. 所以滑动窗口在滑动的时候就只能滑到2001,确认序号保证了滑动窗口线性的连续的向后更新,不会出现跳跃的情况
  3. 主机A在收到3个同样的确认应答则进行重发,比如40015001等都发送2001
  4. 主机A会立即对这个丢包的报文进行重新发送,补发之后确认序号就直接变成5001了
  5. 这种策略我们叫做快重传

2.5.5重传策略对比

已经有了快重传,为什么还要有超时重传?

快重传是有条件的,是3个同样的确认应答才会进行

倘若数据通信末期,没有那么多数据进行发送了,也没那么多应答了,那么快重传就不会被触发

快重传是为了提高效率的,超时重传是为了兜底的(只有一个两个的时候)

2.5.6滑动窗口动态特性

滑动窗口向左移动?向右移动?移动的时候,大小会变化吗?怎么变化?会为0吗?

  1. 一般不会向左移动,只会向右移动(分情况)
  2. 如果主机B的应用程序迟迟不把数据从接收缓冲区取走,那么缓冲区里的剩余空间就会越来越小,这个变化会通过ACK报文反馈给主机A,告诉它“你能发送的数据量变小了”,所以,主机A的滑动窗口大小会根据主机B的缓冲区的实时状态而动态调整
  3. 动态变化分为变大,变小,不变,变小是因为对方的接收缓冲区的剩余空间变小了(右不变,左移动),变大是因为对方可能直接处理了一批数据(左右都移动,范围扩大),也可以对方缓冲区打满,此时我们的滑动窗口为0,双指针重叠,对方再取走两个,此时滑动窗口变为2(左右都移动,范围缩小)
  4. int start=根据确认序号,设置
  5. int end=确认序号+对方接受能力大小(其实是min(对方接受能力大小,有效数据)待发送的数据都没那么多了,自然也就发送不了接收能力大小那么多了)

滑动窗口,会在发送缓冲区中越界吗?

tcp采用了类似环形算法,所以不会越界

2.5.7序号随机化

序号真的是数组下标吗

当断完连接又进行重新连接的时候,有可能会有老数据前来,新连接就有可能受到老数据的干扰

所以双方对应的序号都是随机的,比如主机A的起始序号是1234,主机B的起始序号是4321,一般协商之后以小的那个为起始序号

有了起始序号比如1234+数组下标就是序号

确认序号-1234就是下次发送数据在缓冲区起始位置

2.6延迟应答

2.6.1延迟应答原理

在这里插入图片描述

  1. 发送方一次发送更多的数据,发送的效率就越高
  2. 发送方一次发送更多的数据,对方告诉我它能接收更多的数据
  3. 如果接收方给发送方通告一个更大 的窗口大小(TCP报头),就能实现一二
  4. 如何让接收方给对方通告一个更大的窗口呢?在允许的范围内,收到报文不着急应答,上层就有较为充分的时间,来取走数据–延迟应答(博概率)

2.6.2示例

  • 假设接收端缓冲区为1M,一次收到了500K的数据,如果立即应答,返回的窗口就是500K;
  • 但实际上可能处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了
  • 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一点,也能处理过来
  • 如果接收端(TCP的协议层)稍微等一会再应答,比如等待200ms再应答,这时候返回的窗口大小就是1M

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

2.6.3限制条件

那么所有的包都可以延迟应答么?肯定不是

  • 数量限制:每隔N个包就应答一次
  • 时间限制:超过最大延迟时间就应答一次

具体的数量和超时时间,依操作系统不同也有差异,一般N取2,超时时间取200ms

2.7拥塞控制

2.7.1网络拥塞检测

上述的所有的策略,起作用的都是在两端机器上的,那么tcp在面对网络信道情况是如何处理的呢

如果发送数据,出现问题,不仅仅是对方主机出现问题,也可能是网络出现了问题

  • 如果通信双方出现了大量的数据丢包问题(大量的数据都超时了),tcp会判断网络出问题了(网络拥塞了)

2.7.2慢启动机制

一个班的作业只有几个人没写那么就是学生的问题,如果全班都没写就是老师的问题

此时我们不能对报文进行超时重发,如果是硬件设备出问题了,发了也没用,如果是数据量太大,引起阻塞,那就会加重网络的拥塞

因为大家用的都是tcp协议,只有一个人少发肯定不够,大家一起少发才能缓解网络拥塞,这就是用TCP协议实现了多主机面对网络出现拥塞的"共识",根据拥塞严重程度的不同有不同的处理情况,有时候只被个别主机判别,那就个别主机进行操作,越严重越有更多的主机参与进来

  • TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的拥堵状态,再决定按照多大的速度传输数据
  • 此处引入一个拥塞窗口
  • 发送开始的时候,定义拥塞窗口大小为1
  • 每次收到一个ACK应答,拥塞窗口加1
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小(对方的接收能力)作比较,取较小的值作为实际发送的窗口

2.7.3窗口计算

  1. 滑动窗口大小=min(窗口大小,拥塞窗口)
  2. 窗口大小考虑的是对方主机的接受能力
  3. 拥塞窗口考虑的是动态的,网络的接收能力
  4. 所以滑动窗口的end=确认序号+min(对方缓冲区的接受能力,有效数据,拥塞窗口)

拥塞窗口是主机判断网络健康程度的指标,超过拥塞窗口,会引发网络拥塞,否则不会

2.7.4增长控制

像上面这样的拥塞窗口增长速度,是指数级的慢启动,只是初始时慢,但是增长速度非常快

  • 为了不增长的那么快,因此不能使用拥塞窗口单纯的加倍
  • 此时引入一个叫做慢启动的阈值
  • 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
  • 网络出现问题,发送少量的报文(前期慢),如果都ok,网络已经趋于健康了,应该尽快恢复正常通信了(增长幅度高)

实际机器发送的数据量就是以窗口大小和拥塞窗口小的那个为标准,拥塞窗口非常大,代表网络情况非常好此时就以对方的接收能力为标准了

2.7.5拥塞恢复

在这里插入图片描述

  • 当TCP开始启动的时候,慢启动阈值等于窗口最大值
  • 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口会变成原来的一半,同时拥塞窗口置为1

就是我发送这么多数据,到网络拥塞之后我就有数了下次不能发这么多了,尝试发一半过去

只有我们正常触发了网络拥塞才会执行这样的算法,通常情况下都是以对方接收能力为主的

2.8TCP小结

可靠性

  • 校验和
  • 序列号
  • 确认应答
  • 超时重发
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能:

  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

2.9面向字节流

  • 写100个字节数据时,可以调用一次write写100字节,也可以调用100次write,每次写一个字节
  • 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read100字节,也可以一次read一个字节,重复100次

在这里插入图片描述

  1. 用户在通过write等接口将用户层缓冲区的数据拷贝到内核缓冲区都是以字节的方式呈现出来的
  2. 接收方可能缓冲区积累了大量的请求,一次取多少由用户决定
  3. 用户层以为自己写了多个请求交给内核,在TCP协议只认识多个字节流数据
  4. TCP不关心上层协议,不关心上层报文格式,tcp只有字节的概念
  5. 上层需要定义一个缓冲区,把tcp层的字节流凭借起来,在用户层判断对数据如何进行解析
  6. 所以tcp当中没有数据的长度,只有报头的长度,将报头和有效载荷做分离之后,将数据交给接收缓冲区就可以了

2.10粘包问题

在这里插入图片描述

tcp层只有字节流的概念,用户层才需要对字节流进行解析,将字节流变成一个一个完整的请求,对报文进行一个一个的处理

如果用户只是单纯的读

那么就会出现读半个等不完整报文的情况,如果用户只对一个报文处理就将这一个报文丢弃,那么就会影响到后续半个报文的处理

  • 如果在用户层我们不对报文进行一个一个的分离,可能会多处理少处理对应的请求---------粘包问题

  • 解决粘报的策略就是定协议,在应用层通过协议,明确报文和报文之间的边界

  • 第一种方法就是定长报文,比如定义每个报文都是100字节,每次都是以固定长度来读

  • 第二种方法就是使用特殊字符,比如http使用\r\n作为一个报文和一个报文的边界

  • 第三种方法就是使用自描述字段+定长报头,比如说规定报头是8个字节,前4个字节用来描述有效载荷的长度

  • 第四种方法就是使用自描述字段+特殊字符,比如说报头以空行结尾,请求里面有content length描述的是有效载荷的长度

2.11TCP异常情况

2.11.1进程终止

  1. 进程终止本身就是进程结束,连接和进程没有直接关系
  2. 连接和文件是直接相关的,文件的生命周期是随进程的
  3. 进程出异常操作系统不能将进程模块的异常影响到文件模块,进程终止对于通信双方来将都以为是进程结束了,就会进行正常的四次挥手
  4. 所以进程终止代表进程结束,连接正常自动断开

2.11.2机器重启

  1. 机器重启和机器关机之前都会提醒我们是否要结束正在运行的进程
  2. 杀掉所有的进程就代表进程结束了也就是连接四次挥手正常自动断开

2.11.3机器掉电/网线断开

  1. 网线断开是没有机会进行四次挥手的
  2. 网络断开连接就维护不上了,这个连接就可以直接关掉了
  3. 重新联网再向对方发送建立连接的消息,对于客户端来将这个连接不存在,对于服务器来将这个连接还维护着,这就出现了连接认知不一致
    是定长报文,比如定义每个报文都是100字节,每次都是以固定长度来读
  • 第二种方法就是使用特殊字符,比如http使用\r\n作为一个报文和一个报文的边界
  • 第三种方法就是使用自描述字段+定长报头,比如说规定报头是8个字节,前4个字节用来描述有效载荷的长度
  • 第四种方法就是使用自描述字段+特殊字符,比如说报头以空行结尾,请求里面有content length描述的是有效载荷的长度
posted @ 2026-01-30 17:59  yangykaifa  阅读(0)  评论(0)    收藏  举报