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

如何用C语言Socket实现多客户端接入及消息互看的CLI通信程序

多客户端CLI即时通信改造方案

我正在用C语言开发一个简单的CLI即时通信程序,已经基于Socket实现了单客户端向服务器发消息、服务器显示消息的功能,但这个程序只能接入一个客户端。我想改造它,让它支持多客户端接入,并且客户端能看到其他客户端发送的消息。以下是我现有的server.c和client.c代码:


现有server.c代码

/*
server.c
*/

#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAX 256
#define PORT 8080

int main() {
    int socket_return, accept_return, bind_return, listen_return;
    struct sockaddr_in server_address, peer_address;

    // 创建socket
    socket_return = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_return == -1) {
        perror("socket creation failed");
        return -1;
    } else {
        printf("socket created\n");
    }
    // 清空服务器地址
    bzero(&server_address, sizeof(server_address));

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(PORT);

    // 绑定socket
    bind_return = bind(socket_return, (struct sockaddr *)&server_address, sizeof(server_address));
    if (bind_return == -1) {
        perror("binding failed");
        close(socket_return);
        return -1;
    } else {
        printf("binding done\n");
    }

    // 监听socket
    listen_return = listen(socket_return, 5);
    if (listen_return == -1) {
        perror("listening failed");
        close(socket_return);
        return -1;
    } else {
        printf("server is listening...\n");
    }

    socklen_t peer_address_len = sizeof(peer_address);
    // 接受连接
    accept_return = accept(socket_return, (struct sockaddr *)&peer_address, &peer_address_len);
    if (accept_return == -1) {
        perror("accepting failed");
        close(socket_return);
        return -1;
    } else {
        printf("connection accepted\n");
    }
    char msg[MAX];
    while (1) {
        bzero(msg, sizeof(msg));

        // 读取消息
        ssize_t bytes_read = read(accept_return, msg, sizeof(msg));
        if (bytes_read == 0) {
            printf("client disconnected\n");
            break;
        } else if (bytes_read < 0) {
            perror("failed to read message");
            break;
        }
        // 显示客户端消息
        printf("From client: %s", msg);

    }

    // 关闭连接
    close(accept_return);
    close(socket_return);

    return 0;
}

现有client.c代码

/*
client.c
*/

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

#define MAX 256
#define PORT 8080

int main() {
    int socket_return, connect_return;
    struct sockaddr_in server_address;

    // 创建socket
    socket_return = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_return == -1) {
        printf("socket creation failed\n");
        return -1;
    } else {
        printf("socket created\n");
    }

    bzero(&server_address, sizeof(server_address));

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = htons(PORT);

    // 连接服务器
    connect_return = connect(socket_return, (struct sockaddr *)&server_address, sizeof(server_address));
    if (connect_return == -1) {
        printf("connection failed\n");
        close(socket_return);
        return -1;
    } else {
        printf("connected to server\n");
    }

    char msg[MAX];
    int i;

    while (1) {
        bzero(msg, sizeof(msg));
        printf("Enter string: ");
        i = 0;
        while ((msg[i++] = getchar()) != '\n');
        // 当输入exit时退出客户端
        if ((strncmp(msg, "exit", 4)) == 0) {
            printf("Exiting client\n");
            close(socket_return);
            break;
        }
        // 发送消息
        write(socket_return, msg, sizeof(msg));
        printf("You wrote %s\n",msg);
    }

    // 关闭连接
    close(socket_return);

    return 0;
}

改造方案实现

核心思路

要支持多客户端,服务器需要同时处理多个连接,这里采用多线程方案(实现简单、适合小规模场景),同时维护客户端连接列表实现消息转发;客户端需要新增接收线程,同时处理输入和消息接收。

改造后server.c代码

#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>

#define MAX 256
#define PORT 8080
#define MAX_CLIENTS 100

// 存储客户端socket的全局列表
int client_sockets[MAX_CLIENTS];
// 保证列表线程安全的互斥锁
pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;

// 将客户端socket加入列表
void add_client(int sock) {
    pthread_mutex_lock(&client_mutex);
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (client_sockets[i] == 0) {
            client_sockets[i] = sock;
            break;
        }
    }
    pthread_mutex_unlock(&client_mutex);
}

// 从列表移除客户端socket
void remove_client(int sock) {
    pthread_mutex_lock(&client_mutex);
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (client_sockets[i] == sock) {
            client_sockets[i] = 0;
            break;
        }
    }
    pthread_mutex_unlock(&client_mutex);
}

// 转发消息给所有其他客户端
void broadcast_message(int sender_sock, char *msg, ssize_t len) {
    pthread_mutex_lock(&client_mutex);
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (client_sockets[i] != 0 && client_sockets[i] != sender_sock) {
            write(client_sockets[i], msg, len);
        }
    }
    pthread_mutex_unlock(&client_mutex);
}

// 线程函数:处理单个客户端的消息读写
void *handle_client(void *arg) {
    int client_sock = *(int *)arg;
    free(arg);

    char msg[MAX];
    ssize_t bytes_read;

    while (1) {
        bzero(msg, sizeof(msg));
        bytes_read = read(client_sock, msg, sizeof(msg)-1); // 预留终止符空间

        if (bytes_read <= 0) {
            printf("客户端断开连接\n");
            remove_client(client_sock);
            close(client_sock);
            pthread_exit(NULL);
        }

        // 服务器打印收到的消息
        printf("来自客户端: %s", msg);
        // 转发消息给其他客户端
        broadcast_message(client_sock, msg, bytes_read);
    }
}

int main() {
    int server_sock, bind_return, listen_return;
    struct sockaddr_in server_address, peer_address;

    // 初始化客户端列表为0(0表示未使用)
    bzero(client_sockets, sizeof(client_sockets));

    // 创建服务器socket
    server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock == -1) {
        perror("socket创建失败");
        return -1;
    }
    printf("socket创建成功\n");

    bzero(&server_address, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(PORT);

    // 绑定socket
    bind_return = bind(server_sock, (struct sockaddr *)&server_address, sizeof(server_address));
    if (bind_return == -1) {
        perror("绑定失败");
        close(server_sock);
        return -1;
    }
    printf("绑定完成\n");

    // 监听socket
    listen_return = listen(server_sock, 5);
    if (listen_return == -1) {
        perror("监听失败");
        close(server_sock);
        return -1;
    }
    printf("服务器正在监听...\n");

    // 循环接受新连接
    while (1) {
        socklen_t peer_address_len = sizeof(peer_address);
        int *client_sock = malloc(sizeof(int));
        *client_sock = accept(server_sock, (struct sockaddr *)&peer_address, &peer_address_len);

        if (*client_sock == -1) {
            perror("接受连接失败");
            free(client_sock);
            continue;
        }
        printf("接受新连接,客户端IP: %s\n", inet_ntoa(peer_address.sin_addr));

        add_client(*client_sock);

        // 创建线程处理该客户端
        pthread_t thread_id;
        if (pthread_create(&thread_id, NULL, handle_client, client_sock) != 0) {
            perror("创建线程失败");
            close(*client_sock);
            remove_client(*client_sock);
            free(client_sock);
        }
        pthread_detach(thread_id); // 分离线程,避免资源泄漏
    }

    close(server_sock);
    return 0;
}

改造后client.c代码

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

#define MAX 256
#define PORT 8080

int client_sock;

// 线程函数:接收服务器转发的消息
void *receive_messages(void *arg) {
    char msg[MAX];
    ssize_t bytes_read;

    while (1) {
        bzero(msg, sizeof(msg));
        bytes_read = read(client_sock, msg, sizeof(msg)-1);

        if (bytes_read <= 0) {
            printf("\n服务器断开连接,即将退出\n");
            close(client_sock);
            exit(EXIT_SUCCESS);
        }

        // 打印收到的消息,同时刷新输出缓冲区保证输入提示正常显示
        printf("\n收到消息: %s输入内容: ", msg);
        fflush(stdout);
    }
}

int main() {
    int connect_return;
    struct sockaddr_in server_address;

    // 创建客户端socket
    client_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (client_sock == -1) {
        printf("socket创建失败\n");
        return -1;
    }
    printf("socket创建成功\n");

    bzero(&server_address, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = htons(PORT);

    // 连接服务器
    connect_return = connect(client_sock, (struct sockaddr *)&server_address, sizeof(server_address));
    if (connect_return == -1) {
        printf("连接服务器失败\n");
        close(client_sock);
        return -1;
    }
    printf("已连接到服务器\n");
    printf("输入内容: ");
    fflush(stdout);

    // 创建接收消息的线程
    pthread_t thread_id;
    if (pthread_create(&thread_id, NULL, receive_messages, NULL) != 0) {
        perror("创建接收线程失败");
        close(client_sock);
        return -1;
    }
    pthread_detach(thread_id);

    char msg[MAX];
    int i;

    while (1) {
        bzero(msg, sizeof(msg));
        i = 0;
        while ((msg[i++] = getchar()) != '\n');

        if (strncmp(msg, "exit", 4) == 0) {
            printf("正在退出客户端\n");
            close(client_sock);
            exit(EXIT_SUCCESS);
        }

        write(client_sock, msg, strlen(msg));
        printf("你发送了: %s输入内容: ", msg);
        fflush(stdout);
    }

    close(client_sock);
    return 0;
}

编译与运行说明

  • 编译服务器:gcc server.c -o server -lpthread
  • 编译客户端:gcc client.c -o client -lpthread
  • 启动服务器:./server
  • 启动多个客户端:在不同终端窗口运行./client

关键注意事项

  1. 线程安全:客户端列表的增删操作必须加互斥锁,避免多线程冲突
  2. 资源清理:客户端断开时要及时从列表移除并关闭socket,线程需分离避免内存泄漏
  3. IO处理:客户端接收消息后要刷新输出缓冲区,保证输入提示正常显示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:22:04