C语言TCP客户端服务端二次输入命令返回无效的问题排查
TCP客户端服务端二次命令返回"invalid command"问题排查与修复
问题现象
用C语言开发的TCP客户端服务端应用,各功能首次执行正常,但客户端输入time这类无参数命令时,服务器第一次能返回正确响应,第二次执行相同命令就会返回invalid command。
核心原因
问题出在strtok函数的特性及使用方式上:
strtok会修改原字符串:它会用NULL字节替换字符串中的分隔符,直接破坏传入的缓冲区内容。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
相关产品推荐
相关产品推荐

