c语言udp socket

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

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

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

下面我将分步介绍如何使用 C 语言创建 UDP 服务器和客户端。


核心概念与步骤

无论是服务器还是客户端,使用 UDP Socket 的基本步骤都如下:

  1. 创建套接字:使用 socket() 函数创建一个通信端点。
  2. 绑定地址 (服务器):服务器需要将套接字绑定到一个特定的 IP 地址和端口号,以便客户端知道往哪里发送数据。
  3. 发送/接收数据:使用 sendto()recvfrom() 函数进行数据传输,这两个函数是 UDP 编程的核心,因为它们需要指定数据要发送到的地址或从哪个地址接收的数据。
  4. 关闭套接字:通信结束后,使用 close() 函数关闭套接字,释放资源。

所需头文件

#include <stdio.h>      // 标准输入输出
#include <stdlib.h>     // 标准库函数
#include <string.h>     // 字符串操作
#include <unistd.h>     // POSIX 系统调用 (如 close)
#include <sys/socket.h> // Socket 相关函数和结构体
#include <netinet/in.h> // Internet 地址族 (sockaddr_in)
#include <arpa/inet.h>  // IP 地址转换函数 (inet_pton)
#include <errno.h>      // 错误码

UDP 服务器示例

服务器负责监听一个端口,接收来自客户端的数据,并可能将响应数据发回。

// 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>
#include <errno.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
    int server_fd, client_len;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in server_addr, client_addr;
    // 1. 创建套接字
    // 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);
    }
    // 2. 绑定地址和端口
    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");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("UDP Server listening on port %d...\n", PORT);
    // 3. 循环接收和发送数据
    while (1) {
        // 清空缓冲区
        memset(buffer, 0, BUFFER_SIZE);
        // 4. 接收来自客户端的数据
        // recvfrom 会将客户端的地址信息也填充到 client_addr 结构体中
        client_len = sizeof(client_addr);
        int n = recvfrom(server_fd, (char *)buffer, BUFFER_SIZE, 0,
                         (struct sockaddr *)&client_addr, &client_len);
        if (n < 0) {
            perror("recvfrom failed");
            continue; // 继续下一次循环,而不是退出
        }
        // 打印接收到的信息和客户端地址
        printf("Received from %s:%d -> %s\n",
               inet_ntoa(client_addr.sin_addr),
               ntohs(client_addr.sin_port),
               buffer);
        // 5. 将接收到的数据原样发回 (Echo Server)
        sendto(server_fd, (const char *)buffer, n, 0,
               (const struct sockaddr *)&client_addr, client_len);
        printf("Echoed back to the client.\n");
    }
    // 6. 关闭套接字 (理论上上面的 while(1) 不会退出)
    close(server_fd);
    return 0;
}

代码解释:

  • socket(): 创建了一个 UDP 套接字。
  • setsockopt(): 设置 SO_REUSEADDR 选项,这是一个好习惯,尤其是在开发调试阶段。
  • bind(): 将套接字绑定到本机的所有网络接口 (INADDR_ANY) 和 8080 端口。htons() 函数将主机字节序的端口号转换为网络字节序(大端序)。
  • recvfrom(): 这是 UDP 接收数据的关键函数。
    • 它会阻塞,直到有数据到达。
    • buffer 用于存储接收到的数据。
    • client_addr 会填充发送方的 IP 地址和端口号。
    • 返回值是接收到的字节数。
  • sendto(): 这是 UDP 发送数据的关键函数。
    • 它需要明确指定要发送到的地址 (client_addr)。
    • n 是要发送的字节数,我们这里使用接收到的字节数,实现回显功能。
  • inet_ntoa(): 将网络地址结构体 in_addr 中的 IP 地址转换为点分十进制字符串(如 "192.168.1.100")。
  • ntohs(): 将网络字节序的端口号转换回主机字节序,方便打印。

UDP 客户端示例

客户端主动向服务器发送数据,并接收服务器的响应。

// 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>
#include <errno.h>
#define SERVER_PORT 8080
#define SERVER_IP "127.0.0.1" // 本地回环地址,用于测试
#define BUFFER_SIZE 1024
int main() {
    int sock_fd;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in server_addr;
    // 1. 创建套接字
    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    // 2. 设置服务器地址信息
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;         // IPv4
    server_addr.sin_port = htons(SERVER_PORT); // 服务器端口
    // 将点分十进制 IP 地址转换为网络字节序
    // inet_pton 是更安全、更现代的函数,比 inet_aton 更好
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("invalid address/ Address not supported");
        close(sock_fd);
        exit(EXIT_FAILURE);
    }
    // 3. 与服务器交互
    while (1) {
        printf("Enter a message to send to the server (or 'exit' to quit): ");
        fgets(buffer, BUFFER_SIZE, stdin);
        // 去掉末尾的换行符
        buffer[strcspn(buffer, "\n")] = 0;
        if (strcmp(buffer, "exit") == 0) {
            break;
        }
        // 4. 发送数据到服务器
        sendto(sock_fd, (const char *)buffer, strlen(buffer), 0,
               (const struct sockaddr *)&server_addr, sizeof(server_addr));
        printf("Message sent.\n");
        // 5. 接收服务器的响应
        memset(buffer, 0, BUFFER_SIZE); // 清空缓冲区
        int n = recvfrom(sock_fd, (char *)buffer, BUFFER_SIZE, 0,
                         NULL, NULL); // 客户端不需要关心是谁发回的,所以可以传NULL
        if (n < 0) {
            perror("recvfrom failed");
            continue;
        }
        printf("Received from server: %s\n", buffer);
    }
    // 6. 关闭套接字
    close(sock_fd);
    return 0;
}

代码解释:

  • socket(): 客户端同样需要创建一个套接字。
  • inet_pton(): 这是将 IP 地址字符串(如 "127.0.0.1")转换为 struct in_addr 结构体的标准方法,比旧的 inet_aton 函数支持更广泛的地址格式(如 IPv6)。
  • sendto(): 客户端使用 sendto() 将数据发送到预先设置好的服务器地址 (server_addr)。
  • recvfrom(): 客户端调用 recvfrom() 来等待服务器的响应,我们不需要关心响应来自哪个地址(因为我们只连接了一个服务器),所以后两个参数可以传 NULL,在实际应用中,你可能需要处理来自不同服务器的响应,这时就需要填充一个 sockaddr 结构体。

如何编译和运行

  1. 保存代码:将服务器代码保存为 udp_server.c,客户端代码保存为 udp_client.c

  2. 编译:打开终端,使用 gcc 进行编译。

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

    • 首先启动服务器

      ./udp_server

      你会看到输出:UDP Server listening on port 8080...

    • 然后在一个新的终端窗口启动客户端

      ./udp_client

      客户端会提示你输入消息。

  4. 测试

    • 在客户端输入 Hello, Server! 并按回车。
    • 你会在服务器终端看到接收到的信息,并看到它回显的消息。
    • 在客户端终端,你会看到服务器回显的 Hello, Server!
    • 在客户端输入 exit 并按回车,客户端程序会退出。

总结与关键点

特性 TCP (流式套接字) UDP (数据报套接字)
连接 面向连接,需 connect() 无连接,直接 sendto()
可靠性 可靠,保证数据不丢失、不重复、有序 不可靠,可能丢包、重复、乱序
速度 较慢,有连接建立和拥塞控制开销 较快,无额外开销
边界 无消息边界,数据流式传输 有消息边界,recvfrom() 一次读取一个 sendto() 发送的数据包
函数 socket(), bind(), listen(), accept(), read(), write(), close() socket(), bind(), sendto(), recvfrom(), close()
适用场景 文件传输、网页浏览、邮件等需要高可靠性的场景 视频会议、在线游戏、DNS、VoIP等对实时性要求高、能容忍少量丢包的场景

对于 UDP 编程,最重要的就是理解并熟练使用 sendto()recvfrom() 这两个函数,它们是 UDP 通信的核心,不要忘记处理网络字节序(htons, htonl, ntohs, ntohl)和错误处理。

-- 展开阅读全文 --
头像
织梦建站密码忘了怎么找回?
« 上一篇 02-23
dede view.php存在什么安全隐患?
下一篇 » 02-23

相关文章

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

目录[+]