当在循环中突然退出 C 程序时,为什么会发生额外的循环迭代?
When abruptly exiting a C program mid-loop, why do additional loop iterations occur?
考虑下面的基本客户端和服务器程序(只是骨架/说明我的问题)。客户端发起与服务器的连接,提示用户输入消息,然后发送到服务器并打印到屏幕。
如果我在循环中间突然退出客户端程序(例如通过关闭终端window),有时客户端会继续迭代循环一段时间(即最后一次发送到服务器的消息/客户端关闭时当前驻留在写缓冲区中的消息被重复发送到服务器,通常直到循环耗尽)。然而,其他时候,服务器上的 read() 调用正确 returns 0,并且连接关闭没有问题(行为似乎相当随机)。
我不太明白这是怎么回事。首先,为什么程序关闭后会出现额外的循环迭代?终端 window 关闭与实际进程本身结束之间是否存在滞后时间?即使确实发生了额外的循环迭代,对 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 Closed\n");
break;
} else {
printf("%s", buf);
}
}
close(listenfd);
}
当您的终端(以及连接到您的进程 stdin/out/err 的 pty 设备的 remote/master 端)关闭时,fgets
将在 stdin
,并且将 return 立即与不完整的行(不以 \n
结尾)或根本没有输入(空 return 值);实际上,它将是后者。如果您检查了结果,您会看到这一点并能够采取相应的行动。
在服务器中,这一行:
printf("%s", buf);
应替换为:
*buf[i] = '\n';
printf( "%s", buf );
所以有一个有效的字符串要打印(read()
不会终止字符串)
注意:如果发生 I/O 错误或出现信号等,则 read()
将 return 一个小于 0 的值,并应导致退出 for(;;; ) 循环,不继续循环,打印 buf[]
的 'old' 内容
在客户端的这一行:
write(sockfd, buf, sizeof(buf));
如果写入失败,会return一个小于0的值if/when这样的事件发生,应该退出循环,而不是继续循环,
检查所有错误情况非常重要。这种错误检查(以及由此产生的错误处理)很容易使代码的大小加倍,但必须这样做;否则,您所看到的 'odd' 事件将会发生,并且不会对发生的事情进行简单的解释。
当系统函数 return 出现错误指示时,使用函数 perror()
将您提供的一些文本显示在 stderr
上,以及来自系统的消息为什么它认为发生了错误。
If I abruptly quit the client program in the middle of the loop (e.g. by closing the terminal window),
关闭终端 window 不会退出客户端程序——它继续 运行,只是没有输入(因此从现在关闭的终端读取的任何内容都会 return EOF ).但是,您永远不会在客户端检查 fgets
的 return 值,因此您永远不会注意到,您只是不断循环,发送相同的缓冲区。
另外,代码:
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 不保留消息边界的问题,因此在服务器上,您可能会在单个消息中收到多个消息或仅消息的一部分读取调用。
考虑下面的基本客户端和服务器程序(只是骨架/说明我的问题)。客户端发起与服务器的连接,提示用户输入消息,然后发送到服务器并打印到屏幕。
如果我在循环中间突然退出客户端程序(例如通过关闭终端window),有时客户端会继续迭代循环一段时间(即最后一次发送到服务器的消息/客户端关闭时当前驻留在写缓冲区中的消息被重复发送到服务器,通常直到循环耗尽)。然而,其他时候,服务器上的 read() 调用正确 returns 0,并且连接关闭没有问题(行为似乎相当随机)。
我不太明白这是怎么回事。首先,为什么程序关闭后会出现额外的循环迭代?终端 window 关闭与实际进程本身结束之间是否存在滞后时间?即使确实发生了额外的循环迭代,对 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 Closed\n");
break;
} else {
printf("%s", buf);
}
}
close(listenfd);
}
当您的终端(以及连接到您的进程 stdin/out/err 的 pty 设备的 remote/master 端)关闭时,fgets
将在 stdin
,并且将 return 立即与不完整的行(不以 \n
结尾)或根本没有输入(空 return 值);实际上,它将是后者。如果您检查了结果,您会看到这一点并能够采取相应的行动。
在服务器中,这一行:
printf("%s", buf);
应替换为:
*buf[i] = '\n';
printf( "%s", buf );
所以有一个有效的字符串要打印(read()
不会终止字符串)
注意:如果发生 I/O 错误或出现信号等,则 read()
将 return 一个小于 0 的值,并应导致退出 for(;;; ) 循环,不继续循环,打印 buf[]
在客户端的这一行:
write(sockfd, buf, sizeof(buf));
如果写入失败,会return一个小于0的值if/when这样的事件发生,应该退出循环,而不是继续循环,
检查所有错误情况非常重要。这种错误检查(以及由此产生的错误处理)很容易使代码的大小加倍,但必须这样做;否则,您所看到的 'odd' 事件将会发生,并且不会对发生的事情进行简单的解释。
当系统函数 return 出现错误指示时,使用函数 perror()
将您提供的一些文本显示在 stderr
上,以及来自系统的消息为什么它认为发生了错误。
If I abruptly quit the client program in the middle of the loop (e.g. by closing the terminal window),
关闭终端 window 不会退出客户端程序——它继续 运行,只是没有输入(因此从现在关闭的终端读取的任何内容都会 return EOF ).但是,您永远不会在客户端检查 fgets
的 return 值,因此您永远不会注意到,您只是不断循环,发送相同的缓冲区。
另外,代码:
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 不保留消息边界的问题,因此在服务器上,您可能会在单个消息中收到多个消息或仅消息的一部分读取调用。