4. 多线程并发服务器

任务说明

本案例实现一个基于 pthread 的多线程并发聊天室服务器。服务器监听固定端口,每接受一个客户端连接就创建一个独立线程处理该客户端的收发消息,并将收到的消息广播给所有在线客户端,从而实现多客户端并发聊天。

实现方案

服务器使用 socket + bind + listen 建立监听套接字,主循环中调用 accept 接受连接。每接受一个连接,通过 pthread_create 创建线程,线程入口函数负责与单个客户端进行收发。为支持广播,使用全局客户端列表保存所有在线客户端的 socket 描述符,并用互斥锁保护列表的并发访问。当某客户端发送消息时,线程将该消息转发给除发送者外的所有客户端。客户端退出时,线程从列表中移除自身并关闭连接。

代码实现


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

#define PORT 8888
#define MAX_CLIENTS 64
#define BUFFER_SIZE 1024

int client_fds[MAX_CLIENTS];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void broadcast_message(int sender_fd, const char *msg, int len) {
    pthread_mutex_lock(&lock);
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (client_fds[i] != -1 && client_fds[i] != sender_fd) {
            send(client_fds[i], msg, len, 0);
        }
    }
    pthread_mutex_unlock(&lock);
}

void remove_client(int fd) {
    pthread_mutex_lock(&lock);
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (client_fds[i] == fd) {
            client_fds[i] = -1;
            break;
        }
    }
    pthread_mutex_unlock(&lock);
    close(fd);
}

void *client_handler(void *arg) {
    int client_fd = *(int *)arg;
    free(arg);
    char buffer[BUFFER_SIZE];
    int n;

    while ((n = recv(client_fd, buffer, sizeof(buffer) - 1, 0)) > 0) {
        buffer[n] = '\0';
        broadcast_message(client_fd, buffer, n);
    }

    remove_client(client_fd);
    return NULL;
}

int main() {
    int server_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);

    for (int i = 0; i < MAX_CLIENTS; i++) {
        client_fds[i] = -1;
    }

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        exit(1);
    }

    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    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_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        exit(1);
    }

    if (listen(server_fd, 10) < 0) {
        perror("listen");
        exit(1);
    }

    printf("Chat server listening on port %d\n", PORT);

    while (1) {
        int *client_fd = malloc(sizeof(int));
        *client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
        if (*client_fd < 0) {
            perror("accept");
            free(client_fd);
            continue;
        }

        pthread_mutex_lock(&lock);
        int slot = -1;
        for (int i = 0; i < MAX_CLIENTS; i++) {
            if (client_fds[i] == -1) {
                client_fds[i] = *client_fd;
                slot = i;
                break;
            }
        }
        pthread_mutex_unlock(&lock);

        if (slot == -1) {
            printf("Max clients reached, rejecting\n");
            close(*client_fd);
            free(client_fd);
            continue;
        }

        pthread_t tid;
        pthread_create(&tid, NULL, client_handler, client_fd);
        pthread_detach(tid);
    }

    close(server_fd);
    return 0;
}

编译与运行


gcc -o chat_server chat_server.c -lpthread
./chat_server

验证方法

在三个终端中分别执行以下命令连接服务器:


nc localhost 8888

在任意一个终端输入一行文本并回车,另外两个终端应能立即收到该文本。当某个终端按 Ctrl+C 断开连接后,其余终端仍可正常收发消息,服务器不崩溃。

判定标准

  • 服务器启动后输出监听端口信息,无报错退出。
  • 多个客户端可同时连接,且任一客户端发送的消息能被其他所有客户端收到。
  • 断开任意客户端不影响其他客户端通信,服务器持续运行。
多线程并发聊天室架构 服务器 listen :8888 主线程 accept pthread_mutex client_fds[] fd1, fd2, fd3 ... 全局共享 加锁/解锁 客户端 A nc localhost 8888 客户端 B nc localhost 8888 线程 1 recv/send 线程 2 recv/send 广播消息 图例 工作线程 共享数据/锁

关键说明

本实现中每个客户端连接对应一个独立线程,线程间通过全局数组共享客户端描述符。互斥锁保证了对共享数组的原子操作,避免数据竞争。广播时遍历数组向所有其他客户端发送数据。代码中未实现客户端用户名等高级功能,仅聚焦于多线程并发收发的核心机制。