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

C多线程服务器客户端通信异常问题排查求助

问题描述

我正在参照一个GitHub项目实现C多线程服务器客户端程序,期望实现无限循环收发消息,直到客户端发送SIGINT、SIGSTOP信号或消息“stop”时终止。目前仅客户端发送的第一条消息能被服务器接收,服务器会回复“abcd”;从第二条消息开始,客户端无法收到服务器回复,第三条消息发送后客户端自动退出,而服务器仍在运行。我怀疑读写操作存在问题,以下是我的代码及终端输出:

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/syscall.h> //thread_id

#define PORT 7507
#define BUFFER_SIZE 2048
#define BACKLOG 10
#define gettid() ((pid_t)syscall(SYS_gettid))

typedef struct pthread_args {
    int sockfd;
    struct sockaddr_in client_address;
    
    clock_t start, stop; 
    
    char* message;
    float seconds;
    float delay; 
} pthread_args;

void* handler(void* args) {
    pthread_args* pthread_arg = (pthread_args*)args;
    int sockfd = pthread_arg->sockfd;
    struct sockaddr_in client_address = pthread_arg->client_address;
    float seconds = pthread_arg->seconds;
    float delay = pthread_arg->delay;
    char* message = pthread_arg->message;   
    
    free(args);
    
    printf("Message received from client (thread id=%d): %s\n", gettid(), pthread_arg->message);
    
    if((write(sockfd, "abcd", 4))<0) {
        perror("write");
        exit(EXIT_FAILURE);
    }
    
    close(sockfd);
    return NULL;    
}

int main() {
    int sockfd, new_sockfd, bytes;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in server_address;
    
    pthread_attr_t pthread_attr;
    pthread_args* pthread_arg;
    pthread_t thread;
    
    socklen_t client_len;
    
    memset(&server_address, 0, sizeof(server_address));
        server_address.sin_family = AF_INET;
        server_address.sin_port = htons(PORT);
        server_address.sin_addr.s_addr = INADDR_ANY;
        
        //socket
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            exit(EXIT_FAILURE);
        }
        
        //bind
        if (bind(sockfd, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
            perror("bind");
            exit(EXIT_FAILURE);
    }
    
    //listen
    if (listen(sockfd, BACKLOG) == -1) {
            perror("listen");
            exit(EXIT_FAILURE);
        }
        
        if (pthread_attr_init(&pthread_attr) != 0) {
            perror("pthread_attr_init");
            exit(EXIT_FAILURE);
        }
        
    if (pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED) != 0) {
            perror("pthread_attr_setdetachstate");
            exit(EXIT_FAILURE);
        }
        
        while(1) {
            pthread_arg = (pthread_args*)malloc(1*sizeof(pthread_args*));
            
            if(!pthread_arg) {
                perror("malloc");
                exit(EXIT_FAILURE);
            }
            
            //accept
            client_len = sizeof(pthread_arg->client_address);
            new_sockfd = accept(sockfd, (struct sockaddr*)&pthread_arg->client_address, &client_len);
            if(new_sockfd == -1) {
                perror("accept");
                free(pthread_arg);
                exit(EXIT_FAILURE);
            }
            
            bzero(buffer, BUFFER_SIZE-1);
            if((read(new_sockfd, &buffer, BUFFER_SIZE-1))<0) {
                perror("read");
                exit(EXIT_FAILURE);
            }
            
            pthread_arg->sockfd = new_sockfd;
            pthread_arg->message=(char*)malloc(strlen(buffer));
            if(!pthread_arg->message) {
                perror("malloc");
                exit(EXIT_FAILURE);
            }
            strcpy(pthread_arg->message, buffer);
            pthread_arg->seconds = 0;
            pthread_arg->delay = 0;
            
            if(pthread_create(&thread, &pthread_attr, handler, (void*)pthread_arg) != 0) {
                perror("pthread_create");
                free(pthread_arg);
                exit(EXIT_FAILURE);
            }
        }
    
    shutdown(sockfd, SHUT_RDWR);
    close(sockfd);
    return 0;

}

client.c

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

#define PORT 7507
#define SERVER "localhost"
#define BUFFER_SIZE 2048

int main(int argc, char *argv[])
{
    int sockfd, bytes;
    
    struct hostent* server_host;
    struct sockaddr_in server_address;
    
    char message_sent[BUFFER_SIZE];
    char message_received[BUFFER_SIZE];
    
    if(argc>1) {
        printf("too many arguments\n");
        exit(EXIT_FAILURE);
    }
    
    server_host = gethostbyname(SERVER);
    
    memset(&server_address, 0, sizeof(server_address));
        server_address.sin_family = AF_INET;
        server_address.sin_port = htons(PORT);
        memcpy(&server_address.sin_addr.s_addr, server_host->h_addr, server_host->h_length);
        
        //socket
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            exit(EXIT_FAILURE);
        }
        
        //connect
        if (connect(sockfd, (struct sockaddr *)&server_address, sizeof server_address) == -1) {
        perror("connect");
            exit(EXIT_FAILURE);
    }
    
    while(1) {
        bzero(message_sent, sizeof(message_sent));
        printf("Your message: ");
        fgets(message_sent, BUFFER_SIZE-1, stdin);
        message_sent[strcspn(message_sent, "\n")]=0;
        
        //sending to server
        if((write(sockfd, &message_sent, strlen(message_sent)))<0) {
            perror("write");
            exit(EXIT_FAILURE);
        }
    
        bzero(message_received, sizeof(message_received));
        //reading from server
        if ((read(sockfd, &message_received, BUFFER_SIZE-1))<0)
        {
            perror("read");
            exit(EXIT_FAILURE);
        }
    
        printf("Server says: %s\n", message_received);
    }
    
    close(sockfd);
    return 0;
}

终端输出

客户端:

Your message: message1
Server says: abcd
Your message: message2
Server says:
Your message: message3
Server says:
*客户端自动停止*

服务器:

Message received from client (thread id=x): message1
*此处停止,未收到message2和message3,但服务器仍运行*

问题分析与解决方案

核心问题

  1. 内存分配错误:服务器main函数中malloc(1*sizeof(pthread_args*))仅分配了指针大小的内存,远小于结构体实际大小,导致内存越界破坏数据。
  2. 连接处理逻辑错误:服务器每接收一个连接后只处理一次消息就关闭连接,但客户端是在同一个连接上持续发消息,连接关闭后后续读写必然失败。
  3. 野指针访问:handler函数中free(args)后仍访问pthread_arg->message,属于未定义行为,会导致程序异常。
  4. TCP流边界未处理:TCP是字节流,代码未添加消息结束符,也未处理粘包/拆包,导致消息读取不完整。

修复步骤

1. 修复服务器内存分配

将main中的内存分配代码改为:

pthread_arg = (pthread_args*)malloc(sizeof(pthread_args));

2. 重构服务器连接处理逻辑

服务器应为每个客户端连接创建线程,在线程内持续循环处理消息,而非单次处理后关闭连接:

void* handler(void* args) {
    pthread_args* pthread_arg = (pthread_args*)args;
    int sockfd = pthread_arg->sockfd;
    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    free(args); // 先释放结构体内存,避免后续误访问

    // 持续处理该连接的消息
    while (1) {
        bzero(buffer, BUFFER_SIZE);
        bytes_read = read(sockfd, buffer, BUFFER_SIZE-1);
        if (bytes_read < 0) {
            perror("read");
            break;
        } else if (bytes_read == 0) {
            printf("Client disconnected (thread id=%d)\n", gettid());
            break;
        }

        // 处理终止命令
        if (strcmp(buffer, "stop") == 0) {
            printf("Client sent stop command (thread id=%d)\n", gettid());
            write(sockfd, "stopping", 8);
            break;
        }

        printf("Received from client (thread id=%d): %s\n", gettid(), buffer);
        // 回复时添加换行符,确保客户端能识别完整消息
        if (write(sockfd, "abcd\n", 5) < 0) {
            perror("write");
            break;
        }
    }

    close(sockfd);
    return NULL;    
}

同时删除main中提前读取消息的代码,将连接直接交给线程处理:

// 移除main中的这段代码:
/*
bzero(buffer, BUFFER_SIZE-1);
if((read(new_sockfd, &buffer, BUFFER_SIZE-1))<0) {
    perror("read");
    exit(EXIT_FAILURE);
}

pthread_arg->message=(char*)malloc(strlen(buffer));
if(!pthread_arg->message) {
    perror("malloc");
    exit(EXIT_FAILURE);
}
strcpy(pthread_arg->message, buffer);
*/
// 改为初始化结构体字段
pthread_arg->sockfd = new_sockfd;
pthread_arg->message = NULL;
pthread_arg->seconds = 0;
pthread_arg->delay = 0;

3. 修复客户端读写逻辑

  • 修正write的参数(数组名本身就是指针,无需取地址),并发送字符串结束符:
if(write(sockfd, message_sent, strlen(message_sent)+1) <0) {
    perror("write");
    exit(EXIT_FAILURE);
}
  • 处理服务器关闭连接的情况,并确保字符串结束:
bytes = read(sockfd, message_received, BUFFER_SIZE-1);
if (bytes < 0) {
    perror("read");
    exit(EXIT_FAILURE);
} else if (bytes == 0) {
    printf("Server closed connection\n");
    break;
}
message_received[bytes] = '\0';
  • 添加客户端终止逻辑:
// 发送消息后判断是否终止
if (strcmp(message_sent, "stop") == 0) {
    printf("Stopping client\n");
    break;
}
// 接收回复后判断是否终止
if (strcmp(message_received, "stopping") == 0) {
    printf("Server acknowledged stop command\n");
    break;
}

4. 信号处理(可选)

添加信号处理函数,响应SIGINT、SIGSTOP信号:

#include <signal.h>

volatile sig_atomic_t stop = 0;

void handle_signal(int sig) {
    stop = 1;
}

int main() {
    // ... 其他代码 ...
    signal(SIGINT, handle_signal);
    signal(SIGSTOP, handle_signal);

    while(!stop) {
        // ... 原有循环代码 ...
        if (stop) break;
    }

    close(sockfd);
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 03:42:07