跳转到内容

C 语言 udp 编程

来自代码酷

C语言UDP编程[编辑 | 编辑源代码]

UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,与TCP不同,它不提供可靠性保证(如数据包顺序、重传机制等),但具有低延迟和高效率的特点,适用于实时性要求高的应用场景(如视频流、在线游戏等)。在C语言中,UDP编程通过套接字(socket)接口实现。

基本概念[编辑 | 编辑源代码]

UDP的特点[编辑 | 编辑源代码]

  • 无连接:通信前无需建立连接,直接发送数据。
  • 不可靠:不保证数据包的顺序、完整性或是否到达。
  • 高效:头部开销小(仅8字节),适合高频小数据量传输。
  • 支持广播/多播:可同时向多个目标发送数据。

适用场景[编辑 | 编辑源代码]

  • 实时应用(如VoIP、视频会议)
  • DNS查询
  • 在线多人游戏
  • 传感器数据上报

UDP编程核心步骤[编辑 | 编辑源代码]

以下是UDP通信的基本流程:

flowchart LR A[创建套接字] --> B[绑定端口(可选)] B --> C[发送/接收数据] C --> D[关闭套接字]

1. 创建套接字[编辑 | 编辑源代码]

使用`socket()`函数创建UDP套接字:

#include <sys/socket.h>
#include <netinet/in.h>

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

2. 绑定端口(服务端)[编辑 | 编辑源代码]

服务端通常需要绑定固定端口:

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
servaddr.sin_port = htons(8080);       // 端口号

if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
    perror("bind failed");
    exit(EXIT_FAILURE);
}

3. 发送数据[编辑 | 编辑源代码]

使用`sendto()`函数发送数据:

struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);
inet_pton(AF_INET, "192.168.1.100", &dest_addr.sin_addr);

char *message = "Hello UDP!";
sendto(sockfd, message, strlen(message), 0,
       (struct sockaddr*)&dest_addr, sizeof(dest_addr));

4. 接收数据[编辑 | 编辑源代码]

使用`recvfrom()`函数接收数据:

char buffer[1024];
struct sockaddr_in sender_addr;
socklen_t addr_len = sizeof(sender_addr);

int n = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                (struct sockaddr*)&sender_addr, &addr_len);
buffer[n] = '\0'; // 添加字符串终止符
printf("Received: %s\n", buffer);

完整示例[编辑 | 编辑源代码]

服务端代码[编辑 | 编辑源代码]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    int sockfd;
    struct sockaddr_in servaddr, cliaddr;
    char buffer[1024];

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    // 绑定
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(8080);
    bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

    printf("Server listening on port 8080...\n");

    while (1) {
        socklen_t len = sizeof(cliaddr);
        int n = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                        (struct sockaddr*)&cliaddr, &len);
        buffer[n] = '\0';
        printf("Client: %s\n", buffer);

        // 发送响应
        sendto(sockfd, "ACK", 3, 0,
              (struct sockaddr*)&cliaddr, len);
    }

    close(sockfd);
    return 0;
}

客户端代码[编辑 | 编辑源代码]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    int sockfd;
    struct sockaddr_in servaddr;
    char buffer[1024];

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    // 设置服务器地址
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);

    printf("Enter message: ");
    fgets(buffer, sizeof(buffer), stdin);
    
    // 发送数据
    sendto(sockfd, buffer, strlen(buffer), 0,
          (struct sockaddr*)&servaddr, sizeof(servaddr));

    // 接收响应
    recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
    printf("Server: %s\n", buffer);

    close(sockfd);
    return 0;
}

高级主题[编辑 | 编辑源代码]

超时设置[编辑 | 编辑源代码]

通过`setsockopt()`设置接收超时:

struct timeval tv;
tv.tv_sec = 5;  // 5秒超时
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

广播通信[编辑 | 编辑源代码]

允许向局域网内所有主机发送数据:

int broadcast = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));

struct sockaddr_in bc_addr;
bc_addr.sin_family = AF_INET;
bc_addr.sin_port = htons(8080);
bc_addr.sin_addr.s_addr = inet_addr("192.168.1.255"); // 广播地址

sendto(sockfd, "Broadcast Message", 17, 0,
      (struct sockaddr*)&bc_addr, sizeof(bc_addr));

实际应用案例[编辑 | 编辑源代码]

网络时间协议(NTP)客户端示例:

// 简化的NTP请求(实际协议更复杂)
void ntp_query(const char* server) {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in servaddr = {
        .sin_family = AF_INET,
        .sin_port = htons(123), // NTP标准端口
    };
    inet_pton(AF_INET, server, &servaddr.sin_addr);

    char packet[48] = { 0x1B }; // NTP协议头
    sendto(sockfd, packet, sizeof(packet), 0,
          (struct sockaddr*)&servaddr, sizeof(servaddr));

    recvfrom(sockfd, packet, sizeof(packet), 0, NULL, NULL);
    // 解析NTP响应(此处省略)
    close(sockfd);
}

常见问题[编辑 | 编辑源代码]

问题 解决方案
数据包丢失 应用层实现重传机制或改用TCP
数据包乱序 为数据包添加序列号,接收端重新排序
缓冲区溢出 适当增大接收缓冲区或分片处理大数据

性能优化建议[编辑 | 编辑源代码]

  • 使用`SO_RCVBUF`/`SO_SNDBUF`调整缓冲区大小
  • 多线程处理:单独线程负责接收数据
  • 批处理:合并小数据包减少系统调用次数

数学基础[编辑 | 编辑源代码]

UDP校验和计算(RFC 1071): checksum=i=0n1data[i]mod216

通过本文的学习,读者应能掌握C语言中UDP编程的核心方法,并理解其适用场景与优化方向。