【问题标题】:Server dies on `send` if client was closed with Ctrl+c如果客户端使用 Ctrl+c 关闭,服务器在“发送”时死机
【发布时间】:2013-06-24 08:01:42
【问题描述】:

我不明白为什么这个应用程序在send 上死掉了。这是服务器的代码:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <stdlib.h>
#include <errno.h>

#define UNIX_PATH_MAX    108
#define SPATH "./sock"

int main() {
    int sfd, rv = 100, newfd;
    char b[100];
    char ok[3] = "ok\0";
    struct sockaddr_un sa;

    sfd = socket(AF_UNIX, SOCK_STREAM, 0);

    strncpy(sa.sun_path, SPATH, UNIX_PATH_MAX);
    sa.sun_family = AF_UNIX;

    if (bind(sfd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (listen(sfd, SOMAXCONN) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    if ((newfd = accept(sfd, NULL, NULL)) == -1) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    while (rv != -1 && rv != 0) {
        rv = recv(newfd, b, 100, 0);

        printf("%s\n", b);

        sleep(3);

        printf("Send reply\n");
        send(newfd, ok, 3, 0);
        printf("Sent reply\n");
    }

    printf("END\n");
}

不幸的是,如果客户端在发送消息后立即使用Ctrl+C 强制关闭,服务器将打印(如最后一行):

before send

然后它什么也没做就死了。我试图检查 errno 或其他任何内容(包括在 if 语句中对 send 的调用),但似乎是 send 本身导致服务器死机。我试过write,但它是一样的。

我用 bash 检查了它的返回码,它以返回码 141 退出,我无法理解它的含义。

如果你想试试,这里是客户端代码:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>

#define UNIX_PATH_MAX    108
#define SPATH "./sock"

int main() {
    int sfd, rv;
    char b[100];
    char ok[3];
    struct sockaddr_un sa;

    sfd = socket(AF_UNIX, SOCK_STREAM, 0);

    strncpy(sa.sun_path, SPATH, UNIX_PATH_MAX);
    sa.sun_family = AF_UNIX;

    connect(sfd, (struct sockaddr*)&sa, sizeof(sa));

    while (scanf("%s", b)) {
        send(sfd, b, 100, 0);

        recv(sfd, ok, 3, 0);

        printf("%s\n", ok);
    }

    printf("END\n");
}

只需编译两者,运行两者,然后在消息发送后使用 Ctrl+C 终止客户端。

【问题讨论】:

  • 但这不是预期的吗?毕竟,服务器写入数据的客户端不再可用。
  • 在服务器中,刚才,为什么你没有检查recv()的返回值??,在使用printf之前,你必须nul终止你新接收的字符串,例如, b[rev] = 0
  • 没有人会“只编译两个,运行两个,然后杀死客户端”。您应该在此处发布错误消息、堆栈跟踪等。这不是免费的二级帮助台,您也没有报告某些供应商代码中的软件错误。 -1
  • 当程序的退出状态> 128时,表示它被status-128信号杀死。如果你查看 Unix 信号列表,你会看到 13 是SIGPIPE
  • @EJP,可能没有人编译它,但有些人已经阅读了整个问题,而不仅仅是最后一行。我已经发布了我得到的错误代码,他们甚至没有尝试运行代码就找到了解决方案。感谢您关于如何在 Stackoverflow 上提问的课程,但不幸的是,这不是我的疑问。干杯。

标签: c sockets send


【解决方案1】:

当您在已关闭的连接上调用send() 时,操作系统可能会向您的进程发出SIGPIPE。通常SIGPIPE 的默认处理程序是杀死你的进程。

为了防止这种情况发生,您可以忽略该信号(例如,使用signal(SIGPIPE, SIG_IGN)sigignore(SIGPIPE)),或者您可以将MSG_NOSIGNAL 选项传递给send()

int send_result = send(newfd, ok, 3, MSG_NOSIGNAL);
if (send_result >= 0) {
    /* okay, but check if all your data got sent! */
    if (send_result < 3) /* ...do something */;
} else {
    switch (errno) {
    /* ... */
    case EPIPE:
        /* sending on a closed connection... */
    }
}

现在,send() 调用将失败,而不是发出 SIGPIPEerrno 将设置为 EPIPE

【讨论】:

  • 不需要使用 errno,手册页明确指出:““当对等方执行有序关闭时,返回值将为 0。”因此,这甚至可能不是一个可移植的解决方案, 测试 EPIPE。
  • 我们检查errno,如果结果小于0,表示错误。有序关机的0 返回与recv()。我没有使用过EPIPE 检查对send() 错误的系统(Solaris、Linux、BSD、Winsock)。
  • Richard Stevens - Unix 网络编程第三版,第 5 章,第 5.3 节 TCP Echo 服务器。作者建议测试 0 而不是 EPIPE,实际上我找不到任何指向使用 EPIPE 的证据。但是,当然,您的答案似乎是正确的。
  • 但我再次看到没有证据表明使用 EPIPE 检查 0。你看到图 5.14 tcpcliserv/str_cli11.c 第 10 行了吗,Richard 正在测试 0。if (Readline(sockfd, recvline, MAXLINE) == 0)
  • 非常感谢。 MSG_NOSIGNAL 解决了这个问题。现在我可以正确捕获错误了。
【解决方案2】:

在服务器端,检查客户端是否关闭了连接:

rv = recv(newfd, b, 100, 0);
if(rv<=0) {
        if(!rv) {
             fprintf(stderr, "conn. closed.");
        } else {
              perror("recv");
        }
        exit(EXIT_FAILURE);
}

【讨论】:

  • !rv 不正确。您必须检查 rv == 0 以检测 EOS,并检查 -1 以检测错误。
  • @EJP 等等!,0 评估为 false,其他任何评估为 TRUE,如果 rv==0,则 !rv 将评估为 TRUE,并且 fprintf 函数将被执行。 Asterisk CodingStyle 明确建议使用 !variable 而不是 variable==0。伙计,你的 -1 太不正确了。
  • 如果在客户端已经被杀死时服务器正在发送,这不会阻止观察到的崩溃发生。
  • @Kira 这是一个技巧,也是一个令人困惑的技巧,正如这个讨论所示。返回值记录为 -1、0 或正整数,这是您应该测试的。您引用的编码风格中没有这样的建议。
  • @EJP 很抱歉引用错误;人为错误,我的意思是,Mozilla 编码风格,部分:C/C++ 实践。在这里:developer.mozilla.org/en-US/docs/Developer_Guide/Coding_Style 很容易检查。 “...使用 (x) 或 (!x) 代替...”
猜你喜欢
  • 1970-01-01
  • 2016-10-06
  • 2016-02-17
  • 1970-01-01
  • 1970-01-01
  • 2018-03-06
  • 1970-01-01
  • 2019-04-24
  • 1970-01-01
相关资源
最近更新 更多