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

基于C语言Socket编程的多线程多客户端服务器:指定客户端收发问题

C多线程Socket服务器问题解答

1. newSocket与客户端的对应关系

每次调用accept()成功后,返回的newSocket是当前连接客户端的专属通信句柄——服务器的serverSocket只负责监听端口、接收连接请求,真正和客户端收发数据靠的是这个newSocket。每个客户端连接都会生成一个唯一的newSocket,只要连接没断开,这个句柄就和该客户端绑定。

2. 与客户端收发数据的实现

用系统调用recv()和send()即可,参数传入对应客户端的newSocket:

  • 接收数据:recv(newSocket, 缓冲区地址, 缓冲区大小, 0),返回值是实际接收的字节数,返回-1表示出错,0表示客户端断开连接。
  • 发送数据:send(newSocket, 数据地址, 数据长度, 0),返回值是实际发送的字节数,返回-1表示出错。

示例线程内处理代码:

void* client_handler(void* arg) {
    int client_socket = *(int*)arg;
    free(arg); // 释放动态分配的内存

    char buf[1024];
    ssize_t recv_len;
    while ((recv_len = recv(client_socket, buf, sizeof(buf)-1, 0)) > 0) {
        buf[recv_len] = '\0';
        printf("收到数据:%s\n", buf);
        
        // 回复客户端
        const char* reply = "已收到你的消息";
        send(client_socket, reply, strlen(reply), 0);
    }

    close(client_socket); // 关闭连接
    return NULL;
}

3. 识别接收数据的客户端来源

accept()调用时,第二个参数serverStorage(一般是struct sockaddr_in类型)会被填充客户端的IP地址和端口信息,通过以下方式解析:

struct sockaddr_in client_addr;
socklen_t addr_size = sizeof(client_addr);
int newSocket = accept(serverSocket, (struct sockaddr*)&client_addr, &addr_size);

// 解析客户端IP和端口
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
uint16_t client_port = ntohs(client_addr.sin_port);
printf("新客户端连接:IP=%s,端口=%d,Socket=%d\n", client_ip, client_port, newSocket);

如果需要后续随时识别客户端,可以把newSocket和客户端IP、端口封装成结构体,存入全局的客户端列表(比如链表、数组)。

4. 修改现有代码实现指定客户端收发

现有代码的核心问题

你当前的代码存在致命bug:传递&newSocket给线程是错误的——newSocket是循环内的局部变量,线程启动后,循环会继续执行,下一次accept()会覆盖newSocket的值,导致所有线程拿到的是同一个newSocket的地址,最终操作的是错误的客户端句柄。必须传递newSocket的副本(比如动态分配内存保存值)。

修改后的完整示例代码

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

#define MAX_CLIENTS 4 // 支持最多4个客户端

// 客户端信息结构体
typedef struct {
    int socket;
    char ip[INET_ADDRSTRLEN];
    uint16_t port;
} ClientInfo;

ClientInfo client_list[MAX_CLIENTS];
int client_count = 0;
pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; // 保护客户端列表的互斥锁

// 向指定IP的客户端发送消息
int send_to_client_by_ip(const char* target_ip, const char* msg) {
    pthread_mutex_lock(&client_mutex);
    for (int i = 0; i < client_count; i++) {
        if (strcmp(client_list[i].ip, target_ip) == 0) {
            send(client_list[i].socket, msg, strlen(msg), 0);
            pthread_mutex_unlock(&client_mutex);
            return 0;
        }
    }
    pthread_mutex_unlock(&client_mutex);
    return -1; // 未找到目标客户端
}

void* client_handler(void* arg) {
    ClientInfo* client = (ClientInfo*)arg;
    printf("客户端[%s:%d]已连接,Socket=%d\n", client->ip, client->port, client->socket);

    char buf[1024];
    ssize_t recv_len;
    while ((recv_len = recv(client->socket, buf, sizeof(buf)-1, 0)) > 0) {
        buf[recv_len] = '\0';
        printf("收到[%s:%d]的消息:%s\n", client->ip, client->port, buf);

        // 示例:如果消息是"给192.168.1.100发消息",调用指定发送函数
        if (strstr(buf, "给") != NULL && strstr(buf, "发消息") != NULL) {
            // 这里简化处理,假设提取IP为192.168.1.100,消息为"测试"
            send_to_client_by_ip("192.168.1.100", "来自其他客户端的消息:测试");
        } else {
            // 回复当前客户端
            const char* reply = "已收到你的消息";
            send(client->socket, reply, strlen(reply), 0);
        }
    }

    // 客户端断开连接,从列表移除
    pthread_mutex_lock(&client_mutex);
    for (int i = 0; i < client_count; i++) {
        if (client_list[i].socket == client->socket) {
            // 移动后面的元素覆盖当前位置
            for (int j = i; j < client_count-1; j++) {
                client_list[j] = client_list[j+1];
            }
            client_count--;
            break;
        }
    }
    pthread_mutex_unlock(&client_mutex);

    printf("客户端[%s:%d]已断开连接\n", client->ip, client->port);
    close(client->socket);
    free(client);
    return NULL;
}

int main() {
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) {
        perror("socket创建失败");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080); // 监听8080端口
    serverAddr.sin_addr.s_addr = INADDR_ANY;

    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        perror("bind失败");
        close(serverSocket);
        exit(EXIT_FAILURE);
    }

    if (listen(serverSocket, MAX_CLIENTS) == -1) {
        perror("listen失败");
        close(serverSocket);
        exit(EXIT_FAILURE);
    }
    printf("服务器正在监听8080端口...\n");

    pthread_t threads[MAX_CLIENTS];
    int thread_idx = 0;

    while (1) {
        struct sockaddr_in clientAddr;
        socklen_t addr_size = sizeof(clientAddr);
        int newSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &addr_size);
        if (newSocket == -1) {
            perror("accept失败");
            continue;
        }

        // 检查客户端数量是否超过上限
        pthread_mutex_lock(&client_mutex);
        if (client_count >= MAX_CLIENTS) {
            printf("客户端数量已达上限,拒绝新连接\n");
            close(newSocket);
            pthread_mutex_unlock(&client_mutex);
            continue;
        }
        pthread_mutex_unlock(&client_mutex);

        // 分配客户端信息结构体
        ClientInfo* client = malloc(sizeof(ClientInfo));
        client->socket = newSocket;
        inet_ntop(AF_INET, &clientAddr.sin_addr, client->ip, INET_ADDRSTRLEN);
        client->port = ntohs(clientAddr.sin_port);

        // 添加到客户端列表
        pthread_mutex_lock(&client_mutex);
        client_list[client_count++] = *client;
        pthread_mutex_unlock(&client_mutex);

        // 创建线程处理客户端
        if (pthread_create(&threads[thread_idx++], NULL, client_handler, client) != 0) {
            perror("线程创建失败");
            close(newSocket);
            free(client);
            pthread_mutex_lock(&client_mutex);
            client_count--;
            pthread_mutex_unlock(&client_mutex);
        }

        if (thread_idx >= MAX_CLIENTS) {
            thread_idx = 0;
            // 可选:回收已结束的线程(如果需要)
            // for (int i = 0; i < MAX_CLIENTS; i++) {
            //     pthread_join(threads[i], NULL);
            // }
        }
    }

    close(serverSocket);
    pthread_mutex_destroy(&client_mutex);
    return 0;
}

关键修改点说明

  • 新增ClientInfo结构体保存客户端的socket、IP和端口,并用全局列表维护,配合互斥锁保证线程安全。
  • 线程参数传递的是动态分配的ClientInfo指针,避免了原代码中newSocket被覆盖的问题。
  • 实现了send_to_client_by_ip函数,可根据IP找到对应客户端的socket并发送数据,实现指定客户端收发。
  • 限制了最大客户端连接数为4,达到上限时拒绝新连接。
  • 客户端断开时从列表中移除对应的信息,避免无效的socket操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:00:58