1

考虑下面的基本客户端和服务器程序(只是简单的 / 来说明我的问题)。客户端启动与服务器的连接,提示用户输入一条消息,然后将其发送到服务器并打印到屏幕上。

如果我在循环中间突然退出客户端程序(例如通过关闭终端窗口),有时客户端会继续循环循环一段时间(即最后一条消息发送到服务器/当前驻留在客户端关闭时的写入缓冲区会重复发送到服务器,通常直到循环耗尽)。然而,其他时候,服务器上的 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 Closed\n");
            break;
        } else {
            printf("%s", buf);
        }
    }

    close(listenfd);
}
4

3 回答 3

4

当您的终端(以及连接到您的进程的 stdin/out/err 的 pty 设备的远程/主端)关闭时,fgets将在 上看到文件结束状态stdin,并将立即返回不完整的行(未结束in \n) 或根本没有输入(返回值为空);在实践中它将是后者。如果您检查了结果,您将看到这一点并能够采取相应的行动。

于 2017-04-30T03:57:32.797 回答
0

在服务器中,这一行:

printf("%s", buf); 

应替换为:

*buf[i] = '\n'; 
printf( "%s", buf );

所以有一个有效的字符串要打印(read()不会终止字符串)

注意:如果发生 I/O 错误或出现信号等,read()则将返回小于 0 的值,并应导致退出 for(;;;) 循环,而不是继续循环,打印“旧”内容buf[]

在客户端的这一行中:

write(sockfd, buf, sizeof(buf));

如果写入失败,它将返回一个小于 0 的值如果/当这样的事件发生时,循环应该退出,而不是继续循环,

检查所有错误情况非常重要。这种错误检查(以及由此产生的错误处理)可以很容易地使代码的大小增加一倍,但必须这样做;否则,您所看到的这种“奇怪”事件将会发生,而对发生的事情没有简单的解释。

当系统函数返回错误指示时,使用该函数perror() 显示您提供的一些文本stderr,以及来自系统的消息,说明它认为错误发生的原因。

于 2017-04-30T17:49:33.490 回答
0

如果我在循环中间突然退出客户端程序(例如通过关闭终端窗口),

关闭终端窗口不会退出客户端程序——它会继续运行,只是没有输入(因此从现在关闭的终端中读取的任何内容都将返回 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 不保留消息边界的问题,因此在服务器上,您可能会在单个读取调用中获得多个消息或仅部分消息。

于 2017-04-30T20:01:52.193 回答