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

C语言TCP客户端服务端二次输入命令返回无效的问题排查

TCP客户端服务端二次命令返回"invalid command"问题排查与修复

问题现象

用C语言开发的TCP客户端服务端应用,各功能首次执行正常,但客户端输入time这类无参数命令时,服务器第一次能返回正确响应,第二次执行相同命令就会返回invalid command。

核心原因

问题出在strtok函数的特性及使用方式上:

  1. strtok会修改原字符串:它会用NULL字节替换字符串中的分隔符,直接破坏传入的缓冲区内容。
  2. strtok依赖静态内部指针:函数内部维护了一个静态指针记录上次分割的位置,多次调用时如果不传入新的字符串指针,会基于上次的状态继续分割。

在你的代码中,CheckValidCommand函数先对buffer调用strtok做校验,修改了buffer的内容并改变了strtok的内部状态;回到menu函数后再次调用strtok(buffer, " ")时,原buffer已经被破坏,加上strtok的静态指针残留,导致无法正确分割出命令,最终触发无效命令判断。

另外客户端每次发送sizeof(buffer)长度的数据(而非实际输入的命令长度),会导致服务器缓冲区残留垃圾数据,进一步干扰命令解析。

修复方案

1. 避免修改原命令缓冲区

在CheckValidCommand中复制一份原缓冲区到临时数组,用临时数组执行strtok操作,不破坏原命令数据:

int CheckValidCommand(char buff[])
{
    int isValid = FALSE;
    int wordCount = 0;
    char tokens[5][20];
    // 复制原缓冲区到临时数组,避免修改原命令数据
    char temp_buff[MAX];
    strcpy(temp_buff, buff);

    // 后续所有strtok操作均使用temp_buff,不再操作原buff
    for (int i = 0; i < strlen(temp_buff) - 1; i++)
    {
        if (temp_buff[i] == ' ')
        {
            wordCount++;
        }
    }
    wordCount++;

    char* token = strtok(temp_buff, " ");
    token = ConvertToLower(token);

    // 以下逻辑保持不变,仅将所有操作对象从buff改为temp_buff
    if (strcmp(token, "join") == 0)
    {
        int pos = 0;
        int totalTokens = 0;
            
        while (token != NULL)
        {
            if (strlen(token) > 20)
                return FALSE;
            
            strcpy(tokens[totalTokens], token);

            totalTokens++;
            token = strtok(NULL, " ");
        }

        totalTokens--;

        char* Name = malloc(sizeof(char) * 20);
        char* Username = malloc(sizeof(char) * 20);

        strcpy(Username, tokens[1]);
        strcpy(Name, tokens[2]);
        strcat(Name, " ");

        for (int x = 2; x < totalTokens - 1; x++)
        {
            strcat(Name, " ");
            strcat(Name, tokens[x]);
        }
        strcat(Name, tokens[totalTokens]);

        if (strlen(Name) > 20 || strlen(Username) > 20)
            return FALSE;
        else
            return TRUE;
    }
    else if (strcmp(token, "whois") == 0)
    {
        if (wordCount > 2)
            return FALSE;

        token = strtok(NULL, " ");

        if (strlen(token) > 20)
            return FALSE;

        return TRUE;
    }
    else if (strcmp(token, "msg") == 0)
    {
        token = strtok(NULL, " ");

        if (strlen(token) > 20)
            return FALSE;

        char txt[MAX];
        bzero(txt, MAX); // 初始化缓冲区,避免垃圾数据干扰
        
        while (token != NULL)
        {
            strcat(txt, token);
            strcat(txt, " ");
            token = strtok(NULL, " "); // 补充token迭代,避免死循环
        }

        strcpy(txt, RemoveExtraSpace(txt));

        if (strlen(txt) > 256)
            return FALSE;

        return TRUE;
    }
    else if (strcmp(token, "time") == 0)
    {
        if (strtok(NULL, " ") == NULL)
            return TRUE;
        else 
            return FALSE;
    }
    else if (strcmp(token, "alive") == 0)
    {
        if (strtok(NULL, " ") == NULL)
            return TRUE;
        else 
            return FALSE;
    }
    else if (strcmp(token, "quit") == 0)
    {
        if (strtok(NULL, " ") == NULL)
            return TRUE;
        else 
            return FALSE;
    }
    else
    {
        return FALSE;
    }
}

2. 客户端发送实际命令长度

将客户端发送数据的逻辑从发送固定缓冲区长度,改为发送实际输入的命令长度(包含字符串终止符):

// 原代码
write(sockfd, buffer, sizeof(buffer));
// 修改为
write(sockfd, buffer, strlen(buffer) + 1); // +1 确保发送字符串终止符'\0'

3. 服务器端优化读取逻辑

服务器读取命令时,处理实际读取的字节数,确保字符串终止符正确:

//read command
bzero(buffer, MAX);
ssize_t read_len = read(connfd, buffer, MAX - 1); // 预留一个字节放终止符
if (read_len <= 0) {
    // 处理连接断开逻辑
    break;
}
buffer[read_len] = '\0'; // 手动添加字符串终止符

额外注意

strtok不是线程安全函数,如果后续扩展多线程服务,建议改用strtok_r(POSIX标准)或strtok_s(Windows标准)替代。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 02:35:19