代码之家  ›  专栏  ›  技术社区  ›  jkramer

如何防止信号管(或正确处理)

  •  226
  • jkramer  · 技术社区  · 16 年前

    我有一个小型的服务器程序,它接受TCP或本地UNIX套接字上的连接,读取一个简单的命令,并根据命令发送一个回复。问题是,客户机有时可能对答案不感兴趣,并且很早就退出了,因此写入该套接字将导致sigpipe并使我的服务器崩溃。防止撞车的最佳做法是什么?是否有方法检查行的另一侧是否仍在读取?(select()在这里似乎不起作用,因为它总是说套接字是可写的)。或者我应该用一个处理程序捕捉信号管道并忽略它吗?

    10 回复  |  直到 6 年前
        1
  •  220
  •   dvorak    9 年前

    你一般都想忽略 SIGPIPE 并直接在代码中处理错误。这是因为C中的信号处理程序对它们的功能有许多限制。

    最便携的方法是设置 硅管 处理程序到 SIG_IGN . 这将防止任何套接字或管道写入导致 硅管 信号。

    忽视 硅管 信号,使用以下代码:

    signal(SIGPIPE, SIG_IGN);
    

    如果你在使用 send() 调用,另一个选项是使用 MSG_NOSIGNAL 选项,它将 硅管 在每次呼叫的基础上关闭行为。请注意,并非所有操作系统都支持 水蚤目 旗帜。

    最后,您还可以考虑 SO_SIGNOPIPE 可设置的套接字标志 setsockopt() 在某些操作系统上。这将阻止 硅管 因为只写它所设置的套接字。

        2
  •  145
  •   srdjan.veljkovic    6 年前

    另一种方法是更改套接字,这样它就不会在write()时生成sigpipe。这在库中更方便,因为您可能不希望使用用于sigpipe的全局信号处理程序。

    在大多数基于BSD的(MACOS,FreeBSD…)系统中,(假设你使用C/C++),你可以这样做:

    int set = 1;
    setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
    

    在这种情况下,将返回epipe,而不是生成sigpipe信号。

        3
  •  110
  •   Community CDub    7 年前

    我参加聚会迟到了,但是 SO_NOSIGPIPE 不可移植,可能无法在您的系统上工作(这似乎是一个BSD问题)。

    一个不错的选择,如果你在,比如说,一个没有 硫磺管 将设置 MSG_NOSIGNAL 发送(2)呼叫时的标志。

    示例替换 write(...) 通过 send(...,MSG_NOSIGNAL) (见 nobar 的评论)

    char buf[888];
    //write( sockfd, buf, sizeof(buf) );
    send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
    
        4
  •  28
  •   hroptatyr    10 年前

    在这 post 我描述了在既没有sou nosigpipe也没有msg nosignal的情况下,Solaris案例的可能解决方案。

    相反,我们必须在当前执行库代码的线程中临时抑制sigpipe。以下是如何做到这一点:为了抑制sigpipe,我们首先检查它是否处于挂起状态。如果是这样,这意味着它被阻塞在这个线程中,我们不需要做任何事情。如果库生成额外的sigpipe,它将与挂起的sigpipe合并,这是一个no-op。如果sigpipe没有挂起,那么我们将在这个线程中阻塞它,并检查它是否已经被阻塞。然后我们可以自由地执行我们的写入操作。当我们要将sigpipe恢复到其原始状态时,我们将执行以下操作:如果sigpipe最初处于挂起状态,则不执行任何操作。否则,我们检查它是否正在等待处理。如果是这样(这意味着out操作已经生成了一个或多个sigpipe),那么我们在该线程中等待它,从而清除其挂起状态(为此,我们使用具有零超时的sigtimedwait();这是为了避免在恶意用户手动将sigpipe发送到整个进程的情况下阻塞:在这种情况下,我们将看到它挂起,但其他R线程可以在我们更改等待它之前处理它)。清除挂起状态后,我们将在此线程中取消阻塞sigpipe,但前提是它最初没有被阻塞。

    实例代码 https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

        5
  •  19
  •   Sam    13 年前

    就地处理信号管

    通常最好在本地处理错误,而不是在全局信号事件处理程序中处理错误,因为在本地,您将有更多的上下文来了解正在发生的事情和要采取的措施。

    我的一个应用程序中有一个通信层,允许我的应用程序与外部附件通信。当发生写错误时,我在通信层中抛出异常并让它冒泡到一个try-catch块来处理它。

    代码:

    忽略sigpipe信号以便在本地处理的代码是:

    // We expect write failures to occur but we want to handle them where 
    // the error occurs rather than in a SIGPIPE handler.
    signal(SIGPIPE, SIG_IGN);
    

    此代码将阻止引发sigpipe信号,但在尝试使用套接字时,您将收到一个读/写错误,因此需要检查该错误。

        6
  •  13
  •   Jonathan Leffler    16 年前

    您不能阻止管道远端的进程退出,如果在您完成写入之前退出,您将收到一个sigpipe信号。如果您发出信号,那么您的写入将返回一个错误-您需要注意并对该错误作出反应。仅仅捕获和忽略处理程序中的信号并不是一个好主意——您必须注意管道现在已经失效,并修改程序的行为,使其不再写入管道(因为信号将再次生成,并再次忽略,您将重试,整个过程可以继续执行 长的 时间和浪费大量的CPU电源)。

        7
  •  5
  •   Sam Reynolds    16 年前

    或者我应该用一个处理程序捕捉信号管道并忽略它吗?

    我相信这是对的。你想知道另一端什么时候关闭了他们的描述符,这就是sigpipe告诉你的。

    山姆

        8
  •  2
  •   talash    11 年前

    Linux手册说:

    epipe本地端已在面向连接的情况下关闭 插座。在这种情况下,进程还将接收一个sigpipe 除非设置了msg nosignal。

    但对于Ubuntu 12.04来说,这是不对的。我为那个案子写了一个测试,我总是收到没有信号管的EPIPE。如果我第二次尝试写入同一个损坏的套接字,则生成sigpipe。所以,如果发生这个信号,您不需要忽略sigpipe,它意味着程序中的逻辑错误。

        9
  •  2
  •   Ben Aveling    9 年前

    防止撞车的最佳做法是什么?

    或者根据每个人禁用信号管道,或者捕获并忽略错误。

    是否有方法检查行的另一侧是否仍在读取?

    是,使用select()。

    select()在这里似乎不起作用,因为它总是说套接字是可写的。

    您需要选择 阅读 位。你可能会忽略 位。

    当远端关闭其文件句柄时,select将告诉您有准备读取的数据。当你去读它的时候,你会得到0个字节,这就是操作系统告诉你文件句柄已经关闭的方式。

    唯一不能忽略写入位的时间是,如果您发送的是大容量的数据,那么另一端可能会有被积压的风险,这会导致缓冲区被填满。如果发生这种情况,那么尝试写入文件句柄可能会导致程序/线程阻塞或失败。在写之前测试select可以保护您不受影响,但它不能保证另一端是健康的,或者您的数据将到达。

    请注意,您可以从close()以及在编写时获取sigpipe。

    关闭将刷新所有缓冲数据。如果另一端已经关闭,那么关闭将失败,您将收到一个信号管。

    如果您使用的是缓冲TCPIP,那么成功的写入只意味着您的数据已经排队发送,而不是意味着它已经发送。在成功调用Close之前,您不知道数据已发送。

    Sigpipe告诉你出了什么问题,它没有告诉你该怎么做,或者你应该怎么做。

        10
  •  1
  •   Alexis Wilke    6 年前

    在现代POSIX系统(即Linux)下,您可以使用 sigprocmask() 功能。

    #include <signal.h>
    
    void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
    {
        sigset_t set;
        sigset_t old_state;
    
        // get the current state
        //
        sigprocmask(SIG_BLOCK, NULL, &old_state);
    
        // add signal_to_block to that existing state
        //
        set = old_state;
        sigaddset(&set, signal_to_block);
    
        // block that signal also
        //
        sigprocmask(SIG_BLOCK, &set, NULL);
    
        // ... deal with old_state if required ...
    }
    

    如果以后要还原以前的状态,请确保保存 old_state 在安全的地方。如果多次调用该函数,则需要使用堆栈或只保存第一个或最后一个 奥尔德状态 …或者可能具有移除特定阻塞信号的功能。

    有关更多信息,请阅读 man page .