网络编程之 UDP:用户数据报协议详解与实战

UDP(User Datagram Protocol)作为传输层的重要协议,以其无连接、不可靠但高效的特性,在实时通信、流媒体等领域有着广泛应用。本文将深入解析 UDP 的核心概念,并通过实战案例展示其编程实现。

一、UDP 协议特性

UDP 与 TCP 相比,具有以下特点:

  • 无连接:通信前无需建立连接,直接发送数据。
  • 不可靠:不保证数据的可靠传输,可能丢包、乱序。
  • 高效:无需维护连接状态,开销小,适合实时性要求高的场景。
  • 面向数据报:数据以独立的数据包形式传输,边界清晰。
二、UDP 编程框架

UDP 编程采用 C/S 模式,基本框架如下:

服务器端

复制代码
socket() → bind() → recvfrom() → close()

客户端

复制代码
socket() → [bind()] → sendto() → close()

关键函数

  • socket(PF_INET, SOCK_DGRAM, 0):创建 UDP 套接字。
  • sendto():发送数据报。
  • recvfrom():接收数据报。
三、UDP 核心函数解析
1. sendto() 函数
复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
  • 功能:向指定目标发送数据。
  • 参数
    • dest_addr:目标地址(必选)。
    • addrlen:地址长度。
2. recvfrom() 函数
复制代码
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  • 功能:接收数据并获取发送方地址(可选)。
  • 参数
    • src_addr:发送方地址(若为 NULL 则忽略)。
    • addrlen:地址长度指针。
四、UDP 编程实战
1. UDP 测试程序

服务器端代码(udp_server.c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 50000
#define BUF_SIZE 1024

int main() {
    // 创建 UDP 套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORT);

    // 绑定套接字
    if (bind(sockfd, (const struct sockaddr *)&serv_addr, 
             sizeof(serv_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    printf("UDP Server listening on port %d...\n", PORT);

    // 接收数据
    char buffer[BUF_SIZE];
    struct sockaddr_in cli_addr;
    socklen_t len = sizeof(cli_addr);

    while (1) {
        memset(buffer, 0, BUF_SIZE);
        ssize_t n = recvfrom(sockfd, (char *)buffer, BUF_SIZE,
                            MSG_WAITALL, (struct sockaddr *)&cli_addr,
                            &len);
        if (n < 0) {
            perror("recvfrom failed");
            continue;
        }

        printf("Client [%s:%d]: %s\n",
               inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port),
               buffer);

        // 回显消息
        sendto(sockfd, (const char *)buffer, strlen(buffer),
               MSG_CONFIRM, (const struct sockaddr *)&cli_addr, len);
    }

    close(sockfd);
    return 0;
}

客户端代码(udp_client.c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"
#define PORT 50000
#define BUF_SIZE 1024

int main() {
    // 创建 UDP 套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    char buffer[BUF_SIZE];
    socklen_t len = sizeof(serv_addr);

    while (1) {
        printf("Enter message (or 'exit' to quit): ");
        fgets(buffer, BUF_SIZE, stdin);
        buffer[strcspn(buffer, "\n")] = 0;  // 去除换行符

        if (strcmp(buffer, "exit") == 0) {
            break;
        }

        // 发送数据
        sendto(sockfd, (const char *)buffer, strlen(buffer),
               MSG_CONFIRM, (const struct sockaddr *)&serv_addr, len);

        // 接收响应
        memset(buffer, 0, BUF_SIZE);
        ssize_t n = recvfrom(sockfd, (char *)buffer, BUF_SIZE,
                            MSG_WAITALL, (struct sockaddr *)&serv_addr,
                            &len);
        if (n < 0) {
            perror("recvfrom failed");
            continue;
        }

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

    close(sockfd);
    return 0;
}
2. 点对点聊天程序

基于 UDP 的点对点聊天程序需要双方同时作为客户端和服务器:

复制代码
// 简化版点对点聊天程序框架
void chat_client() {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    // 初始化地址...

    // 创建两个线程:一个接收消息,一个发送消息
    pthread_t recv_thread, send_thread;
    pthread_create(&recv_thread, NULL, receive_messages, &sockfd);
    pthread_create(&send_thread, NULL, send_messages, &sockfd);
    
    pthread_join(recv_thread, NULL);
    pthread_join(send_thread, NULL);
    close(sockfd);
}
五、UDP 聊天室实现
需求分析
  • 注册机制:客户端连接时向服务器注册。
  • 消息转发:服务器将消息广播给所有在线客户端。
  • 下线通知:客户端下线时通知其他用户。
核心设计

服务器端

复制代码
// 客户端信息结构体
typedef struct {
    struct sockaddr_in addr;
    char username[50];
    int online;
} Client;

Client clients[MAX_CLIENTS];  // 客户端列表

// 处理注册请求
void handle_registration(int sockfd, struct sockaddr_in *client_addr, char *username) {
    // 查找空位或已有客户端
    // 更新客户端信息
    // 通知其他客户端
}

// 处理消息转发
void handle_message(int sockfd, struct sockaddr_in *client_addr, char *message) {
    // 查找发送者
    // 转发消息给所有在线客户端
}

// 主循环
while (1) {
    recvfrom(sockfd, buffer, BUF_SIZE, 0, 
             (struct sockaddr *)&client_addr, &len);
    
    // 解析消息类型(注册、消息、下线)
    // 调用相应处理函数
}

客户端

复制代码
// 发送线程
void *send_messages(void *arg) {
    int sockfd = *(int *)arg;
    struct sockaddr_in server_addr;
    // 初始化服务器地址...

    // 注册用户名
    sendto(sockfd, username, strlen(username), 0, 
           (struct sockaddr *)&server_addr, sizeof(server_addr));

    // 循环发送消息
    while (1) {
        fgets(message, BUF_SIZE, stdin);
        sendto(sockfd, message, strlen(message), 0, 
               (struct sockaddr *)&server_addr, sizeof(server_addr));
    }
}

// 接收线程
void *receive_messages(void *arg) {
    int sockfd = *(int *)arg;
    // 循环接收消息并打印
}
六、UDP 与 TCP 的对比
特性 UDP TCP
连接状态 无连接 面向连接
可靠性 不可靠(可能丢包) 可靠(保证交付)
传输效率 高(开销小) 低(维护连接开销大)
应用场景 实时音视频、DNS 文件传输、HTTP
七、总结

UDP 以其简单高效的特点,在需要快速传输、实时性要求高的场景中表现出色。本文从 UDP 的基本特性出发,详细解析了其编程框架和核心函数,并通过三个实战案例(测试程序、点对点聊天、聊天室)展示了 UDP 的应用方式。

虽然 UDP 不保证可靠传输,但在许多场景下,这种 "不可靠" 恰恰成为其优势。通过合理的上层设计(如重传机制、消息确认),UDP 也能在一定程度上弥补其不足,满足复杂应用的需求。

相关推荐
丁满与彭彭4 分钟前
嵌入式学习笔记--MCU阶段--DAY08总结
笔记·单片机·学习
soulermax23 分钟前
数字ic后端设计从入门到精通12(含fusion compiler, tcl教学)全定制设计进阶
嵌入式硬件·硬件架构
Meraki.Zhang1 小时前
【STM32实践篇】:串口通信
stm32·嵌入式硬件·串口·uart·usart
wind_one13 小时前
STM32小实验四--按键控制LED灯
stm32·单片机·嵌入式硬件
jingjing~6 小时前
STM32计数器记数实验(TIM2 + OLED显示)| 附完整源码
stm32·单片机·嵌入式硬件
XINVRY-FPGA12 小时前
XC7A35T‑2FGG484I Xilinx FPGA Artix‑7 AMD
嵌入式硬件·fpga开发·云计算·硬件架构·硬件工程·fpga·pcb工艺
明早你自己说13 小时前
学习寄存器——GPIO(二)学习BSRR BRR ODR寄存器的原子性和在HAL库的应用
单片机·嵌入式硬件·学习
2301_7931210414 小时前
基于单片机的数字温度计设计
单片机·嵌入式硬件
Dev_XH14 小时前
【成品设计】STM32户外便携太阳能充电器设计
stm32·单片机·嵌入式硬件