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

基于线程的TCP Echo Server命令行无输出及LIST/GET命令异常停滞问题求助

问题排查与修复方案

我仔细检查了你的TCP Echo Server代码,找出了几个导致服务器无输出、处理GET命令时停滞的关键问题,下面逐个分析并给出修复方案:

1. 线程参数传递的竞态问题

在main函数的循环里,你把栈上的client变量地址传给了pthread_create,但主线程会立刻进入下一次循环,覆盖client的值。线程启动后读取的*(int *)arg会拿到错误的文件描述符,导致后续所有操作失效,这也是服务器无输出的核心原因之一。

修复方式:
动态分配内存存储client的值,避免主线程覆盖问题:

// 在main的accept之后
int *client_ptr = malloc(sizeof(int));
*client_ptr = client;
if (pthread_create(&child, NULL, Child, client_ptr) != 0) {
    perror("Thread creation");
    free(client_ptr); // 创建失败要释放内存
} else {
    pthread_detach(child);
}

然后在Child函数里记得释放内存:

int client = *(int *)arg;
free(arg); // 释放动态分配的内存

2. LIST命令中的无意义死循环

你的LIST命令里写了while(1),这会导致线程无限重复遍历目录,永远无法退出这个循环,既无法处理后续客户端命令,也会让服务器看起来陷入停滞。

修复方式:
直接去掉死循环,遍历一次目录就完成LIST响应,处理完后跳过后续的echo逻辑:

if (strcmp(line,"LIST\n") ==0) {
    DIR *d = opendir(".");
    if (d) {
        struct dirent *dir;
        while ((dir = readdir(d)) != NULL) {
            send(client, dir->d_name, strlen(dir->d_name), 0);
            send(client, "\n", 1, 0);
        }
        closedir(d);
    } else {
        send(client, "Failed to open directory\n", strlen("Failed to open directory\n"), 0);
    }
    continue; // 跳过echo逻辑,等待下一个命令
}

3. 字符串处理缺少终止符

recv函数不会自动给接收的缓冲区添加\0,但strcmp、StartsWith、printf("%s")这些函数都依赖字符串的\0结尾。这会导致字符串比较失败(比如strcmp(line,"LIST\n")永远不成立),服务器无法识别LIST命令,也会让printf输出乱码或者无输出。

修复方式:
每次recv成功后,立刻给缓冲区添加终止符:

bytes_read = recv(client, line, sizeof(line)-1, 0); // 留一个位置给'\0'
if (bytes_read > 0) {
    line[bytes_read] = '\0'; // 添加字符串终止符
    printf("Received: %s", line); // 现在printf能正常输出了
    // 后续的命令判断逻辑...
}

4. GET命令未实现核心逻辑

当前GET命令只是printf(line),没有读取文件并发送给客户端,而且处理完GET后还会执行后续的send(client, line, bytes_read, 0),把GET命令本身回传给客户端,同时因为没有处理文件,客户端会一直等待响应,看起来服务器停滞。

修复方式:
解析GET命令中的文件名,读取文件内容并发送,处理完成后跳过echo逻辑:

if (StartsWith(line,"GET ")) { // 注意加空格,避免匹配到GET开头的其他字符串
    char filename[DEFAULT_BUFLEN];
    sscanf(line, "GET %s", filename); // 解析文件名
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        char err_msg[DEFAULT_BUFLEN];
        snprintf(err_msg, sizeof(err_msg), "Error: File %s not found\n", filename);
        send(client, err_msg, strlen(err_msg), 0);
    } else {
        char file_buf[DEFAULT_BUFLEN];
        size_t read_bytes;
        while ((read_bytes = fread(file_buf, 1, sizeof(file_buf), fp)) > 0) {
            send(client, file_buf, read_bytes, 0);
        }
        fclose(fp);
        send(client, "\nFile transfer completed\n", strlen("\nFile transfer completed\n"), 0);
    }
    continue; // 跳过echo逻辑
}

另外,优化StartsWith函数避免重复计算字符串长度:

bool StartsWith(const char *a, const char *b) {
    size_t len_b = strlen(b);
    return strncmp(a, b, len_b) == 0;
}

修复后的完整代码

#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdbool.h>
#include <dirent.h>
#include <stdio.h>

/* Definations */
#define DEFAULT_BUFLEN 1024
#define PORT 1888

void PANIC(char* msg);
#define PANIC(msg) { perror(msg); exit(-1); }

bool StartsWith(const char *a, const char *b) {
    size_t len_b = strlen(b);
    return strncmp(a, b, len_b) == 0;
}

void* Child(void* arg) {
    char line[DEFAULT_BUFLEN];
    ssize_t bytes_read;
    int client = *(int *)arg;
    free(arg); // 释放动态分配的内存

    send(client, "Welcome to Vepa Server\n", strlen("Welcome to Vepa Server\n"), 0);

    do {
        bytes_read = recv(client, line, sizeof(line)-1, 0); // 留一个字节给终止符
        if (bytes_read > 0) {
            line[bytes_read] = '\0'; // 添加字符串终止符
            printf("Received from client: %s", line);

            // 处理LIST命令
            if (strcmp(line, "LIST\n") == 0) {
                DIR *d = opendir(".");
                if (d) {
                    struct dirent *dir;
                    while ((dir = readdir(d)) != NULL) {
                        send(client, dir->d_name, strlen(dir->d_name), 0);
                        send(client, "\n", 1, 0);
                    }
                    closedir(d);
                } else {
                    char err_msg[] = "Failed to open current directory\n";
                    send(client, err_msg, strlen(err_msg), 0);
                }
                continue; // 跳过后续的echo逻辑,等待下一个命令
            }

            // 处理GET命令
            if (StartsWith(line, "GET ")) {
                char filename[DEFAULT_BUFLEN];
                if (sscanf(line, "GET %s", filename) == 1) {
                    FILE *fp = fopen(filename, "r");
                    if (fp == NULL) {
                        char err_msg[DEFAULT_BUFLEN];
                        snprintf(err_msg, sizeof(err_msg), "Error: File '%s' does not exist\n", filename);
                        send(client, err_msg, strlen(err_msg), 0);
                    } else {
                        char file_buf[DEFAULT_BUFLEN];
                        size_t read_bytes;
                        while ((read_bytes = fread(file_buf, 1, sizeof(file_buf), fp)) > 0) {
                            send(client, file_buf, read_bytes, 0);
                        }
                        fclose(fp);
                        char success_msg[] = "\nFile transfer completed successfully\n";
                        send(client, success_msg, strlen(success_msg), 0);
                    }
                } else {
                    char err_msg[] = "Error: Invalid GET command format. Use 'GET filename'\n";
                    send(client, err_msg, strlen(err_msg), 0);
                }
                continue; // 跳过echo逻辑
            }

            // Echo逻辑:把收到的内容回传给客户端
            if (send(client, line, bytes_read, 0) < 0) {
                printf("Send failed\n");
                break;
            }
        } else if (bytes_read == 0) {
            printf("Connection closed by client\n");
            break;
        } else {
            printf("Recv failed: %s\n", strerror(errno));
            break;
        }
    } while (bytes_read > 0);

    close(client);
    return NULL;
}

int main(int argc, char *argv[]) {
    int sd, opt, optval;
    struct sockaddr_in addr;
    unsigned short port = 0;

    while ((opt = getopt(argc, argv, "p:")) != -1) {
        switch (opt) {
            case 'p':
                port = atoi(optarg);
                break;
            default:
                fprintf(stderr, "Usage: %s [-p port]\n", argv[0]);
                exit(EXIT_FAILURE);
        }
    }

    if ((sd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
        PANIC("Socket");

    addr.sin_family = AF_INET;
    addr.sin_port = (port > 0) ? htons(port) : htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    // 设置SO_REUSEADDR选项
    optval = 1;
    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0)
        PANIC("Bind");

    if (listen(sd, SOMAXCONN) != 0)
        PANIC("Listen");

    printf("System ready on port %d\n", ntohs(addr.sin_port));

    while (1) {
        int client;
        socklen_t addr_size = sizeof(addr);
        pthread_t child;

        client = accept(sd, (struct sockaddr*)&addr, &addr_size);
        if (client < 0) {
            perror("Accept failed");
            continue;
        }

        printf("Connected: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));

        // 动态分配内存存储client文件描述符,避免竞态问题
        int *client_ptr = malloc(sizeof(int));
        if (client_ptr == NULL) {
            perror("Malloc failed");
            close(client);
            continue;
        }
        *client_ptr = client;

        if (pthread_create(&child, NULL, Child, client_ptr) != 0) {
            perror("Thread creation failed");
            free(client_ptr);
            close(client);
        } else {
            pthread_detach(child); /* 与主线程分离,线程结束后自动释放资源 */
        }
    }

    return 0;
}

验证修复效果

  1. 服务器命令行现在会正常输出客户端连接信息和收到的命令。
  2. 发送LIST命令,服务器会返回当前目录的文件列表。
  3. 发送GET filename.txt命令,服务器会读取文件内容并发送给客户端,文件不存在时返回错误信息。
  4. 处理完命令后,服务器不会停滞,能继续接收客户端的下一个命令。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 03:32:43