recvmsg系统调用及示例

recvmsg 函数详解

1. 函数介绍

recvmsg 是Linux网络编程中功能最强大的接收数据函数之一。它是 recv 和 recvfrom 的增强版本,支持接收控制信息(如文件描述符、时间戳等)和分散缓冲区(scatter-gather I/O)。recvmsg 特别适用于需要接收复杂网络数据包的应用场景。

2. 函数原型

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

3. 功能

recvmsg 从套接字接收数据,并可以同时接收额外的控制信息。它支持分散缓冲区接收、接收发送者地址信息、接收辅助数据(如文件描述符传递)等功能。

4. 参数

  • int sockfd: 套接字文件描述符
  • *struct msghdr msg: 消息头结构,描述接收缓冲区和控制信息
  • int flags: 接收标志,控制接收行为

5. 返回值

  • 成功: 返回实际接收到的字节数
  • 连接关闭: 返回0
  • 失败: 返回-1,并设置errno

6. 相似函数,或关联函数

  • recv: 基本接收函数
  • recvfrom: 带地址信息的接收函数
  • sendmsg: 对应的发送函数
  • read: 基本读取函数

7. 示例代码

示例1:基础recvmsg使用

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

/**
 * 演示recvmsg的基本使用方法
 */
int demo_recvmsg_basic() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    struct msghdr msg;
    struct iovec iov[1];
    char buffer[1024];
    char control_buffer[1024];
    ssize_t bytes_received;
    
    printf("=== recvmsg 基本使用示例 ===\n");
    
    // 创建TCP服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("创建服务器套接字失败");
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8080);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return -1;
    }
    
    // 监听连接
    if (listen(server_fd, 1) == -1) {
        perror("监听失败");
        close(server_fd);
        return -1;
    }
    
    printf("服务器监听在端口 8080\n");
    
    // 在后台启动客户端(简化演示)
    if (fork() == 0) {
        // 客户端代码
        sleep(1);  // 等待服务器启动
        int client_sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in serv_addr;
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(8080);
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
            const char *message = "Hello from client!";
            send(client_sock, message, strlen(message), 0);
            printf("客户端发送消息: %s\n", message);
        }
        
        close(client_sock);
        exit(0);
    }
    
    // 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        close(server_fd);
        return -1;
    }
    
    printf("客户端连接来自: %s:%d\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    
    // 准备msghdr结构
    memset(&msg, 0, sizeof(msg));
    
    // 设置接收缓冲区
    iov[0].iov_base = buffer;
    iov[0].iov_len = sizeof(buffer) - 1;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    
    // 设置控制缓冲区(用于接收辅助数据)
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);
    
    // 设置地址信息缓冲区
    msg.msg_name = &client_addr;
    msg.msg_namelen = client_len;
    
    // 接收消息
    bytes_received = recvmsg(client_fd, &msg, 0);
    if (bytes_received == -1) {
        perror("recvmsg 失败");
        close(client_fd);
        close(server_fd);
        return -1;
    }
    
    buffer[bytes_received] = '\0';
    printf("recvmsg 接收到 %zd 字节数据: %s\n", bytes_received, buffer);
    printf("消息标志: %d\n", msg.msg_flags);
    
    // 显示地址信息
    if (msg.msg_namelen > 0) {
        struct sockaddr_in *addr = (struct sockaddr_in*)msg.msg_name;
        printf("发送者地址: %s:%d\n", 
               inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
    }
    
    close(client_fd);
    close(server_fd);
    
    return 0;
}

int main() {
    return demo_recvmsg_basic();
}

示例2:分散缓冲区接收

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

/**
 * 演示recvmsg的分散缓冲区接收功能
 */
int demo_recvmsg_scatter_gather() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr;
    struct msghdr msg;
    struct iovec iov[3];
    char buffer1[100], buffer2[100], buffer3[100];
    ssize_t bytes_received;
    
    printf("=== recvmsg 分散缓冲区接收示例 ===\n");
    
    // 创建UDP套接字
    server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_fd == -1) {
        perror("创建UDP套接字失败");
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8081);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return -1;
    }
    
    printf("UDP服务器监听在端口 8081\n");
    
    // 启动UDP客户端
    if (fork() == 0) {
        // 客户端代码
        sleep(1);
        int client_sock = socket(AF_INET, SOCK_DGRAM, 0);
        struct sockaddr_in serv_addr;
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(8081);
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        // 发送长消息
        const char *long_message = "This is a very long message that will be split across multiple buffers during scatter-gather reception.";
        sendto(client_sock, long_message, strlen(long_message), 0,
               (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        printf("客户端发送长消息 (%zu 字节)\n", strlen(long_message));
        
        close(client_sock);
        exit(0);
    }
    
    // 准备分散缓冲区
    memset(&msg, 0, sizeof(msg));
    
    // 设置三个分散的缓冲区
    iov[0].iov_base = buffer1;
    iov[0].iov_len = sizeof(buffer1) - 1;
    iov[1].iov_base = buffer2;
    iov[1].iov_len = sizeof(buffer2) - 1;
    iov[2].iov_base = buffer3;
    iov[2].iov_len = sizeof(buffer3) - 1;
    
    msg.msg_iov = iov;
    msg.msg_iovlen = 3;
    
    // 接收消息
    bytes_received = recvmsg(server_fd, &msg, 0);
    if (bytes_received == -1) {
        perror("recvmsg 失败");
        close(server_fd);
        return -1;
    }
    
    printf("recvmsg 接收到 %zd 字节数据\n", bytes_received);
    printf("消息被分散到 %d 个缓冲区\n", (int)msg.msg_iovlen);
    printf("实际使用的缓冲区数: %d\n", (int)msg.msg_iovlen);
    
    // 添加字符串终止符
    size_t total_len = 0;
    for (int i = 0; i < 3 && total_len < (size_t)bytes_received; i++) {
        size_t buf_len = iov[i].iov_len;
        if (total_len + buf_len > (size_t)bytes_received) {
            buf_len = bytes_received - total_len;
        }
        ((char*)iov[i].iov_base)[buf_len] = '\0';
        total_len += buf_len;
    }
    
    printf("缓冲区1内容 (%zu 字节): %s\n", strlen(buffer1), buffer1);
    printf("缓冲区2内容 (%zu 字节): %s\n", strlen(buffer2), buffer2);
    printf("缓冲区3内容 (%zu 字节): %s\n", strlen(buffer3), buffer3);
    
    // 合并显示完整消息
    char full_message[512];
    snprintf(full_message, sizeof(full_message), "%s%s%s", buffer1, buffer2, buffer3);
    printf("完整消息: %s\n", full_message);
    
    close(server_fd);
    
    return 0;
}

int main() {
    return demo_recvmsg_scatter_gather();
}

示例3:接收控制信息

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

/**
 * 演示recvmsg接收控制信息(时间戳)
 */
int demo_recvmsg_control_data() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    struct msghdr msg;
    struct iovec iov[1];
    char buffer[1024];
    char control_buffer[1024];
    ssize_t bytes_received;
    
    printf("=== recvmsg 控制信息接收示例 ===\n");
    
    // 创建TCP服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("创建服务器套接字失败");
        return -1;
    }
    
    // 启用时间戳选项
    int timestamp_on = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_TIMESTAMP, 
                   &timestamp_on, sizeof(timestamp_on)) == -1) {
        printf("警告: 无法启用时间戳选项: %s\n", strerror(errno));
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8082);
    
    // 绑定套接字
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server_fd);
        return -1;
    }
    
    // 监听连接
    if (listen(server_fd, 1) == -1) {
        perror("监听失败");
        close(server_fd);
        return -1;
    }
    
    printf("带时间戳的服务器监听在端口 8082\n");
    
    // 启动客户端
    if (fork() == 0) {
        // 客户端代码
        sleep(1);
        int client_sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in serv_addr;
        
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(8082);
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
            const char *message = "Message with timestamp";
            send(client_sock, message, strlen(message), 0);
            printf("客户端发送消息: %s\n", message);
        }
        
        close(client_sock);
        exit(0);
    }
    
    // 接受客户端连接
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        close(server_fd);
        return -1;
    }
    
    printf("客户端连接建立\n");
    
    // 准备msghdr结构用于接收控制信息
    memset(&msg, 0, sizeof(msg));
    
    // 设置接收缓冲区
    iov[0].iov_base = buffer;
    iov[0].iov_len = sizeof(buffer) - 1;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    
    // 设置控制缓冲区
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);
    
    // 接收消息和控制信息
    bytes_received = recvmsg(client_fd, &msg, 0);
    if (bytes_received == -1) {
        perror("recvmsg 失败");
        close(client_fd);
        close(server_fd);
        return -1;
    }
    
    buffer[bytes_received] = '\0';
    printf("接收到消息: %s\n", buffer);
    printf("接收时间戳信息:\n");
    
    // 解析控制信息
    struct cmsghdr *cmsg;
    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
            struct timeval *tv = (struct timeval*)CMSG_DATA(cmsg);
            printf("  时间戳: %ld.%06ld\n", tv->tv_sec, tv->tv_usec);
            
            // 转换为可读格式
            char time_str[64];
            time_t sec = tv->tv_sec;
            struct tm *tm_info = localtime(&sec);
            strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
            printf("  可读时间: %s.%06ld\n", time_str, tv->tv_usec);
        } else {
            printf("  其他控制信息: level=%d, type=%d\n", 
                   cmsg->cmsg_level, cmsg->cmsg_type);
        }
    }
    
    if (msg.msg_controllen == 0) {
        printf("  没有接收到控制信息\n");
    }
    
    close(client_fd);
    close(server_fd);
    
    return 0;
}

int main() {
    return demo_recvmsg_control_data();
}

示例4:文件描述符传递

#define _GNU_SOURCE
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

/**
 * 演示通过recvmsg传递文件描述符
 */
int demo_recvmsg_fd_passing() {
    int sv[2];  // socket pair
    struct msghdr msg;
    struct iovec iov[1];
    char buffer[256];
    char control_buffer[CMSG_SPACE(sizeof(int))];
    ssize_t bytes_received;
    
    printf("=== recvmsg 文件描述符传递示例 ===\n");
    
    // 创建socket pair用于进程间通信
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        perror("创建socket pair失败");
        return -1;
    }
    
    printf("创建了socket pair: %d, %d\n", sv[0], sv[1]);
    
    if (fork() == 0) {
        // 子进程:发送文件描述符
        close(sv[0]);  // 关闭接收端
        
        // 创建一个临时文件
        const char *temp_filename = "/tmp/fd_pass_test.txt";
        int temp_fd = open(temp_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (temp_fd == -1) {
            perror("创建临时文件失败");
            close(sv[1]);
            exit(1);
        }
        
        // 写入一些数据
        const char *test_data = "This data is in the passed file descriptor";
        write(temp_fd, test_data, strlen(test_data));
        
        printf("子进程创建了文件: %s\n", temp_filename);
        printf("子进程准备传递文件描述符 %d\n", temp_fd);
        
        // 准备发送消息和文件描述符
        struct msghdr send_msg;
        struct iovec send_iov[1];
        struct cmsghdr *cmsg;
        char send_buffer[] = "File descriptor passed";
        char send_control[CMSG_SPACE(sizeof(int))];
        
        // 设置消息内容
        send_iov[0].iov_base = send_buffer;
        send_iov[0].iov_len = strlen(send_buffer);
        
        memset(&send_msg, 0, sizeof(send_msg));
        send_msg.msg_iov = send_iov;
        send_msg.msg_iovlen = 1;
        send_msg.msg_control = send_control;
        send_msg.msg_controllen = sizeof(send_control);
        
        // 设置控制信息(文件描述符)
        cmsg = CMSG_FIRSTHDR(&send_msg);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
        memcpy(CMSG_DATA(cmsg), &temp_fd, sizeof(int));
        
        send_msg.msg_controllen = cmsg->cmsg_len;
        
        // 发送消息和文件描述符
        if (sendmsg(sv[1], &send_msg, 0) == -1) {
            perror("sendmsg 失败");
            close(temp_fd);
            close(sv[1]);
            exit(1);
        }
        
        printf("子进程发送了消息和文件描述符\n");
        
        // 关闭原始文件描述符
        close(temp_fd);
        unlink(temp_filename);
        close(sv[1]);
        
        exit(0);
    } else {
        // 父进程:接收文件描述符
        close(sv[1]);  // 关闭发送端
        
        // 准备接收消息和文件描述符
        memset(&msg, 0, sizeof(msg));
        
        iov[0].iov_base = buffer;
        iov[0].iov_len = sizeof(buffer) - 1;
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;
        msg.msg_control = control_buffer;
        msg.msg_controllen = sizeof(control_buffer);
        
        // 接收消息和文件描述符
        bytes_received = recvmsg(sv[0], &msg, 0);
        if (bytes_received == -1) {
            perror("recvmsg 失败");
            close(sv[0]);
            return -1;
        }
        
        buffer[bytes_received] = '\0';
        printf("父进程接收到消息: %s\n", buffer);
        
        // 解析接收到的文件描述符
        int received_fd = -1;
        struct cmsghdr *cmsg;
        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
            if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
                memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
                printf("父进程接收到文件描述符: %d\n", received_fd);
                break;
            }
        }
        
        if (received_fd != -1) {
            // 使用接收到的文件描述符读取数据
            char read_buffer[256];
            lseek(received_fd, 0, SEEK_SET);  // 重置文件位置
            ssize_t read_bytes = read(received_fd, read_buffer, sizeof(read_buffer) - 1);
            if (read_bytes > 0) {
                read_buffer[read_bytes] = '\0';
                printf("从传递的文件描述符读取数据: %s\n", read_buffer);
            }
            
            // 关闭接收到的文件描述符
            close(received_fd);
        } else {
            printf("没有接收到文件描述符\n");
        }
        
        close(sv[0]);
        
        // 等待子进程结束
        int status;
        wait(&status);
    }
    
    return 0;
}

int main() {
    return demo_recvmsg_fd_passing();
}

示例5:完整的网络服务器示例

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <time.h>

/**
 * 网络服务器结构
 */
typedef struct {
    int server_fd;
    int port;
    struct pollfd *clients;
    int max_clients;
    int client_count;
} network_server_t;

/**
 * 初始化网络服务器
 */
int server_init(network_server_t *server, int port, int max_clients) {
    struct sockaddr_in server_addr;
    
    memset(server, 0, sizeof(network_server_t));
    server->port = port;
    server->max_clients = max_clients;
    server->client_count = 0;
    
    // 分配客户端数组
    server->clients = calloc(max_clients + 1, sizeof(struct pollfd));
    if (!server->clients) {
        perror("分配客户端数组失败");
        return -1;
    }
    
    // 创建服务器套接字
    server->server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server->server_fd == -1) {
        perror("创建服务器套接字失败");
        free(server->clients);
        return -1;
    }
    
    // 设置套接字选项
    int opt = 1;
    if (setsockopt(server->server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        perror("设置套接字选项失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 设置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);
    
    // 绑定套接字
    if (bind(server->server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("绑定套接字失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 监听连接
    if (listen(server->server_fd, 10) == -1) {
        perror("监听失败");
        close(server->server_fd);
        free(server->clients);
        return -1;
    }
    
    // 设置服务器套接字为poll监听
    server->clients[0].fd = server->server_fd;
    server->clients[0].events = POLLIN;
    
    printf("网络服务器初始化完成,监听端口 %d\n", port);
    return 0;
}

/**
 * 接受新客户端连接
 */
int server_accept_client(network_server_t *server) {
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_fd;
    
    client_fd = accept(server->server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (client_fd == -1) {
        perror("接受连接失败");
        return -1;
    }
    
    if (server->client_count >= server->max_clients) {
        printf("客户端数量已达上限,拒绝连接\n");
        close(client_fd);
        return -1;
    }
    
    // 添加到客户端数组
    int index = server->client_count + 1;
    server->clients[index].fd = client_fd;
    server->clients[index].events = POLLIN;
    server->client_count++;
    
    printf("新客户端连接: %s:%d (fd=%d)\n", 
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);
    
    return 0;
}

/**
 * 使用recvmsg处理客户端消息
 */
int server_handle_client_message(network_server_t *server, int client_index) {
    int client_fd = server->clients[client_index].fd;
    struct msghdr msg;
    struct iovec iov[2];
    char buffer1[512], buffer2[512];
    char control_buffer[1024];
    ssize_t bytes_received;
    
    // 准备msghdr结构
    memset(&msg, 0, sizeof(msg));
    
    // 设置分散缓冲区
    iov[0].iov_base = buffer1;
    iov[0].iov_len = sizeof(buffer1) - 1;
    iov[1].iov_base = buffer2;
    iov[1].iov_len = sizeof(buffer2) - 1;
    msg.msg_iov = iov;
    msg.msg_iovlen = 2;
    
    // 设置控制缓冲区
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);
    
    // 接收消息
    bytes_received = recvmsg(client_fd, &msg, 0);
    if (bytes_received == -1) {
        if (errno == ECONNRESET) {
            printf("客户端 %d 连接重置\n", client_fd);
        } else {
            perror("recvmsg 失败");
        }
        return -1;
    }
    
    if (bytes_received == 0) {
        printf("客户端 %d 关闭连接\n", client_fd);
        return -1;
    }
    
    printf("从客户端 %d 接收到 %zd 字节数据\n", client_fd, bytes_received);
    
    // 处理接收到的数据
    size_t total_copied = 0;
    char full_message[1024];
    full_message[0] = '\0';
    
    for (int i = 0; i < 2 && total_copied < (size_t)bytes_received; i++) {
        size_t to_copy = iov[i].iov_len;
        if (total_copied + to_copy > (size_t)bytes_received) {
            to_copy = bytes_received - total_copied;
        }
        
        strncat(full_message, (char*)iov[i].iov_base, to_copy);
        total_copied += to_copy;
    }
    
    printf("  消息内容: %s\n", full_message);
    printf("  使用缓冲区数: %d\n", (int)msg.msg_iovlen);
    printf("  消息标志: %d\n", msg.msg_flags);
    
    // 回显消息
    char response[1024];
    snprintf(response, sizeof(response), "Echo: %s", full_message);
    send(client_fd, response, strlen(response), 0);
    
    return 0;
}

/**
 * 运行服务器主循环
 */
int server_run(network_server_t *server) {
    printf("服务器开始运行,等待客户端连接...\n");
    
    while (1) {
        // 使用poll等待事件
        int nfds = server->client_count + 1;
        int activity = poll(server->clients, nfds, 1000);  // 1秒超时
        
        if (activity == -1) {
            if (errno == EINTR) continue;  // 被信号中断
            perror("poll 失败");
            break;
        }
        
        if (activity == 0) {
            // 超时,继续循环
            continue;
        }
        
        // 检查服务器套接字(新连接)
        if (server->clients[0].revents & POLLIN) {
            server_accept_client(server);
            activity--;
        }
        
        // 检查客户端套接字
        for (int i = 1; i <= server->client_count && activity > 0; i++) {
            if (server->clients[i].revents & POLLIN) {
                if (server_handle_client_message(server, i) == -1) {
                    // 客户端断开连接,移除客户端
                    close(server->clients[i].fd);
                    // 将最后一个客户端移到当前位置
                    if (i < server->client_count) {
                        server->clients[i] = server->clients[server->client_count];
                    }
                    server->client_count--;
                    i--;  // 重新检查当前位置
                }
                activity--;
            }
        }
    }
    
    return 0;
}

/**
 * 清理服务器资源
 */
void server_cleanup(network_server_t *server) {
    // 关闭所有客户端连接
    for (int i = 1; i <= server->client_count; i++) {
        close(server->clients[i].fd);
    }
    
    // 关闭服务器套接字
    if (server->server_fd != -1) {
        close(server->server_fd);
    }
    
    // 释放内存
    if (server->clients) {
        free(server->clients);
    }
    
    printf("服务器资源清理完成\n");
}

/**
 * 演示完整的网络服务器
 */
int demo_complete_network_server() {
    network_server_t server;
    
    printf("=== 完整网络服务器示例 ===\n");
    
    // 初始化服务器
    if (server_init(&server, 8083, 10) != 0) {
        return -1;
    }
    
    // 启动测试客户端
    if (fork() == 0) {
        sleep(2);  // 等待服务器启动
        
        // 创建多个客户端进行测试
        for (int i = 0; i < 3; i++) {
            if (fork() == 0) {
                int client_sock = socket(AF_INET, SOCK_STREAM, 0);
                struct sockaddr_in serv_addr;
                
                memset(&serv_addr, 0, sizeof(serv_addr));
                serv_addr.sin_family = AF_INET;
                serv_addr.sin_port = htons(8083);
                serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
                
                if (connect(client_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == 0) {
                    char message[256];
                    snprintf(message, sizeof(message), "Hello from client %d", i + 1);
                    
                    send(client_sock, message, strlen(message), 0);
                    printf("客户端 %d 发送: %s\n", i + 1, message);
                    
                    // 接收回显
                    char response[1024];
                    ssize_t bytes = recv(client_sock, response, sizeof(response) - 1, 0);
                    if (bytes > 0) {
                        response[bytes] = '\0';
                        printf("客户端 %d 接收回显: %s\n", i + 1, response);
                    }
                    
                    sleep(1);
                }
                
                close(client_sock);
                exit(0);
            }
        }
        
        // 等待所有客户端完成
        for (int i = 0; i < 3; i++) {
            int status;
            wait(&status);
        }
        
        exit(0);
    }
    
    // 运行服务器30秒
    printf("服务器将运行30秒...\n");
    sleep(30);
    
    // 清理资源
    server_cleanup(&server);
    
    // 等待测试客户端结束
    int status;
    wait(&status);
    
    return 0;
}

int main() {
    return demo_complete_network_server();
}

recvmsg 标志参数详解

常用标志:

  • MSG_OOB: 接收带外数据
  • MSG_PEEK: 查看数据但不从队列中移除
  • MSG_WAITALL: 等待接收完整的消息
  • MSG_TRUNC: 返回数据包的实际长度(UDP)
  • MSG_CTRUNC: 控制数据被截断

高级标志:

  • MSG_DONTWAIT: 非阻塞操作
  • MSG_ERRQUEUE: 接收错误队列中的数据
  • MSG_NOSIGNAL: 接收时不产生SIGPIPE信号

使用注意事项

性能考虑:

  1. 缓冲区管理: 合理设置缓冲区大小避免频繁分配
  2. 分散缓冲区: 适当使用scatter-gather I/O提高效率
  3. 控制信息: 只在需要时启用控制信息接收

错误处理:

  1. 部分接收: 处理数据被截断的情况
  2. 连接状态: 检查连接是否正常关闭
  3. 资源清理: 及时关闭文件描述符和释放内存

安全考虑:

  1. 缓冲区溢出: 确保缓冲区大小足够且正确处理
  2. 权限检查: 验证传递的文件描述符权限
  3. 输入验证: 验证接收到的数据内容

总结

recvmsg 是Linux网络编程中最强大的接收函数,提供了:

  • 基本数据接收功能
  • 分散缓冲区接收(scatter-gather I/O)
  • 控制信息接收(时间戳、文件描述符等)
  • 地址信息接收
  • 灵活的标志控制

通过合理使用 recvmsg,可以构建高性能、功能丰富的网络应用程序。

recvmsg系统调用及示例-CSDN博客

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注