Linux下C语言Socket客户端/服务端登录后无响应问题排查
我帮你排查了代码中的几个关键问题,导致输入用户名回车后双方无响应的核心原因和修复方法如下:
1. 服务端未检查密码文件是否成功打开(最直接原因)
在readUser和readPass函数中,你调用fopen("passwd.txt", "r")后没有检查返回值。如果当前目录下不存在passwd.txt,或者服务端没有读取权限,fopen会返回NULL。后续代码中while (!feof(f1))使用NULL文件指针调用feof,这会触发未定义行为,通常表现为服务端无限循环卡住,无法给客户端返回任何响应,客户端则一直阻塞在read调用上等待回复,最终双方都无响应。
修复方法:
在打开文件后立即添加检查逻辑:
// 在readUser函数中 f1 = fopen("passwd.txt", "r"); if (f1 == NULL) { error("ERROR opening passwd.txt"); } // 在readPass函数中同样添加该检查
2. 密码文件解析的格式错误
readPass函数中的sscanf格式字符串写错了:
sscanf(newbuf, "%[^:]_%[^:]", user, pass);
你使用了下划线_作为用户名和密码的分隔符,但密码文件的格式应该是用户名:密码(冒号分隔),这会导致密码无法被正确读取,后续的密码验证逻辑失效。另外,fgets会读取行尾的换行符,需要在格式字符串中排除它避免密码包含换行符。
修复方法:
修改为冒号分隔,并排除换行符:
sscanf(newbuf, "%[^:]:%[^\n]", user, pass);
3. 错误的条件判断(赋值代替比较)
客户端和服务端多处出现if (n = 0)的错误写法,这是赋值操作而非比较操作,会导致错误判断完全失效(比如write/read失败时无法触发错误处理)。虽然这不是直接导致无响应的原因,但会隐藏其他潜在问题。
修复方法:
所有此类判断都改为if (n < 0)(因为write和read返回-1表示错误,返回0表示连接关闭):
// 客户端sendUser函数中 if (n < 0) { error("ERROR writing to socket"); } // 客户端main函数中读取响应的判断 if (n < 0) { error("ERROR reading from socket"); } // 服务端sendResponse等函数中的判断同理修改
4. 低效且易出错的文件读取逻辑
readUser中找到用户名后,readPass重新打开文件并循环到对应行读取密码,这种方式不仅低效,还可能因为文件在两次读取之间被修改导致数据不一致。更合理的做法是在readUser中找到匹配的用户名时直接提取对应的密码,传递给后续逻辑。
优化示例:
修改readUser函数,找到匹配用户时直接获取密码并调用密码验证:
int readUser(int newsockfd, int n, char buffer[]) { bzero(buffer, 256); n = read(newsockfd,buffer,255); if (n < 0) { error("ERROR reading from socket"); } printf("Received username: %s\n", buffer); char valid[1]; char newbuf[256]; char user[8]; char pass[8]; FILE *f1 = fopen("passwd.txt", "r"); if (f1 == NULL) { error("ERROR opening passwd.txt"); } while (fgets(newbuf, 256, f1) != NULL) { sscanf(newbuf, "%[^:]:%[^\n]", user, pass); if (strcmp(buffer, user) == 0) { fclose(f1); // 及时关闭文件 n = write(newsockfd, "1", 1); if (n < 0) error("ERROR writing to socket"); // 直接调用密码验证,传递已获取的密码 return verifyPass(newsockfd, n, buffer, pass); } } fclose(f1); n = write(newsockfd, "Invalid username, please try again\n", 34); if (n < 0) error("ERROR writing to socket"); return 0; } // 新增verifyPass函数代替原readPass int verifyPass(int newsockfd, int n, char buffer[], char *correctPass) { bzero(buffer, 256); n = read(newsockfd,buffer,255); if (n < 0) { error("ERROR reading from socket"); } if (strcmp(buffer, correctPass) == 0) { n = write(newsockfd, "1", 1); if (n < 0) error("ERROR writing to socket"); sendResponse(newsockfd, n); return 1; } else { n = write(newsockfd, "Invalid password, please try again\n", 33); if (n < 0) error("ERROR writing to socket"); return 0; } }
额外注意事项
- 服务端当前只能处理一个客户端连接,处理完后就退出了,如果需要支持多客户端,需要结合
fork或线程实现(你代码中注释了fork相关代码,可以完善这部分逻辑)。 - 客户端的ncurses操作中,
addch('*')应该指定窗口(比如waddch(login, '*')),否则可能无法正确显示密码输入的星号。
内容的提问来源于stack exchange,提问作者XxSwiftflamexX

