【计算机网络】传输层协议(UDP TCP)

news/2025/2/25 23:49:33

目录

1. 端口号

 端口号的划分

 2. UDP

 UDP协议格式

在系统中的描述

缓冲区

使用注意事项

3. TCP

 缓冲区

 TCP协议格式

 标记位

 面向字节流

 确认应答机制

流量控制

 超时重传

 连接管理

滑动窗口 

延迟应答

 捎带应答

快重传

 拥塞控制

粘包问题

 TIME_WAIT状态

总结


在这里插入图片描述

1. 端口号

再来聊一聊端口号:

接收的报文到了传输层(tcp/udp),要向上交付到应用层,应用层的程序有很多,比如(自己写的服务,http、https...)数据要传个哪个程序?这时就需要port来进行标识 ;

在TCP/IP协议中,用 "源IP","源端口号","目的IP","目的端口号","协议号"这样一个五元组来标识-个通信(可以通过netstat -n查看);IP可以确定哪台机器,Port可以确定哪个应用程序那协议号是干什么的?

如图,IP和Port中间还要经过传输层;经过传输层就需要知道是把报文交给tcp还是udp; 

 使用命令:netstat -nltp查看

  • n选项:属性中能用数字显示的就用数字显示
  • t选项:只查看tcp协议
  • u选项:只查看udp协议(选项中可以同时带t、u选项)
  • p选项:查看服务名称(PID那一列)
  • L选项:只查看listen状态的服务
  • a选项:查看所有状态的服务(a、l选项可以同时带,效果和a选项效果相同) 

 端口号的划分

  • 0 -1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他们的端口号都是固定的
  • 1024 - 65535:操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围分配的。

普通用户无法绑定0~1023端口;使用root可以绑定;有些服务器是非常常用的,为了使用方便,人们约定一些常用的服务器,都是用以下这些固定的端口号:

  • ssh服务器,使用22端口
  • ftp服务器,使用21端口
  • telnet服务器,使用23端口
  • http服务器,使用80端口
  • https服务器,使用443 

我们自己写一个程序使用端口号时,要避开这些知名端口号;使用 pidof 查找正在运行的进程的进程ID;

语法:

pidof [选项] <进程名>

示例:

pidof bash

#多进程:返回所有名为 sshd 和 httpd 的进程的 PID
pidof sshd httpd

 2. UDP

         UDP(用户数据报协议,User Datagram Protocol)是一种用于网络通信的传输层协议,UDP 是一种无连接的协议,它提供了一种简单、高效的数据传输方式,适合于需要快速交互但不一定需要可靠性的数据传输场景;

UDP 的特点 :

  • 无连接性:UDP 不建立连接,数据包是独立的,发送方不需要与接收方建立连接。
  • 低延迟:由于没有连接建立和维护的过程,UDP 通常能提供较低的延迟,非常适合实时应用(如视频会议、在线游戏等)。
  • 不保证可靠性:UDP 不确保数据包的到达,发送的数据可能会丢失或顺序错乱,因此不适合对数据完整性要求高的应用。
  • 简单的报文结构:UDP 的报文结构相对简单,头部较小(只有 8 字节),效率较高。头部包括源端口、目的端口、长度和校验和信息。
  • 多播和广播支持:UDP 支持一对多的通信方式,例如进行多播或广播数据传输。
  • 面向数据报:应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并;比如:用UDP传输100个字节的数据,如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节,而不能循环调用10次recvfrom,每次接收10个字节;

 UDP协议格式

 校验和:通过校验和来验证收到的数据是否在传输中被损坏;

端口号为什么是16位?底层协议udp端口号是16位;

无论是tcp还是udp都要考虑两个问题:

  1. 有效载荷和报头分离的问题
  2. 有效载荷向上交付的问题

对于udp来说,如何解决有效载荷与报头分离的问题?采用固定报头长度

向上交付的问题呢?目的端口号

在系统中的描述

 如何理解报头? 其实就是一个结构化字段

struct udphdr
{
    // 位段
    uint32_t src_port:16;
    uint32_t dst_port:16;
    uint32_t len:16;
    uint32_t checksum:16;
}

 怎么判断,收到的报文是完整的?

  • 如果报文长度不到8字节,那么报文一定不完整(报头都不完整)
  • 如果大于8字节,那么就可以从报头中拿到udp的报文长度,长度-8就是数据长度;

 比如应用层发消息给另一台主机(消息为hello)

struct udphdr hdr={ 1234,8888,5,XXXX };

 需要对消息封装报头:

 udp在封装时包含:1.描述结构体 2.保存数据和报头的缓冲区;

这是一个报文的情况,那多个报文呢?

就需要进行管理,在struct sk_buff中还有一个指针(struct sk_buff*) 

对报文的管理就变成了对链表的增删查改;

缓冲区

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

如果缓中区满了,再到达的UDP数据就会被丢弃;

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

如何理解“缓冲区”?udp的缓冲区其实就是一个队列,也就是上述管理udp数据的链表;

使用注意事项

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

3. TCP

        TCP(传输控制协议)广泛用于互联网和其他网络中;主要有以下特点:传输层协议、有连接、可靠传输、面向字节流

 有连接怎么理解?

         在使用TCP时需要创建一个listen套接字,然后accept接受客户端连接,然后返回一个普通的套接字;TCP是可靠传输,为例保证消息的送达,需要和每个客户端建立一个连接,通过连接进行一对一的通信;

怎么理解listen套接字?

举个例子:

        你和朋友出去玩,到了饭点想找饭店去吃饭,此时看到一家店,外边站着一个人拉着你们进到店里面吃饭,将你们带到店里后,又出去拉其他的客人,进到店里你和朋友又会有其他服务员进行服务(比如:找包间、点菜,上菜);

  • 拉客的人 — Listen 套接字:

        Listen 套接字负责监听外部连接请求,类似于拉客的人,他们在外面主动接待顾客,等待新客人的到来。当有新顾客出现时,拉客的人将其引导入店,类似于 Listen 套接字准备接受来自客户端的连接。

  • 顾客 — 连接请求(客户端):

        顾客是连接请求的发起者,他们希望进入饭店就餐。在 TCP 中,客户端通过发送连接请求(SYN)来与你的服务器建立连接。

  • 饭店内部的服务员 — 普通套接字:

        进入餐厅后,顾客会与服务员进行互动,进行下单和就餐。普通套接字用于与已经建立连接的客户端进行双向通信。一旦把顾客引入店里,拉客的人就不再参与沟通,服务员负责提供后续的餐饮服务(即数据交换)

  • 点菜、上菜的过程 — 数据传输:

        在就餐过程中,顾客与服务员之间进行信息交互,类似于数据包的收发。在 TCP 中,这个过程涉及到数据的传输、确认及重传等操作。

  • 餐厅的管理 — 连接的管理和关闭:

        当顾客用餐完毕后,服务员会处理结账并告知顾客如何离开,类似于通过“四次挥手”的过程安全地关闭连接。当一方完成通信时,会发送 FIN 信号,另一方确认接收到这个信号后,结束连接。

 缓冲区

tcp具有发送和接受缓冲区,也是全双工通信;

比如:应用层发送一个 “hello” 发送给其他主机,调用 send/write  本质就是将数据写入到发送缓冲区,发送数据时就会从发送缓冲区中拿取数据进行发送;目标主机接收到后会将接收到的数据存放到接收缓冲区;上层调用 read/recv 读取缓冲区中的数据进行处理;

        在接受数据时,如果接受缓冲区满了,缓冲区就不再接受数据,再来数据就会直接丢弃
这种设计并不会影响tcp的通信,被丢弃的数据也会通过一定方法,让发送方知道;如果此时还一直保持发送,显然这样是合理的,浪费流量;对于这些问题,需要通过协议来解决;

 TCP协议格式

TCP可以做到可靠传输,那它如何确保数据可靠的送到呢?——协议;下面是它的协议格式:

4位首部长度:0000 取值范围 [0~15],15字节连tcp报头的标准长度都不够; 这里需要乘首部的基本单位(4字节)那么tcp首部长度范围是[0~60];tcp首部最长是60字节;

而标准报头的长度是20字节(一行32位,4字节,5行20字节),减去标准报头20字节,选项最多可以是40字节(选项可以忽略);

如何封装报头呢?

         tcp封装报头的过程和udp相似,有一个struct tcphdr结构体struct sk_buffer中维护两个指针,data和tail,封装报头,只需知道报头的大小,然后让data指针向前移到sizeof(tcphdr)字节,将报头数据填充,封装报头;

校验和:通过校验和来验证收到的数据是否在传输中被损坏;

32位序号:在实际场景中,服务端可能同时接收到许多条消息的,由于收到网络的影响,消息很可能不是按序的到达(收到的数据顺序可能是乱的),这样就会导致数据损坏不能使用;因此它需要保证数据按序到底;如何保证?——32位序列号;接受方根据报头中的32位序号对数据进行排序,这样就可以确保接收到的报文是按照顺序的;

32位确认序号:历史自己发送的哪些报文已经被对方收到,确认序号有这样的规定;只要收到一个确认序号,就表示接收方已经成功接收了该序号之前的所有字节数据;比如发送4次,序号分别是100、200、300、400;确认序号通常是201、301、401、501;在应答时前三个报文都可能丢失,但只要收到401就表示401之前的报文都已被接收;

为什么要分32位序号和确认序号,都使用序号不行吗?

因为tcp通信是全双工的,client在向server端发送数据的同时,server端也可能向client端发送数据;

在实际常见中,应答其实是可以和发送的数据合二为一的(1、2步合二为一) ;每次发送消息(包括确认应答)都是一个完整的TCP报文,都包含以下结构:

 那么就可以做到,在发送消息的同时,进行消息的确认应答;tcp保证可靠性的同时,还会进行各种提高效率的设定,但这些都是内核自主完成的,用户毫不知情;

 标记位

在进行通信时,server 端一定会同时收到各种各样不同类型的报文,这些报文都有类型(建立连接、断开连接、发送数据...)因此就需要有标记位标记消息类型;

 SYN建立连接请求,ACK应答,FIN断开连接;

 URG表示紧急数据:比如服务端收到很多消息,这些消息的处理都需要进行排队,如果来了紧急任务,那么就需要优先对紧急任务进行处理(插队)只要UGR标志位被置为1,就表明是紧急数据;

有标记位,那紧急数据在哪?
        这时就需要用到紧急指针,紧急指针是紧急数据在有效载荷中的偏移量,只要URG标记位无效,紧急指针就无效;标记位被置1,就读取紧急指针;找到了紧急数据,那紧急数据的大小是多少?——使用时一般设定为1字节,也就是说允许插队,但不允许大量插队;

比如:

1表示终止或暂停上传行为,2表示...;

服务器压力打,可以设置紧急数据,

recv中的flag字段可以设置接收紧急数据;询问服务状态:0表示状态良好、1表示压力有点大,但还正常、2表示压力有点大,快要撑不住了...;

PSH:表示推送,催促尽快将数据向上交付;当接收方上层应用层处理比较耗时——卡住了
接受缓冲区就会满,那么接收方在响应时,窗口大小就会置为0,发送方此时就能知道接受方暂时无法继续接受数据,就会停止发送新的数据,问题是,发送方如何知道接受方什么时候能接收数据?发送方会定期发送询问请求(请求只有报头,没有任何数据,报头数据采用上次报头的数据)接收方收到请求就要响应应答,应答时就会附带自己窗口大小;经过多次的询问,如果窗口大小依然是0,这时就可以添加PSH,以前不带PSH,就像一直在问好了没,带上PSH只后就是,好了没赶快把数据向上层交付(表示表单的语义),PSH不仅仅在这种情况下使用,需要数据尽快交付的场景下都可以使用比如:   xshell,连接远端机器,发送指令给ssh,每次发送都会携带PSH,就是为了让指令被快速响应;

RST:TCP建立连接需要进行3次握手,但是3次握手也可能会失败,此时就可能会导致,建立连接认知不一致;比如:client向server发送数据,在server处理请求的时候,client端把网线拔了,client端建立的连接就直接释放了;但server并不知道,继续给client发消息;会儿client端恢复网络,client就会疑惑,不是三次握手吗?我还没发请求怎么就应答了,这时client也可以向server发送RST(重置请求);RST对于双方都是对等的;

 面向字节流

        用户发送数据,其实只是把数据拷贝到发送缓冲区,对于TCP的缓冲区,可以把发送缓冲区理解成一个char类型的数组(面向字节流以字节为基础单位,和字符数组很像);比如:发送4个字节,发送的最后字节的序号就是3,(也就是32位序号)对方收到后返回的一般都是4(表示下一次从4开始发,并不代表下一次的序号就是4)比如:第二次继续发4个字节,发送的最后一个字节位置是7,那发送tcp报头中的序号就是7;

 接收端收到后,在上层读取时,读取的就是一个一个的字节,这也就是面向字节流;

字节感受到了,流在哪?

        注意TCP发送的报文中并没有明确有效载荷的长度;TCP根本就不管报文的分隔;它只管把报头和有效载荷的分离,然后把有效载荷直接无脑的放到接收缓冲区中;所以在接收缓冲区中,可能已经积攒了几十个历史报文的数据;上层读取时,就需要对这些报文进行分割处理(http/https),在这个过程中缓冲区有拿出数据的,有加入数据的;有出有进就形成了流动的概念;

 确认应答机制

TCP为了保证可靠传输,对于每条消息都有一个确认应答机制;

         任意一方向另一方发送消息时,接收方都要应答(表示收到)应答机制可以保证,发送的数据被对方已经收到;当然,确认应答的功能不止于此,这里仅需了解有确认应答机制,在下文会进行补充;

流量控制

         如果接收方来不及接受数据,发送方会根据接收方的接收能力调整发送速度,暂时停止发送数据或者减少发送数据量,以避免数据丢失或网络拥塞;可以避免流量的浪费;

这个控制的过程由发送方的tcp协议做的(OS)上层用户不需要关心;

 如何进行流量控制?发送方要知道接收方的接受能力;

在TCP报头中有一个16位的窗口大小,用来进行流量控制的字段,表示自己接收缓冲区剩余空间的大小;流量控制的过程由发送方和接受方OS共同协商完成;

 超时重传

         发送方发送出去消息,如果没有收到应答,就会再次发送数据,超出重传机制可以确保消息被对方接收到;重传机制对于双方来说是对等的;不管是数据还是应答,只要丢了,发送方都会超时重传,这种情况会造成主机B收到两个报文(一般主机B会使用最新的)因此主机B需要有去重操作(根据序号去重);发送方一旦把数据发送出去一段时间内,已经发送的数据不能被移除;应该被暂时保存起来;

        网络状态是变化的;如果超时重传等待时间太久——效率就会变低;如果超时重传等待时间太短——过于频繁的进行重传浪费流量;因此TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间;Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍;如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传;如果仍然得不到应答,等待 4*500ms 进行重传。依次类推,以指数形式递增累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接;

 连接管理

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

 server端一定允许同时存在很多个已经完成三次握手的连接;OS需要对多个连接进行管理;描述对象,再组织管理;

struct links {  
    int start_seq;          // TCP连接的起始序列号  
    std::string src_ip;     // 源IP地址  
    std::string dst_ip;     // 目标IP地址  
    int srcport;            // 源端口号  
    int dstport;            // 目标端口号  
    uint64_t timestamp;     // 连接的时间戳,通常用于记录连接建立的时间  
    int status;             // 连接状态,可以用来表示连接的不同状态(如连接中、已连接、关闭等)  
    int urg_data_ptr;       // 指向紧急数据的指针,表示紧急数据的序列号  
    struct links *next;     // 指向下一个连接的指针,用于构建链表结构  
};

 双方如果建立连接,那么在双方的0s中就要构建类似于这样的连接结构体,client和server建立连接后,client要维护链接,server也要维护链接;接收方收到紧急数据,把数据加到缓冲,struct links中:urg_data_ptr = 缓冲区中原始报文的地址 + 偏移量,上层读取缓冲区数据没有读到紧急指针,假如只读了10字节:urg_data_ptr = urg_data_ptr - 10;类似这样更新一下紧急指针,双方维护连接是有成本的:时间 + 空间;

链接时会有状态的变化,比如:把一个请求发送过去后,就可以设置状态

  • #define SYN_SENT 1
  • #define SYN_RCVD 2

 为什么要三次握手,不能是1次、2次、5次?

  •  确认通信信道是否通畅(对于client和server双方来说,都要确保数据能正常收发)
  • 最小成本验证全双工;
  • 降低SYN泛洪攻击的风险:客户端先建立连接,服务端后建立连接,增加了攻击成本;
  •  确保建立可靠的连接:3次握手可以确保双方可以正常的进行通信,同时可以确保对方可以收到消息,在任何一个阶段都有确切的结果(知道对方收没收到消息);
  • 数据同步确认:确认序号、状态同步、滑动窗口大小同步等;

四次挥手,为什么要四次挥手?

 tcp协议是对等的,client端向server端建立连接;server端也要向client端建立连接;

从数据传输角度:

  1. client向server发送消息
  2. server向client发送消息

        client向server端发送断开连接(更像是告知),client端数据发送完了,再也不向server发送数据了;c->s信道关闭;
        但是server端并不一定已经把所有的数据发送完,server还需要向client发送数据,s->c信道不会关闭;server端发送完数据才会断开连接;
为了保证可靠的通信,就必须要保证两个朝向上的数据都发完了;所以也就有了四次挥手;当然也存在三次挥手的情况,双方同时把数据发送完的情况比较少;或者双方断开连接的意愿都非常强的情况下可能会三次挥手;

滑动窗口 

 一发一收会很影响性能,那么我们一次发送多条数据,就可以大大的提高性能;也是流量控制的解决方案和重传机制也有关系;

滑动窗口在哪里?滑动窗口是发送缓冲区的一个区域 

 怎么理解滑动窗口?

[win_start,win_end]:滑动窗口,滑动窗口本质就是下标的移动 ;

滑动窗口多大,应该由谁决定?当前窗口大小,由对方的接受能力决定

如何更新?

  • win_start = 确认序号
  • win_end = win_start + win(窗囗大小) 

        在最开始,还没有发送的时候,滑动窗口应该多大?窗口大小在三次握手的时候已经协商完毕;

TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息;那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
        实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是 窗口字段的值左移 M位;窗口大小*M;

滑动窗口的大小不会变没?变大?变小?为零?可以

 发送方收到响应,得知win大小一直在变小,收到一个应答,win_start就向左移动一次;win_end一直不变,窗口的大小不断变小;直至为零(接收缓冲区满了);

 比如:发送5条消息,序号分别是500、600、700、800、900;对应应答如上图;上层不把数据取走,响应时win大小一直在变小;上层读取缓冲区的数据后,缓冲区有空间了,就会发送窗口更新的通知;

滑动窗口可以向左移动吗?绝对不可能

窗口一直向右滑动,越界了怎么办?不会,tcp对缓冲区做了很复杂的处理可以直接认为它是一个环形结构即可;

 当然它也可以延迟应答,避免重复的应答;

延迟应答

         发送5条消息,序号分别是500、600、700、800、900;正常情况下需要一个一个的应答,但是它还可以延迟应答,等消息接收完之后应答确认序号1001(1001以前的数据都已接收),只应答一次;

        如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小. 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;在这种情况下, 接收端处理还远没有达到自己的极限,即使窗口再放大一些, 也能处理过来;如果接收端稍微等一会再应答,比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输 效率;那么所有的包都可以延迟应答么? 肯定也不是;

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

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

 捎带应答

         在延迟应答的基础上,可以发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的. 意味着客户端给服务器说 了 "How are you", 服务器也会给客户端回一个 "Fine, thank you";那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine, thank you" 一起回给客户端;

快重传

 报文丢失了怎么办?滑动窗口内的数据丢失分三种:1.最左侧的丢失 2.中间的丢失 3.最右侧的丢失;很简单,根据应答的确认序号即可知道,确认序号表示:在次之前所有的数据已经接收到;

 比如:发送消息的序号:500、600、700、800、900;但应答的是600(一次发99字节),那说明600序号以前的数据已经全部收到,也就是说发送序号为600的消息丢失了,后3个不知道是否丢失,然后立马补发600序号的消息,如果收到了1000的应答,那么说明消息补发完毕;

 拥塞控制

         因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的;TCP引入 慢启动|机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;

 第一次发送一条报文,第二次发送2条,第三次发送4条,2^n-1;

此处引入一个概念程为:拥塞窗口,发送开始的时候,定义拥塞窗口大小为1;每次收到一个ACK应答,拥塞窗口加1;每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;滑动窗口大小 = min(拥塞窗口大小,对方窗口大小);

拥塞窗口就是一个数字,发送数据量超过拥塞窗口的时候,很大概率会引起网络拥塞;

刚开始窗口大小较小,可以成倍的增加,到后边窗口变大就不能成倍增加,增长策略如下:

         为了不增长的那么快,因此不能使拥塞窗口单纯的加倍,此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长,在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;

        少量的丢包,仅仅是触发超时重传;大量的丢包,就认为是网络拥塞;当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案;实际发送情况不一定按照图中的规律(需要考虑对方接受能力);

粘包问题

粘包问题只会出现在TCP通信中,站在传输层的角度, TCP是一个一个报文过来的. 按照序号排好序放在缓冲区中;

站在应用层的角度,看到的只是一串连续的字节数据;

应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始,到哪个部分,是一个完整的应用层数据包;

 UDP没有粘包问题吗?

        UDP协议中有自描述字段(报文长度)可以知道报文长度是多少,对于UDP, 如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用 层. 就有很明确的数据边界. 站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现"半 个"的情况;

那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界.

  •  定长报文
  • 自描述字段(报文长度)
  • 使用明显的分隔符进行区分

 这个过程就是应用层自定义协议;

应用层协议很大部分是为了解决粘包问题,其次就是序列化和反序列化

序列化与反序列化:就算将缓冲区中的数据取出来,得到一个完整的报文,但是报文依然只是一个字符串;在程序中使用时一般都是结构体对象,因此还需要对字符串进行转化,这个转化的过程就是反序列化,将结构化的对象转化为字符串就叫序列化;

这里不做过多介绍;知道有这个概念就行;

 TIME_WAIT状态

 TIME_WAIT状态是 TCP 连接的一种状态,主要用于确保数据的可靠传输和连接的正确关闭;主动关闭连接的一方(即发送了 FIN 报文的一方)进入此状态;

 比如:

 服务端进入了TIME_WAIT状态;TIME_WAIT状态的持续时间通常是 2 倍的最大段生存时间(Maximum Segment Lifetime, MSL),通常设定为 2 到 4 分钟。这个时间可以根据操作系统的不同而有所变化;

为什么会有这个状态,或者说它的作用是什么?

  • 确保数据完整性:当一个 TCP 连接关闭后,可能还有一些数据在网络中尚未到达接收方。TIME_WAIT状态确保在连接关闭后,发送方仍然保持一段时间的状态,以便接收任何可能的重传数据。
  • 避免延迟重用端口:在 TIME_WAIT状态期间,TCP 不会立即重用该连接的端口号,这样可以避免在新连接中出现混淆,确保新连接不会接收到旧连接的数据。

对于客户端而言,进入 TIME_WAIT状态通常影响较小,主要原因:客户端的端口是随机分配的,其次是该状态的持续时间不会很长,因此影响较小;

使用setsockopt可以设置重复的使用端口号,来解除TIME_WAIT的影响

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

 使用示例:

int optval = 1; // 设置选项值为 1,表示启用 SO_REUSEADDR 
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(optval)

 重复使用不会影响新服务接收到的数据吗?

        可能会,但概率很小,几乎不会出现,这种情况出现的不频繁,其次就是缓冲区存放的数据一般不会重叠覆盖,因为起始的序号都是随机的,差别比较大,这也降低了数据混淆的概率;

 基于TCP协议的应用层协议:

  • HTTP
  • HTTPS
  • SSH
  • Telnet
  • FTP
  • SMTP

当然, 也包括你自己写TCP程序时自定义的应用层协议;


总结

        以上便是本文的全部内容,希望对你有所帮助,感谢阅读!


http://www.niftyadmin.cn/n/5866974.html

相关文章

w227springboot旅游管理系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

【Unity】鱼群效果模拟

鱼群效果模拟 文章目录 鱼群效果模拟Boid算法实现方式version1_CPUversion2_GPUversion3_Multilaterationversion4_Bitonic_Sorting &#xff08;GPU友好&#xff09;version5_Skinning &#xff08;TODO&#xff09; 细节项优化项参考链接 Boid算法 Boid算法是一种模拟群体行…

ASP.NET Core Clean Architecture

文章目录 项目地址一、项目主体1. CQRS1.1 Repository数据库接口1.2 GetEventDetail 完整的Query流程1.3 创建CreateEventCommand并使用validation 2. EFcore层2.1 BaseRepository2.2 CategoryRepository2.3 OrderRepository 3. Email/Excel导出3.1 Email1. IEmail接口层2. Ema…

C++的allactor

https://zhuanlan.zhihu.com/p/693267319 1 双层内存配置器 SGI设计了两层的配置器&#xff0c;也就是第一级配置器和第二级配置器。同时为了自由选择&#xff0c;STL又规定了 __USE_MALLOC 宏&#xff0c;如果它存在则直接调用第一级配置器&#xff0c;不然则直接调用第二级配…

华为数通 HCIP-Datacom H12-831 新题

2024年 HCIP-Datacom&#xff08;H12-831&#xff09;变题后的新题&#xff0c;完整题库请扫描上方二维码&#xff0c;新题在持续更新中。 某台IS-IS路由器自己生成的LSP信息如图所示&#xff0c;从LSP信息中不能推断出以下哪一结论? A&#xff1a;该路由器某一个接口的IPv6地…

本地VSCode远程连wsl2中的C++环境的开发配置指南

请参考上一遍文章&#xff1a;在windows上安装wsl2&#xff0c;在wsl2中配置C开发环境-CSDN博客

量子计算如何改变加密技术:颠覆与变革的前沿

量子计算如何改变加密技术:颠覆与变革的前沿 大家好,我是Echo_Wish,一名专注于人工智能和Python的自媒体创作者。今天,我们来探讨一个前沿且引人深思的话题——量子计算如何改变加密技术。随着量子计算的快速发展,传统的加密技术面临前所未有的挑战和机遇。本文将详细介绍…

算法(四)——动态规划

文章目录 基本思想适用条件最优子结构子问题重叠状态转移方程 解题步骤应用斐波那契数列背包问题最大子数组和 基本思想 动态规划的核心思想在于将一个复杂的问题分解为一系列相互关联的子问题&#xff0c;通过求解子问题并保存其解&#xff0c;避免对相同子问题的重复计算&am…