You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

I/O多路复用中基于FD_SET实现多客户端写入的方法求助

多客户端消息广播问题修复方案

问题分析

你的代码存在两个核心问题,导致无法向所有客户端广播消息且buf输出异常:

  • 客户端FD遍历逻辑错误:用sockfd+j遍历客户端FD完全不成立——客户端FD并非连续分配(比如关闭某个连接后,新FD可能远大于之前的,或中间存在已关闭的无效FD);同时变量k仅记录连接次数,未剔除已断开的连接,导致循环写入无效FD。
  • Buf处理存在越界风险:当result.buf为空时,strlen(result.buf)返回0,result.buf[strlen(result.buf)-1]会访问下标为-1的非法内存,引发输出异常。

修复后的代码示例

我们需要维护一个有效的客户端FD列表,替换原有的错误遍历逻辑,同时安全处理buf内容:

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

#define MAXLINE 1024
#define MAX_CLIENTS 100  // 设定最大支持的客户端数量

struct result
{
    char buf[MAXLINE];
    struct tm tm;
};

int main()
{
    int listen_fd, client_fd, maxfd, fd_num, addrlen;
    struct sockaddr_in server_addr, client_addr;
    fd_set readfds, allfds;
    struct result result;
    struct result *result_pointer;
    struct tm *lt;
    int i, sockfd;
    int client_fds[MAX_CLIENTS];  // 存储当前活跃的客户端FD
    int client_count = 0;         // 当前活跃客户端数量

    // 初始化监听socket(补全你省略的部分)
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(8080);  // 示例监听端口
    bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(listen_fd, 5);

    maxfd = listen_fd;
    memset(client_fds, -1, sizeof(client_fds));  // 用-1标记无效FD

    while(1)
    {
        allfds = readfds;
        printf("Select Wait %d\n", maxfd);
        fd_num = select(maxfd + 1 , &allfds, NULL, NULL, NULL);

        if (FD_ISSET(listen_fd, &allfds))
        {
            addrlen = sizeof(client_addr);
            client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addrlen);
            // 将新客户端FD加入有效列表
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (client_fds[i] == -1) {
                    client_fds[i] = client_fd;
                    client_count++;
                    break;
                }
            }
            FD_SET(client_fd, &readfds);
            if (client_fd > maxfd)
                maxfd = client_fd;
            printf("Accept OK\n");
            printf("maxfd : %d\n", maxfd);
            if (--fd_num <= 0)
                continue;
        }

        for (i = 0; i < MAX_CLIENTS; i++)
        {
            sockfd = client_fds[i];
            if (sockfd == -1 || !FD_ISSET(sockfd, &allfds))
                continue;

            printf("sockfd : %d\n", sockfd);
            memset(result.buf, 0x00, MAXLINE);
            ssize_t read_len = read(sockfd, &result, sizeof(result));
            if (read_len <= 0)
            {
                close(sockfd);
                FD_CLR(sockfd, &readfds);
                client_fds[i] = -1;
                client_count--;
                // 若断开的是当前最大FD,重新计算maxfd优化select范围
                if (sockfd == maxfd) {
                    maxfd = listen_fd;
                    for (int j = 0; j < MAX_CLIENTS; j++) {
                        if (client_fds[j] > maxfd)
                            maxfd = client_fds[j];
                    }
                }
            }
            else
            {
                if (strncmp(result.buf, "quit\n", 5) == 0)
                {
                    close(sockfd);
                    FD_CLR(sockfd, &readfds);
                    client_fds[i] = -1;
                    client_count--;
                    if (sockfd == maxfd) {
                        maxfd = listen_fd;
                        for (int j = 0; j < MAX_CLIENTS; j++) {
                            if (client_fds[j] > maxfd)
                                maxfd = client_fds[j];
                        }
                    }
                }
                else
                {
                    result_pointer = &result;
                    lt = &(result_pointer->tm);
                    // 安全处理buf末尾的换行符
                    if (strlen(result.buf) > 0 && result.buf[strlen(result.buf)-1] == '\n') {
                        result.buf[strlen(result.buf)-1] = '\0';
                    }
                    printf("Read : %s %s", result.buf, asctime(lt));

                    // 向所有活跃客户端广播消息
                    for (int j = 0; j < MAX_CLIENTS; j++) {
                        int target_fd = client_fds[j];
                        if (target_fd != -1 && target_fd != sockfd) {  // 可选:跳过消息发送者自身
                            write(target_fd, &result, sizeof(result));
                        }
                    }
                }
            }

            if (--fd_num <= 0)
                break;
        }
    }
    return 0;
}

关键修复点说明

  • 维护有效客户端FD列表:用client_fds数组存储活跃FD,无效FD标记为-1,确保遍历的都是可用连接。
  • 修正广播逻辑:遍历client_fds数组,对每个有效FD执行write,可选择跳过消息发送者避免重复接收。
  • 安全处理Buf:先判断buf长度大于0,再修改末尾换行符,彻底避免越界访问。
  • 动态更新Maxfd:当断开的FD是当前最大值时,重新计算maxfd,减少select的监听范围,提升效率。

额外注意事项

  • read直接读取sizeof(result)要求客户端发送的数据格式与结构体完全匹配,否则会出现数据错位或粘包问题,建议改用固定长度或分隔符的方式传输数据。
  • 实际生产环境中,需要检查write的返回值,处理网络繁忙导致的部分写入情况。

内容的提问来源于stack exchange,提问作者yeongbin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 05:15:39