【网络进阶】Posix API与网络协议栈(四)

1. UDP数据帧

用户数据报协议(User Datagram Protocol,UDP)是一种无连接的网络协议,在传输层提供了简单的、不可靠的数据报传送服务。因为UDP缺乏复杂的特性如超时重传、乱序数据处理、拥塞控制等,所以它相对于TCP,拥有更低的延迟和开销。这使得UDP成为实时应用(如VoIP、游戏)以及简单查询应用(如DNS)的首选。

UDP数据帧结构比较简单,包括以下几部分:

  • 源端口(Source Port):这是一个可选字段,标识发送UDP数据报的应用程序的端口。
  • 目标端口(Destination Port):这个字段用于标识接收UDP数据报的应用程序的端口。
  • 长度(Length):该字段定义了UDP头部和数据的总长度。
  • 校验和(Checksum):这是一个可选字段,用于检查头部和数据的错误。

当然,我将重新描述以尽可能清晰地阐述整个UDP数据帧的创建和传输过程。

1.1 UDP数据帧在网络协议栈中的传输过程

假设一个应用程序想要发送一份数据给另一台机器上的应用程序。这个过程可以分解为以下步骤:

  1. 应用层(Application Layer):发送端的应用程序将数据准备好,并决定使用UDP协议进行传输。这个数据被称为原始数据或者负载。

  2. 传输层(Transport Layer):应用层将数据交给传输层的UDP协议。UDP协议将添加UDP头部,这个头部包含源端口和目标端口,以及长度和校验和字段。这个过程形成了UDP数据报。

  3. 网络层(Network Layer):传输层的UDP协议将UDP数据报交给网络层的IP协议。IP协议将添加IP头部,包括源IP地址和目标IP地址。这个过程形成了IP数据报,其中包含UDP数据报。

  4. 链路层(Link Layer):网络层将IP数据报交给链路层。链路层将在IP数据报的前后分别添加链路层的头部和尾部,形成一个数据帧。然后,这个数据帧将通过物理介质(如以太网、无线网络)发送出去。

当数据帧到达接收端的机器时,这个过程将反向进行:

  1. 链路层(Link Layer):接收端的链路层从物理介质上接收到数据帧,移除头部和尾部,然后将IP数据报交给网络层。

  2. 网络层(Network Layer):网络层接收到IP数据报,移除IP头部,然后将UDP数据报交给传输层。

  3. 传输层(Transport Layer):传输层接收到UDP数据报,移除UDP头部,然后将原始数据或负载交给应用层。

  4. 应用层(Application Layer):最后,应用层接收到原始数据,然后进行后续的处理。

以上就是UDP数据帧在网络协议栈中的传输过程。可以看出,原始数据在传输过程中,会被逐层封装成数据帧,然后在接收端被逐层剥离出来。每一层都在数据上添加自己的头部信息,以实现各自的功能。而UDP的作用就是在传输层提供端到端的数据传送服务。

1.2 UDP数据帧的特性

  1. 无连接:UDP是一种无连接的协议,意味着发送数据之前无需建立连接。这使得UDP特别适用于一次性的数据传输,如DNS查询或视频流。

  2. 不可靠性:UDP是一种不可靠的协议,它不保证数据帧一定能够成功地到达目的地。如果网络出现拥塞,UDP数据帧可能会被丢弃。此外,如果数据帧以错误的顺序到达,UDP也不会进行重新排序。

  3. 适用于实时应用:由于UDP无需确认信息、重传机制或复杂的链接建立过程,其具有极低的延迟。这使得UDP在实时应用如VoIP通话、在线游戏等中有着极高的应用价值。尽管可能会有一定量的数据丢失,但对于这些应用来说,延迟的增加会对用户体验造成更大的影响。

  4. 支持广播和多播:UDP支持广播和多播,广播允许一个UDP数据帧被发送到网络上的所有主机,而多播则允许数据帧被发送到一个特定的主机组。这使得UDP非常适合在网络上分发信息,例如在网络上播放实时视频。

2. 以太网协议头

以太网协议头是数据链路层的一部分,用于指定以太网帧的源地址和目标地址,并定义了帧的类型。

2.1 以太网协议头的主要组成部分

  1. 目的地址(Destination Address):这个字段是一个6字节的字段,包含了目的地以太网接口的物理地址。如果一台机器想要发送一个数据帧,它将把目的地机器的以太网地址写入这个字段。

  2. 源地址(Source Address):这个字段也是6字节,包含了发送机器以太网接口的物理地址。这样接收端就能知道这个数据帧来自于哪里。

  3. 类型/长度(Type/Length):这是一个2字节的字段。如果这个字段的值大于等于1536(0x0600),那么它表示的是以太网帧的类型;如果这个字段的值小于等于1500,那么它表示的是以太网帧的长度。类型字段用于标识上层协议类型,例如IPv4或IPv6。

  4. 虚拟局域网标签(VLAN Tag,可选):这是一个可选的4字节字段,包含了优先级代码点(Priority Code Point,PCP)、帧识别协议标识符(Drop Eligible Indicator,DEI)以及虚拟局域网标识符(VLAN Identifier,VID)。这个字段不是在所有的以太网帧中都存在,只在802.1Q VLAN标签存在的情况下出现。

请注意,以上字段描述的是以太网帧的头部,实际的以太网帧还包括数据部分以及帧校验序列(Frame Check Sequence,FCS)字段,FCS字段用于对以太网帧的完整性进行校验,以检测数据在传输过程中是否发生错误。

以太网帧头部的设计使得网络设备能够准确地将数据帧发送到正确的目的地,同时也提供了对数据完整性的一定保障。在实际网络环境中,以太网协议是目前最为广泛应用的局域网技术之一,它的设计思想和技术细节值得我们深入学习和理解。

2.2 以太网协议的设计思想和技术细节

  1. 简单性:以太网的设计理念之一是保持简单。比如,以太网是一种基于广播的协议,所有节点都在同一个广播域中。这意味着每个节点都会接收到所有的数据帧,然后根据以太网帧头的目的地址来决定是否处理这个数据帧。

  2. 无连接性:以太网是无连接的,也就是说,发送数据帧的节点不需要在发送数据前与接收节点建立连接。这种无连接性质让以太网更加简单和高效,但也意味着以太网本身不提供任何可靠性保证。

  3. 媒体访问控制(MAC):以太网使用CSMA/CD(载波侦听多路访问/冲突检测)作为其媒体访问控制方法。当一个以太网节点需要发送数据时,它首先会侦听媒体(通常是电缆)以检测是否有其他节点正在发送数据。如果没有,它就会开始发送数据。如果两个节点同时开始发送数据,会发生冲突,此时,节点会停止发送,等待随机时间后再次尝试发送。

  4. 帧结构:以太网帧的结构设计简单且高效,包括目的地址、源地址、类型/长度和数据负载等字段。另外,还有一个帧校验序列字段用于错误检测。这种帧结构既满足了数据传输的基本需求,又保证了整体的简单性。

  5. 可扩展性:以太网的设计还具有良好的可扩展性。例如,原始的以太网只支持10Mbps的传输速率,但随着技术的进步,现在的以太网可以支持100Mbps、1Gbps甚至10Gbps以上的速率。此外,以太网还支持各种物理媒介,包括双绞线、光纤和无线电波等。

以上就是以太网协议的设计思想和技术细节的一部分。总的来说,以太网的设计强调了简单性、无连接性和可扩展性,使其能够在各种网络环境中广泛应用。

3. IP协议

3.1 IP协议的功能与特点

互联网协议(IP)是互联网协议套件(TCP/IP)的核心协议,它负责将封装在IP数据报中的数据从源主机发送到目标主机。其核心功能包括地址标识、分片与重组、路由选择和错误处理等。

  • 地址标识:IP地址是网络中设备的唯一标识,使得数据报能够准确地从源主机路由到目标主机。IP地址分为IPv4(32位)和IPv6(128位)两种格式。

  • 分片与重组:IP协议提供了分片(fragmentation)和重组(reassembly)机制,可以将大的数据报分解为较小的数据报发送,并在接收端重新组合这些数据报,以适应不同网络环境中的最大传输单元(MTU)限制。

  • 路由选择:IP协议的一个关键功能是路由,它根据路由表中的信息,选择数据报从源地址到目标地址的最佳路径。

  • 错误处理:IP协议有能力处理数据报在传输过程中出现的错误,但不负责错误恢复。如果数据报在传输过程中出错,IP会通过生成ICMP消息通知发送端,但重传错误的数据报的任务通常由上层协议(如TCP)负责。

除了上述的核心功能,IP协议还有一些额外的特点和能力。例如,IP协议是无连接的,这意味着发送数据报的主机并不需要在发送之前与接收主机建立连接。此外,IP协议也是尽力而为(best effort)的,它不保证数据报的传输可靠性,不提供错误恢复,也不进行流量控制或拥塞控制,这些任务都交给了上层协议,如TCP。

3.2 IP协议头部

IP协议头部包含了数据报传输所需的所有重要信息。在IPv4中,协议头部的长度固定为20字节(除非使用了选项字段),而在IPv6中,头部的长度固定为40字节。协议头部的主要字段包括:

  • 版本:指示IP协议的版本,IPv4或IPv6。

  • 头部长度:指示IP头部的长度。

  • 服务类型/流量等级:在IPv4中,这个字段被称为服务类型(Type of Service),用于指示数据报的优先级和QoS需求;在IPv6中,这个字段被分为流量类别(Traffic Class)和流标签(Flow Label)两部分。

  • 总长度:指示整

个数据报(包括头部和数据)的长度。

  • 生存时间/跳限:在IPv4中,这个字段被称为生存时间(Time to Live,TTL),用于限制数据报在网络中的生存时间;在IPv6中,这个字段被称为跳限(Hop Limit)。

  • 协议/下一头部:在IPv4中,这个字段被称为协议,用于指示上层协议的类型;在IPv6中,这个字段被称为下一头部。

  • 头部校验和:用于检查头部在传输过程中是否出错。

  • 源地址和目标地址:分别指示数据报的发送主机和接收主机的IP地址。

3.3 IP协议的选项和扩展

IP协议提供了一些选项和扩展,以支持更多的功能。在IPv4中,这些选项包括但不限于源路由选项、记录路由选项、时间戳选项等。然而,由于这些选项可能增加协议的复杂性,并引入安全问题,因此在实际中使用的并不多。

在IPv6中,为了简化头部并提高处理速度,大多数选项被移出了基本头部,放入了扩展头部中。IPv6定义了多种扩展头部,如Hop-by-Hop Options、Routing、Fragment、Authentication、Encapsulating Security Payload等。

4. UDP协议

用户数据报协议(User Datagram Protocol,UDP)是一个简单的面向数据报的传输层协议,它提供了一种无连接的、尽力而为的数据传输服务。以下是对UDP协议的详细介绍:

4.1 UDP协议的功能与特点

UDP协议的主要功能是提供一个简单快速的数据传输服务。虽然UDP协议并不提供任何数据可靠性、顺序性或数据完整性的保证,但是正是因为这种简单性,使得UDP在许多应用场景中都有优势。比如,对于一些对实时性要求较高、可以容忍一定丢包的应用,如VoIP(语音通话)、视频会议、在线游戏等,都优先选择使用UDP。

另一方面,UDP协议是无连接的,这意味着发送和接收数据之间没有建立和断开连接的过程,可以随时发送数据,这种特性对于一些请求/应答模式的服务,如DNS查询,非常有用。

UDP协议还支持一对一、一对多、多对一和多对多的交互通信模式。

4.2 UDP数据报

UDP数据报由头部和数据两部分组成,头部长度固定为8字节。头部包含以下字段:

  • 源端口和目标端口:分别指示数据报的发送端和接收端的端口号。端口号是用来区分主机上的不同应用程序的。

  • 长度:指示整个UDP数据报(包括头部和数据)的长度。

  • 校验和:用于检查数据报在传输过程中是否出错。虽然这是一个可选字段,但是在实践中通常都会使用。

4.3 UDP和IP协议的关系

UDP协议工作在传输层,它依赖网络层的IP协议来完成数据报的发送和接收。UDP协议将应用层的数据封装成数据报,添加UDP头部后,将数据报交给IP协议。IP协议根据UDP数据报的目标IP地址和路由表,将数据报发送到目标主机。在目标主机接收到数据报后,IP协议将数据报交给UDP协议,UDP协议去除头部,将数据交给应用层。

5. UDP协议总结

在互联网中,数据的传输涉及到多个网络层次。每个层次都有其自身的责任和特性,协同工作以支持数据的发送和接收。在这个过程中,MAC地址、IP地址和端口号都扮演着重要的角色。

5.1 MAC地址

MAC地址是在数据链路层(以太网层)中使用的,它是网络接口控制器(也称为网络接口卡,NIC)的唯一标识。在以太网中,数据以帧的形式发送和接收,帧的头部包含了源MAC地址和目标MAC地址,这样才能确保帧能正确地从源主机发送到目标主机。

5.2 IP地址

IP地址是在网络层中使用的,它是主机在网络中的唯一标识。当数据报需要从源主机发送到目标主机时,会根据目标主机的IP地址来确定路由路径。IP数据报的头部包含了源IP地址和目标IP地址,这样才能确保数据报能按照正确的路由路径发送到目标主机。

5.3 端口号

端口号是在传输层(TCP或UDP)中使用的,它用于标识主机上的具体应用程序。由于一个主机上可能有多个应用程序同时使用网络服务,所以需要端口号来区分这些应用程序。在TCP或UDP数据报的头部,包含了源端口号和目标端口号,这样才能确保数据能发送到正确的应用程序。

5.4 UDP协议的总结

回顾上述内容,我们可以看到,UDP协议作为传输层协议,它的责任是提供应用程序之间的数据传输服务。UDP协议是无连接的,提供尽力而为的服务,也就是说,它不保证数据的可靠性、顺序性或完整性。然而,正是因为这种简单性,使得UDP协议在许多场景中都有优势,如VoIP、视频会议、在线游戏等。另一方面,UDP协议也依赖网络层的IP协议和数据链路层的以太网协议来完成其工作。总的来说,UDP协议是互联网协议栈中的一个重要组成部分,虽然它的功能相对简单,但是在网络通信中却有着重要的作用。

6. UDP案例

下面是一个用C语言编写的简单的UDP通信程序的示例,分别是客户端和服务器端的代码。我们将在发送和接收数据报时使用socket API,它是一种通用的网络编程接口。

6.1 UDP服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8888  // 服务器端口号
#define MAXLINE 1024  // 缓冲区大小

int main() {
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len;
    char buffer[MAXLINE];
    ssize_t n;

    // 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&server_addr, 0, sizeof(server_addr));
    memset(&client_addr, 0, sizeof(client_addr));

    // 服务器地址和端口的设置
    server_addr.sin_family = AF_INET;  // IPv4
    server_addr.sin_addr.s_addr = INADDR_ANY;  // 本机的任意IP
    server_addr.sin_port = htons(PORT);  // 端口号

    // 绑定socket和地址
    if (bind(sockfd, (const struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    while (1) {
        printf("Waiting for data...\n");

        client_addr_len = sizeof(client_addr);

        // 接收客户端的数据报
        n = recvfrom(sockfd, (char*)buffer, MAXLINE, 0, (struct sockaddr*)&client_addr, &client_addr_len);
        buffer[n] = '\0';  // 添加字符串结束符

        printf("Client: %s\n", buffer);

        // 将数据报发送回客户端
        sendto(sockfd, (char*)buffer, strlen(buffer), 0, (struct sockaddr*)&client_addr, client_addr_len);

        if (strncmp("exit", buffer, 4) == 0) {
            printf("Server Exit...\n");
            break;
        }
    }

    close(sockfd);

    return 0;
}

6.2 UDP客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8888  // 服务器端口号
#define MAXLINE 1024  // 缓冲区大小

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    char buffer[MAXLINE];
    ssize_t n;

    // 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&server_addr, 0, sizeof(server_addr));

    // 服务器地址和端口的设置
    server_addr.sin_family = AF_INET;  // IPv4
    server_addr.sin_addr.s_addr = INADDR_ANY;  // 服务器的IP地址
    server_addr.sin_port = htons(PORT);  // 端口号

    while (1) {
       

 printf("Enter message: ");
        fgets(buffer, sizeof(buffer), stdin);

        // 将数据报发送到服务器
        sendto(sockfd, (char*)buffer, strlen(buffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));

        // 接收服务器的数据报
        n = recvfrom(sockfd, (char*)buffer, MAXLINE, 0, NULL, NULL);
        buffer[n] = '\0';  // 添加字符串结束符

        printf("Server: %s\n", buffer);

        if (strncmp("exit", buffer, 4) == 0) {
            printf("Client Exit...\n");
            break;
        }
    }

    close(sockfd);

    return 0;
}

注意:在实际操作中,服务器和客户端应在不同的机器或不同的进程中运行。你需要替换客户端代码中的服务器IP地址为实际的服务器IP地址。

在这个案例中,UDP服务器和客户端都是用无限循环来持续接收和发送数据报的。当输入"exit"时,循环将会被终止,服务器和客户端将退出。

在以上示例中,我们使用了以下几个重要的socket API函数:

  • socket(): 创建一个新的socket。
  • bind(): 将socket和特定的IP地址和端口号绑定。
  • sendto(): 发送数据报到特定的地址。
  • recvfrom(): 从特定的地址接收数据报。

这个简单的示例显示了如何使用UDP协议进行数据的发送和接收,但请注意,UDP协议并不提供任何数据的可靠性、顺序性或完整性的保证。在实际应用中,如果需要这些特性,可能需要使用TCP协议,或者在应用层实现这些特性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricky_0528

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值