网络脉搏:UDP与TCP解码

目录

一、简要概述

TCP(传输控制协议)

UDP(用户数据报协议)

二、如何做到可靠性传输

1. 确认应答(ACK)与重传机制

2. 序号机制

3. 数据重排机制

4. 窗口机制

5. 流量控制

6. 带宽限制

三、UDP与TCP,如何选择

面向报文(Message-Oriented)

面向字节流(Stream-Oriented)

四、TCP和UDP格式对比

五、ARQ协议(Automatic Repeat reQuest)

1、ARQ协议-停等式(stop-and-wait)

2、ARQ协议-回退n帧(go-back-n)

3、ARQ协议-选择性重传(selective-repeat)

六、RTT和RTO

七、流量控制

八、相关问题总结

1. TCP的可靠性

TCP如何可靠,为什么可靠

UDP的用途和场景

2. KCP为什么牺牲带宽去换取速度

3. UDP客户端和服务端编程,以及服务端如何维护和客户端的连接逻辑

UDP客户端(C/C++)

UDP服务端

说明

服务端如何维护与客户端的连接逻辑


一、简要概述

UDP(用户数据报协议)和 TCP(传输控制协议)都是互联网协议套件中的核心传输层协议,它们各自有不同的特点和应用场景。

TCP(传输控制协议)

  • 可靠性:TCP 提供可靠的数据传输,通过序列号和确认应答机制确保数据完整性,无误差地传输数据。
  • 连接导向:在数据传输前,TCP 需要建立连接,通过三次握手过程建立起通信双方的连接。
  • 流量控制和拥塞控制:TCP 使用窗口机制调整发送速率,避免网络拥堵。
  • 顺序传输:TCP 保证数据包的顺序性,即使数据包在网络中的传输顺序被打乱,接收端也能按正确顺序重组这些数据包。
  • 适用场景:适用于需要高可靠性的应用,如网页浏览、文件传输、电子邮件等。

UDP(用户数据报协议)

  • 无连接:UDP 不需要建立连接,直接发送数据,减少了建立和维护连接的开销。
  • 高效率:UDP 头部开销小,处理速度快,适合于对速度要求高的场合。
  • 无拥塞控制:UDP 不进行流量控制和拥塞控制,因此它的发送速度不受网络拥塞的影响,但这也可能导致网络拥塞。
  • 不保证可靠性:UDP 不保证数据的可靠到达,也不保证顺序和完整性,可能出现丢包现象。
  • 适用场景:适用于对实时性要求高的应用,如视频会议、实时游戏和语音通话。

总之,TCP 更注重数据的安全性和完整性,而 UDP 更注重传输的速度和效率。用户可以根据实际需要选择合适的传输协议。

二、如何做到可靠性传输

可靠性传输是 TCP 的一大特点,它通过多种机制确保数据正确、完整地从发送端传输到接收端。以下是实现 TCP 可靠性传输的几个关键机制:

1. 确认应答(ACK)与重传机制

  • 功能:发送端发送数据后,需要接收端回送一个确认应答(ACK),确认已成功接收数据。如果发送端在预定时间内没有收到 ACK,它会重新发送数据。
  • 目的:确保数据包不会因为网络问题而丢失。

2. 序号机制

  • 功能:每个 TCP 数据包都被赋予一个序号。发送端按序发送数据包,接收端按序号顺序接收和重组这些数据包。
  • 目的:处理网络延迟或数据包乱序问题,确保数据的正确顺序。

3. 数据重排机制

  • 功能:接收端使用序号将乱序到达的数据包重新排列。
  • 目的:允许数据在传输过程中乱序,但在最终交付给应用程序前确保数据的正确顺序。

4. 窗口机制

  • 功能:TCP 使用窗口机制来控制发送端的发送速率。窗口大小是接收端告诉发送端它一次能接收多少数据。
  • 目的:防止接收端处理不过来导致数据溢出。

5. 流量控制

  • 功能:流量控制是通过动态调整窗口大小来实现的。如果接收端的缓冲区快满了,它会减小窗口大小,从而减慢发送端的发送速率。
  • 目的:确保发送端不会因发送过快而超出接收端的处理能力。

6. 带宽限制

  • 功能:TCP 通过拥塞控制算法来调整数据的发送速率,以适应网络的当前拥塞程度。
  • 目的:防止过多数据同时传输导致的网络拥堵和数据包丢失。

通过这些机制,TCP 提供了一种可靠的数据传输方式,能够自适应网络条件,确保数据在各种网络环境下都能可靠地传输。

三、UDP与TCP,如何选择

这里提一下,"面向报文"和"面向字节流"是两种不同的数据处理方式,它们定义了数据在传输过程中的组织和处理方法。

面向报文(Message-Oriented)

  • 定义:面向报文的通信方式中,数据被视为独立的、有界限的消息单元进行发送和接收。每个消息都被视为一个完整的数据包,保留了消息的边界。
  • 特点
    • 边界保留:传输系统会保持发送的消息边界,接收方完整地接收到发送方发送的单个消息。
    • 无连接:通常与无连接的协议(如UDP)相关,不需要建立和维护复杂的连接状态。
    • 适用场景:适合传输单独的、独立的消息,如日志文件、单个文档或实时通信数据(如VoIP或游戏数据包)。

面向字节流(Stream-Oriented)

  • 定义:面向字节流的通信方式中,数据被视为一个连续的流,没有固定的边界。数据如同水流一样被发送,接收方从这个流中连续地读取数据。
  • 特点
    • 无边界:数据传输不保留消息边界,发送和接收是一个连续的流程。
    • 连接导向:通常与需要建立连接的协议(如TCP)相关,确保数据的可靠传输和顺序性。
    • 适用场景:适合需要连续数据流的应用,如视频流、音频流或文件传输。

四、TCP和UDP格式对比

在网络中,我们认为传输是不可靠的,而在很多场景下我们需要的是可靠的数据,所谓的可靠,指的是数据能够正常收到,且能够顺序收到,于是就有了ARQ协议,TCP之所以可靠就基于此。 

五、ARQ协议(Automatic Repeat reQuest)

ARQ协议(Automatic Repeat-reQuest),即自动重传请求,是传输层的错误纠正协议之一,它通过使用确认和超时两个机制,在不可靠的网络上实现可靠的信息传输。
ARQ协议主要有3种模式:

  1. 停等式(stop-and-wait)ARQ
  2. 回退n帧(go-back-n)ARQ
  3. 选择性重传(selective-repeat)ARQ

1、ARQ协议-停等式(stop-and-wait)

 停等协议的工作原理如下

  1. 发送方对接收方发送数据包,然后等待接收方回复ACK并且开始计时。
  2. 在等待过程中,发送方停止发送新的数据包。
  3. 当数据包没有成功被接收方接收,接收方不会发送ACK.这样发送方在等待-
    定时间后,重新发送数据包。
  4. 反复以上步骤直到收到从接收方发送的ACK.

缺点:较长的等待事件导致低的数据传输速度。

2、ARQ协议-回退n帧(go-back-n)

为了克服停等协议长时间等待ACK的缺陷,连续ARQ协议会连续发送一组数据包,然后再
等待这些数据包的ACK。

什么是滑动窗口:发送方和接收方都会维护一个数据帧的序列,这个序列被称作窗口。发送方的
窗口大小由接收方确定
,目的在于控制发送速度,以免接收方的缓存不够大,而导致溢出,同时控
制流量也可以避免网络拥塞。协议中规定,对于窗口内未经确认的分组需要重传。

回退N步(Go-Back-N,GBN):回退N步协议允许发送方在等待超时的间歇,可以继续发送分 组。所有发送的分组,都带有序号。在GBN协议中,发送方需响应以下三种事件:

  1. 上层的调用。上层调用相应send()时,发送方首先要检查发送窗口是否已满。
  2. 接收ACK。在该协议中,对序号为n的分组的确认采取累积确认的方式,表明接收方已 正确接收到序号n以前(包括n)的所有分组。
  3. 超时。若出现超时,发送方将重传所有已发出但还未被确认的分组

对于接收方来说,若一个序号为n的分组被正确接收,并且按序,则接收方会为该分组返回一个ACK给发送方,并将该分组中的数据交付给上层。在其他情况下,接收方都会丢弃分组。若分组n已接收并交付,那么所有序号比n小的分组也已完成了交付。因此GBN采用累积确认是一个很自然的选择。发送方在发完一个窗口里的所有分组后,会检查最大的有效确认,然后从最大有效确认的后一个分组开始重传。

如上图所示,序号为2的分组丢失,因此分组2及之后的分组都将被重传。

总结:GBN采用的技术包括序号、累积确认、检验和以及计时/重传。

3、ARQ协议-选择性重传(selective-repeat)

虽然GBN改善了停等协议中时间等待较长的缺陷,但它依旧存在着性能问题。特别是当窗口长度很大的时候,会使效率大大降低。而SR协议通过让发送方仅重传在接收方丢失或损坏了的分组,从而避免了不必要的重传,提高了效率。在SR协议下,发送方需响应以下三种事件:

  1. 从上层收到数据。当从上层收到数据后,发送方需检查下一个可用于该分组的序号。若序号在窗口中则将数据发送。
  2. 接收ACK。若收到ACK,且该分组在窗口内,则发送方将那个被确认的分组标记为已接收。若该分组序号等于基序号,则窗口序号向前移动到具有最小序号的未确认分组处。若窗口移动后并且有序号落在窗口内的未发送分组,则发送这些分组。
  3. 超时。若出现超时,发送方将重传已发出但还未确认的分组。与GBN不同的是,SR协议中的每个分组都有独立的计时器

在SR协议下,接收方需响应以下三种事件: (假设接收窗口的基序号为4,分组长度也为4)

  1. 序号在[4,7]内的分组被正确接收。该情况下,收到的分组落在接收方的窗口内,一个ACK 将发送给发送方。若该分组是以前没收到的分组,则被缓存。若该分组的序号等于基序号4, 则该分组以及以前缓存的序号连续的分组都交付给上层,然后,接收窗口将向前移动。
  2. 序号在[0,3]内的分组被正确接收。在该情况下,必须产生一个ACK,尽管该分组是接收方 以前已确认过的分组。若接收方不确认该分组,发送方窗口将不能向前移动。
  3. 其他情况。忽略该分组 对于接收方来说,若一个分组正确接收而不管其是否按序,则接收方会为该分组返回一个ACK 给发送方。失序的分组将被缓存,直到所有丢失的分组都被收到,这时才可以将一批分组按 序交付给上层

六、RTT和RTO

  •  RTO(Retransmission TimeOut)即重传超时时间。
  •  RTT(Round-Trip Time): 往返时延。表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延。由三部分组成:
    • 链路的传播时间(propagation delay)
    • 末端系统的处理时间
    • 路由器缓存中的排队和处理时间(queuing delay)

其中,前两个部分的值对于一个TCP连接相对固定,路由器缓存中的排队和处理时间会随着整个网络拥塞程度的变化而变化。 所以RTT的变化在一定程度上反应网络的拥塞程度。

七、流量控制

  • 双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方 的发送速率太快,会导致接收方处理不过来,这时候接收方只能把处理不过来 的数据存在缓存区里(失序的数据包也会被存放在缓存区里)接收缓存。
  • 如果缓存区满了发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉, 大量的丢包会极大着浪费网络资源,因此,我们需要控制发送方的发送速率, 让接收方与发送方处于一种动态平衡才好。
  • 对发送方发送速率的控制,称之为流量控制。
  • 接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩余多少 是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量win来表示接收窗口的大小。
  • 发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接 收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生

当发送方停止发送数据后,该怎样才能知道自己可以继续发送数据?

  1. 当接收方处理好数据,接受窗口 win > 0 时,接收方发个通知报文去通知发送方,告诉他可以继续发送数据了。当发送方收到窗口大于0的报文时,就继续发送数据。
  2. 当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器。

总结:

  1. 通信的双方都拥有两个滑动窗口,一个用于接受数据,称之为接收窗口;一个用于 发送数据,称之为拥塞窗口(即发送窗口)。指出接受窗口大小的通知我们称之为窗口 通告。
  2. 接收窗口的大小固定吗?接受窗口的大小是根据某种算法动态调整的。
  3. 接收窗口越大越好吗?当接收窗口达到某个值的时候,再增大的话也不怎么会减少 丢包率的了,而且还会更加消耗内存。所以接收窗口的大小必须根据网络环境以及 发送发的的拥塞窗口来动态调整。
  4. 发送窗口和接收窗口相等吗?接收方在发送确认报文的时候,会告诉发送发自己的 接收窗口大小,而发送方的发送窗口会据此来设置自己的发送窗口,但这并不意味 着他们就会相等。首先接收方把确认报文发出去的那一刻,就已经在一边处理堆在 自己缓存区的数据了,所以一般情况下接收窗口>= 发送窗口

八、相关问题总结

1. TCP的可靠性

TCP如何可靠,为什么可靠

TCP的可靠性机制包括以下几个方面:

  1. 序列号和确认机制:TCP为每个字节的数据分配一个序列号,接收方通过确认号(ACK)告诉发送方已经成功接收的数据范围,确保数据顺序和完整性。

  2. 重传机制:如果发送的数据没有在预期时间内收到确认,TCP会重传这些数据,直到接收到确认或达到最大重传次数。

  3. 流量控制:通过滑动窗口机制,TCP动态调整发送方的数据发送速率,防止接收方缓冲区溢出。

  4. 拥塞控制:TCP使用多种拥塞控制算法(如慢启动、拥塞避免、快速重传和快速恢复)来避免网络拥塞,保证数据传输的稳定性。

  5. 校验和:每个TCP段都包含一个校验和,用于检测传输过程中是否出现数据损坏,如果检测到错误,接收方会丢弃该段。

UDP的用途和场景

UDP(用户数据报协议)虽然不提供TCP那样的可靠性,但在某些场景下具有明显优势:

  1. 低延迟应用:如实时音视频通信、在线游戏等,延迟比可靠性更重要。

  2. 简单的请求-响应应用:如DNS查询,数据量小且无需建立连接。

  3. 广播和多播:UDP支持广播和多播,适合发送同一数据给多个接收方。

2. KCP为什么牺牲带宽去换取速度

KCP是一个基于UDP的传输协议,它通过牺牲一定的带宽来换取更快的传输速度,适用于高实时性需求的场景。具体原因如下:

  1. 快速重传:KCP可以快速检测丢包并重传数据,减少等待时间,提高传输效率。

  2. 冗余数据:为了保证数据可靠性,KCP会发送冗余数据以应对网络波动,这会增加带宽占用。

  3. 流控和拥塞控制:KCP使用自定义的流量控制和拥塞控制策略,适应不同网络环境,但这可能会增加数据包的数量。

3. UDP客户端和服务端编程,以及服务端如何维护和客户端的连接逻辑

使用C/C++实现的UDP客户端和服务端的示例代码:

UDP客户端(C/C++)
#include <iostream>
#include <cstring>
#include <arpa/inet.h>

#define PORT 10000
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE] = "This is the message. It will be repeated.";
    socklen_t addr_len = sizeof(server_addr);

    // 创建UDP套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("Socket creation failed");
        return -1;
    }

    // 填写服务器地址信息
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    // 发送数据
    if (sendto(sockfd, buffer, strlen(buffer), 0, (const struct sockaddr *)&server_addr, addr_len) < 0) {
        perror("Send failed");
        close(sockfd);
        return -1;
    }

    // 接收响应
    memset(buffer, 0, BUFFER_SIZE);
    if (recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&server_addr, &addr_len) < 0) {
        perror("Receive failed");
        close(sockfd);
        return -1;
    }

    std::cout << "Received: " << buffer << std::endl;

    // 关闭套接字
    close(sockfd);
    return 0;
}
UDP服务端
#include <iostream>
#include <cstring>
#include <arpa/inet.h>

#define PORT 10000
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    char buffer[BUFFER_SIZE];
    socklen_t addr_len = sizeof(client_addr);

    // 创建UDP套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("Socket creation failed");
        return -1;
    }

    // 填写服务器地址信息
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字到端口
    if (bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(sockfd);
        return -1;
    }

    while (true) {
        memset(buffer, 0, BUFFER_SIZE);
        std::cout << "\nWaiting to receive message" << std::endl;

        // 接收数据
        if (recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len) < 0) {
            perror("Receive failed");
            close(sockfd);
            return -1;
        }

        std::cout << "Received: " << buffer << " from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl;

        // 发送响应
        if (sendto(sockfd, buffer, strlen(buffer), 0, (const struct sockaddr *)&client_addr, addr_len) < 0) {
            perror("Send failed");
            close(sockfd);
            return -1;
        }
    }

    // 关闭套接字
    close(sockfd);
    return 0;
}
说明
  1. UDP客户端:客户端代码创建一个UDP套接字,填写服务器地址信息,并发送数据到服务器。然后等待接收服务器的响应,接收后打印输出并关闭套接字。

  2. UDP服务端:服务端代码创建一个UDP套接字,绑定到指定端口,进入一个循环等待接收客户端消息。收到消息后,打印消息和客户端地址,然后将消息回送给客户端。

服务端如何维护与客户端的连接逻辑

虽然UDP是无连接的,但服务端可以通过以下方式维护与客户端的“连接”状态:

  1. 维护客户端状态表:服务端可以记录每个客户端的地址和状态信息(如上次通信时间、数据包序列号等),模拟连接状态。

  2. 心跳机制:通过定期发送心跳包,服务端可以检测客户端的活跃状态,并更新状态表。

  3. 超时机制:如果在一定时间内没有收到客户端的消息,服务端可以认为该客户端已经断开,并清理其状态信息。

这些措施可以帮助服务端更好地管理与客户端的通信,提供类似TCP的可靠性和连接管理功能。

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值