考虑下面的基本客户端和服务器程序(只需裸露骨头/即可说明我的问题(。客户端启动与服务器的连接,提示用户输入消息,然后将其发送到服务器并打印到屏幕。
如果我在循环中间突然退出客户端程序(例如,通过关闭终端窗口(,有时客户端将继续在循环中迭代一段时间(即,最后发送到服务器/发送到服务器/当前驻留在客户关闭时的写缓冲区中,通常将其发送到服务器,通常直到循环耗尽为止(。但是,其他时间,读取服务器上的read((调用正确返回0,并且连接无问题(行为似乎是相当随机的(。
我不太了解这里发生了什么。首先,为什么程序关闭后会发生其他循环迭代?何时关闭终端窗口和实际过程本身何时结束时,是否只有一个滞后时间?即使确实发生了额外的循环迭代,也不应该在用户输入消息之前呼叫FGETS((块?
我正在使用XFCE桌面的Fedora 25工作站。
我尝试搜索有关此信息的信息,但没有太多运气(我不确定如何以简洁的方式搜索此信息(。非常感谢任何帮助。
谢谢
客户端:
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
int main(void) {
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(3000);
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
connect(sockfd, (struct sockaddr *)&server, sizeof(server));
int i;
for (i = 0; i < 20; i++) {
char buf[512];
printf("Send a message: ");
fgets(buf, 512, stdin);
write(sockfd, buf, sizeof(buf));
}
close(sockfd);
}
服务器:
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
int main(void) {
int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(3000);
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
bind(listenfd, (struct sockaddr *)&server, sizeof(server));
listen(listenfd, 10);
printf("Listening...n");
struct sockaddr_in client;
socklen_t client_size = sizeof(client);
int clientfd = accept(listenfd, (struct sockaddr *)&client, &client_size);
for (;;) {
char buf[512];
int i = read(clientfd, buf, sizeof(buf));
if (i == 0) {
close(clientfd);
printf("Connection Closedn");
break;
} else {
printf("%s", buf);
}
}
close(listenfd);
}
当您的终端(以及连接到您的过程的stdin/out/err(的PTY设备的远程/主侧已关闭时,fgets
将在stdin
上看到文件终止状态,并且将立即使用不完整的行(不在n
中结束(或根本没有输入(null返回值(返回;实际上,这将是后者。如果您检查了结果,则可以看到并可以采取相应的行动。
在服务器中,此行:
printf("%s", buf);
应替换为:
*buf[i] = 'n';
printf( "%s", buf );
因此,有一个有效的字符串打印(read()
不会终止字符串(
注意:如果发生I/O错误或信号发生,等等,则read()
将返回一个小于0的值,应导致退出for(;;(循环,而不是在循环中继续,打印''buf[]
的旧内容
在客户端的这一行中:
write(sockfd, buf, sizeof(buf));
如果写入失败,则如果发生此类事件时,它将返回小于0的值,则循环应退出,不继续循环,
检查所有错误条件非常重要。进行错误检查(以及由此导致的错误处理(可以轻松地将代码的大小增加一倍,但必须完成;否则,您看到的这样的"奇数"事件将发生,而没有简单地解释发生的事情。
当系统函数返回错误指示时,请使用函数perror()
在stderr
上提供了一些文本,以及系统中的消息,即为什么会认为发生错误。
如果我突然退出循环中间的客户端程序(例如,通过关闭终端窗口(,
关闭终端窗口不会退出客户端程序 - 它会继续运行,只是没有输入(因此,从现在关闭的终端中的任何读取都将返回EOF(。但是,您永远不会检查客户端中fgets
的返回值,因此您从未注意到,只会继续循环,发送相同的缓冲区。
另外,代码:
fgets(buf, 512, stdin);
write(sockfd, buf, sizeof(buf));
从输入中读取最多511个字符的线,但随后发送整个512字节缓冲区,无论实际消息多长时间。您想要的更像是:
if (fgets(buf, 512, stdin))
write(sockfd, buf, strlen(buf));
else
break; // there was an error or EOF on reading from stdin
当然,这仍然存在超过511个字节的行问题,然后有一个问题,即TCP不会保留消息边界,因此在服务器上,您可能会在单个读取呼叫中获得多个或仅一部分消息的一部分。