Linux C语言UDP编程如何实现可靠通信?

99ANYc3cd6
预计阅读时长 26 分钟
位置: 首页 C语言 正文

UDP (User Datagram Protocol) 是一种无连接的、不可靠的、但传输速度较快的传输层协议,它不像 TCP 那样需要建立连接(三次握手),直接发送数据包,因此开销小,延迟低,适用于对实时性要求高、能容忍少量丢包的场景,如视频会议、在线游戏、DNS 查询等。

linux c语言 udp
(图片来源网络,侵删)

下面我们将通过一个完整的“回显服务器”(Echo Server)和客户端的例子来学习 UDP 编程的核心步骤。

核心概念

在开始编码前,先了解几个关键的系统调用和数据结构:

  1. socket(): 创建一个套接字,这是进行网络通信的端点。
  2. bind(): 将套接字与一个特定的 IP 地址和端口号绑定,对于服务器来说,这是必须的,这样客户端才知道数据该发到哪里。
  3. sendto(): 发送数据,UDP 是无连接的,所以每次发送都需要指定目标地址(IP 和端口)。
  4. recvfrom(): 接收数据,UDP 也是无连接的,所以接收时不仅能拿到数据,还能知道数据来自哪个地址(谁发的)。
  5. struct sockaddr_in: 用于处理 IPv4 地址的结构体,它包含了端口号、IP 地址等信息。
  6. inet_addr() / inet_pton(): 将点分十进制的 IP 地址字符串(如 "127.0.0.1")转换为网络字节序的二进制格式。
  7. htons() / ntohs(): 主机字节序和网络字节序之间的转换,网络协议规定多字节数据(如端口号)必须使用网络字节序(大端序),而不同 CPU 的主机字节序可能不同(小端序或大端序),因此必须进行转换。

完整示例:UDP 回显服务器和客户端

这个例子包含两个程序:

  • udp_server.c: 一个服务器,它接收客户端发来的任何消息,然后将原消息发回给客户端。
  • udp_client.c: 一个客户端,它从命令行读取消息,发送给服务器,并打印服务器返回的消息。

服务器端代码 (udp_server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    char buffer[BUFFER_SIZE] = {0};
    socklen_t client_addr_len = sizeof(client_addr);
    // 1. 创建套接字 (socket)
    // AF_INET: IPv4
    // SOCK_DGRAM: UDP
    // 0: 自动选择协议 (对于 SOCK_DGRAM,只能是 IPPROTO_UDP)
    if ((server_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    // 设置套接字选项,允许地址重用
    // 这在快速重启服务器时很有用,可以避免 "Address already in use" 错误
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    printf("Socket created successfully.\n");
    // 2. 绑定地址和端口 (bind)
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET; // IPv4
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的网络接口
    server_addr.sin_port = htons(PORT); // 将端口号转换为网络字节序
    // 将套接字绑定到指定的地址和端口
    if (bind(server_fd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    printf("Server bound to port %d.\n", PORT);
    // 3. 循环接收和发送数据 (recvfrom & sendto)
    while (1) {
        printf("Waiting for a message...\n");
        // 从客户端接收数据
        // recvfrom 会将客户端的地址信息存入 client_addr
        int n = recvfrom(server_fd, (char *)buffer, BUFFER_SIZE, 0,
                         (struct sockaddr *)&client_addr, &client_addr_len);
        if (n < 0) {
            perror("recvfrom failed");
            continue; // 继续等待下一个消息
        }
        buffer[n] = '\0'; // 确保字符串正确终止
        // 打印接收到的消息和客户端信息
        printf("Received from %s:%d -> %s\n",
               inet_ntoa(client_addr.sin_addr),
               ntohs(client_addr.sin_port),
               buffer);
        // 将接收到的消息回显给客户端
        sendto(server_fd, (const char *)buffer, n, 0,
               (const struct sockaddr *)&client_addr, client_addr_len);
        printf("Echo message sent back.\n");
    }
    // 4. 关闭套接字 (close)
    // 由于代码在 while(1) 循环中,这里理论上不会执行
    close(server_fd);
    return 0;
}

客户端代码 (udp_client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define SERVER_IP "127.0.0.1" // 本地回环地址
#define BUFFER_SIZE 1024
int main() {
    int sock_fd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE] = {0};
    // 1. 创建套接字 (socket)
    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    printf("Socket created successfully.\n");
    // 2. 设置服务器地址信息 (不需要 bind,系统会自动分配)
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    // 将 IP 地址字符串转换为网络字节序的二进制格式
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }
    // 3. 循环读取用户输入并发送/接收
    while (1) {
        printf("Enter a message to send (or 'exit' to quit): ");
        fgets(buffer, BUFFER_SIZE, stdin);
        // 去掉末尾的换行符
        buffer[strcspn(buffer, "\n")] = 0;
        if (strcmp(buffer, "exit") == 0) {
            break;
        }
        // 发送数据到服务器
        sendto(sock_fd, (const char *)buffer, strlen(buffer), 0,
               (const struct sockaddr *)&server_addr, sizeof(server_addr));
        printf("Message sent to server.\n");
        // 从服务器接收回显消息
        int n = recvfrom(sock_fd, (char *)buffer, BUFFER_SIZE, 0,
                         NULL, NULL); // 客户端不需要关心返回地址
        if (n < 0) {
            perror("recvfrom failed");
            continue;
        }
        buffer[n] = '\0';
        printf("Received from server -> %s\n", buffer);
    }
    // 4. 关闭套接字 (close)
    close(sock_fd);
    return 0;
}

编译和运行

  1. 保存代码: 将上面的代码分别保存为 udp_server.cudp_client.c

    linux c语言 udp
    (图片来源网络,侵删)
  2. 编译: 使用 GCC 编译器进行编译。

    # 编译服务器
    gcc udp_server.c -o udp_server
    # 编译客户端
    gcc udp_client.c -o udp_client
  3. 运行:

    • 首先在一个终端窗口启动服务器:

      ./udp_server

      你会看到输出:

      linux c语言 udp
      (图片来源网络,侵删)
      Socket created successfully.
      Server bound to port 8080.
      Waiting for a message...
    • 然后在另一个终端窗口启动客户端:

      ./udp_client

      你会看到输出:

      Socket created successfully.
      Enter a message to send (or 'exit' to quit):
    • 在客户端输入消息并发送:

      Enter a message to send (or 'exit' to quit): Hello, UDP Server!
      Message sent to server.
      Received from server -> Hello, UDP Server!
      Enter a message to send (or 'exit' to quit):
    • 在服务器端观察输出:

      Waiting for a message...
      Received from 127.0.0.1:54321 -> Hello, UDP Server!
      Echo message sent back.
      Waiting for a message...

      (注意:客户端的端口号 54321 每次运行可能会不同)

    • 在客户端输入 exit 即可退出程序,服务器会继续运行,等待下一个客户端的连接。


UDP 编程流程总结

角色 步骤 函数 说明
服务器 创建套接字 socket() 创建通信端点。
绑定地址和端口 bind() 告诉系统这个套接字负责哪个 IP 和端口,这是服务器的关键步骤。
接收数据 recvfrom() 阻塞等待,直到收到数据包,并获取发送方的地址信息。
(可选) 发送数据 sendto() 将处理后的数据发回给特定的客户端地址。
关闭套接字 close() 释放资源。
客户端 创建套接字 socket() 创建通信端点。
(不需要 bind) 客户端通常不需要 bind,操作系统会自动为它分配一个临时的、可用的端口。
发送数据 sendto() 将数据发送到已知的服务器地址(IP + 端口)。
接收数据 recvfrom() 阻塞等待,接收服务器的响应。
关闭套接字 close() 释放资源。

关键区别:UDP vs. TCP

特性 UDP TCP
连接性 无连接 面向连接(需三次握手)
可靠性 不可靠(不保证不丢失、不重复、不乱序) 可靠(通过确认、重传、排序机制保证)
传输效率 高(开销小,无连接建立和拆除过程) 相对较低(有连接开销和流量控制)
数据形式 数据报(Datagram),有边界 字节流(Stream),无边界
适用场景 实时应用(视频、游戏)、DNS查询、广播 文件传输、网页浏览、邮件等要求可靠性的场景

希望这个详细的教程能帮助你理解 Linux C 语言下的 UDP 编程!

-- 展开阅读全文 --
头像
dede网上复制图片怎么用?
« 上一篇 03-16
dede分页内容如何发
下一篇 » 03-16

相关文章

取消
微信二维码
支付宝二维码

目录[+]