【原创】SIGPIPE 信号处理整理

最近又遇到 SIGPIPE 问题,虽然这个问题是经典的老生常谈,但发现还是有些东西需要明确一下的。

如何处理 SIGPIPE 信号问题?应该在库里处理,还是在可执行程序里处理?

常见代码片段

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

// 写法一(简单粗暴,不判断出错情况)

#if defined(SIGPIPE) && !defined(_WIN32)

    (void) signal(SIGPIPE, SIG_IGN);

#endif

 

// 写法二(判断返回值情况)

#ifndef WIN32

    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)

        return 1;

#endif

 

// 写法三(使用 sigaction 替代 signal ,可以避免传统 signal 系统调用的问题)

    struct sigaction sa;

    sa.sa_handler = SIG_IGN;

    sigemptyset(&sa.sa_mask);

    sa.sa_flags = 0;

    if (sigaction(SIGPIPE, &sa, NULL) < 0)

    {

        perror("cannot ignore SIGPIPE");

        return -1;

    }

 

// 写法四(仅在 IOS 系统上支持 SO_NOSIGPIPE)

#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)

    // We do not want SIGPIPE if writing to socket.

    const int value = 1;

    setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(int));

#endif

注:关于传统 signal 系统调用的问题,可以参考《Linux signal那些事儿

产生 SIGPIPE 的条件
      对一个已经收到 FIN 包的 socket 调用 read 方法,如果接收缓冲已空,则返回 0,这就是常说的“连接关闭”表示。
      对一个已经收到 FIN 包的 socket 第一次调用 write 方法时,如果发送缓冲没问题,则 write 调用会返回写入的数据量,同时进行数据发送。但是发送出去的报文会导致对端发回 RST 报文。因为对端的 socket 已经调用了 close 进行了完全关闭,已经处于既不发送,也不接收数据的状态。所以第二次调用 write 方法时(假设在收到 RST 之后)会生成 SIGPIPE 信号,导致进程退出(这就是为什么第二次 write 才能触发 SIGPIPE 的原因)。

民间描述: 
对一个对端已经关闭的 socket 调用两次 write,第二次 write 将会生成 SIGPIPE 信号,该信号默认结束进程。 

APUE 上的描述: 
如果在写到管道时读进程已经终止,则产生此信号(管道角度)。当类型为 SOCK_STREAM 的套接字已不再连接时,进程写到该套接字也产生此信号(socket 角度)。

SIGPIPE 的处理方式
为了避免进程退出,既可以对 SIGPIPE 信号进行捕获,也可以将其忽略,即为其设置 SIG_IGN 信号处理函数(在系统头文件 <signal.h> 中定义的常量):

?


1

signal(SIGPIPE, SIG_IGN);

这样,当第二次调用 write 方法时,会返回 -1,同时 errno 会被设置成 EPIPE ,程序便能知道对端已经关闭。

-=-=-=-
关于 signal() 函数的说明:

  • signal 函数由 ISO C 定义。因为 ISO C 不涉及多进程,进程组以及终端 I/O 等,所以它对信号的定义非常含糊,以至于对 UNIX 系统而言几乎毫无用处。
  • 因为 signal 的语义与系统实现有关,所以最好使用 sigaction 函数代替 signal 函数。
  • signal 函数的限制:不改变信号的处理方式,就不能确定信号的当前处理方式(因为需要通过 signal 函数的返回值来确定以前的处理配置);sigaction 函数则没有这个问题。
  • 基于 signal 函数实现的信号处理可能是不可靠的(在早期的 UNIX 版本中,进程每次接到信号进行处理时,(内核已经在投递前)将该信号的动作复位为默认值 SIG_DFL,所以从信号发生之后到在信号处理程序中再次调用 signal 函数之前这段时间中有一个时间窗口。若在这个窗口中再发生一次该信号,则会导致执行该信号的默认动作,可能导致进程的终止)

-=-=-=-

另外,还有其他方法来处理 SIGPIPE 信号:设置 socket 在进行写操作时不产生 SIGPIPE 信号

?


1

2

int set = 1;

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

这样做的好处在于:在某些情况 下我们并不需要一个全局的 SIGPIPE handler 。但是 SO_NOSIGPIPE 不具有可移植性,后续有说明。

-=-=-=-

查阅资料后找到了两个方法:

  • 使用 signal(SIGPIPE, SIG_IGN) 忽略 SIGPIPE 。经实验在 ios7 模拟器上虽然 xcode 还是会捕获 SIGPIPE,但是程序不会崩溃,继续后可以执行。但是在真机上依然会崩溃。
  • 使用 SO_NOSIGPIPE 。经实验在多个 ios 版本下都不再触发 SIGPIPE,完美解决问题。

SO_NOSIGPIPE 在 mac 中存在,可惜在 android 中不存在。请使用 MSG_NOSIGNAL 来代替

?


1

2

3

#if defined(__ANDROID__)

    #define SO_NOSIGPIPE MSG_NOSIGNAL

#endif

在我的系统上

?


1

2

3

4

5

[root@YOYO ~]# uname -a

Linux YOYO 2.6.32-358.el6.x86_64 #1 SMP Fri Feb 22 00:31:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

[root@YOYO ~]#

[root@YOYO ~]# cat /etc/issue

CentOS release 6.4 (Final)

查看 man 手册,可以看到

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

[root@Betty ~]# man 2 send

...

       #include <sys/types.h>

       #include <sys/socket.h>

 

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

...

       MSG_NOSIGNAL (since Linux 2.2)

              Requests not to send SIGPIPE on errors on stream oriented sockets when the other end breaks the connection.  The EPIPE error is still returned.

              自从 Linux 2.2 开始提供对 MSG_NOSIGNAL 的支持。若设置该 flag 则 socket 远端读被关闭时,内核也不会发送 SIGPIPE 信号给当前进程。设置后,仍然可以得到 EPIPE 错误码。

...

       EPIPE  The local end has been shut down on a connection oriented socket.  In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.

              面向连接的 socket 的本地端由于 “Broken Pipe” 关闭时,产生该错误码。该这种情况,如果进程没有设置 MSG_NOSIGNAL 则还会收到 SIGPIPE 信号。

...

====

一下内容取自 glib-2.35.4

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

* @G_FILE_ERROR_PIPE: Broken pipe; there is no process reading from the

 *     other end of a pipe. Every library function that returns this

 *     error code also generates a `SIGPIPE' signal; this signal

 *     terminates the program if not handled or blocked. Thus, your

 *     program will never actually see this code unless it has handled

 *     or blocked `SIGPIPE'.

 *     Broken pipe 表明已经没有进程在 pipe 的远端进行读动作。

 *     每一个库函数都会返回 EPIPE 错误码,且同时产生一个 SIGPIPE 信号;

 *     该信号会终止当前程序,若当前程序没有针对该信号进行处理或者阻塞。

 *     所以,如果你的层序没有针对 SIGPIPE 进行处理或者阻塞动作,那么你的程序

 *     将永远看不到错误码 EPIPE (因为 SIGPIPE 默认会导致程序终止)。

...

 

#ifdef SIGPIPE

  /* There is no portable, thread-safe way to avoid having the process

   * be killed by SIGPIPE when calling send() or sendmsg(), so we are

   * forced to simply ignore the signal process-wide.

   * 当调用 send() 和 sendmsg() 时,不存在可移植,且线程安全的方式来避免进程

   * 被信号 SIGPIPE 杀掉,所以我们强制要求在系统范围内忽略简单的忽略该信号

   */

  signal (SIGPIPE, SIG_IGN);

#endif

...

/* Although we ignore SIGPIPE, gdb will still stop if the app receives

 * one, which can be confusing and annoying. So if possible, we want

 * to suppress the signal entirely.

 * 即使设置了忽略 SIGPIPE 信号,但是 gdb 仍旧会在 app 接收到该信号的时候停止

 * 而这将对调试产生困扰。所以如果可能,最好将该信号完全抹除

 */

#ifdef MSG_NOSIGNAL

#define G_SOCKET_DEFAULT_SEND_FLAGS MSG_NOSIGNAL

#else

#define G_SOCKET_DEFAULT_SEND_FLAGS 0

#endif

下面是 stackoverflow 上的一篇关于 SIGPIPE 的讨论:
http://stackoverflow.com/questions/108183/how-to-prevent-sigpipes-or-handle-them-properly

时间: 2024-09-17 22:30:10

【原创】SIGPIPE 信号处理整理的相关文章

微博营销的时效性很重要

前几天郭美美用蹩脚的英文发了一篇微博,引来众多网友的神级翻译,都是用讽刺调侃的语气表达不满,使得郭美美的单条微博转发将近25万次,笔者借着这个东风 发了一条汇总翻译微博,24小时的时间获得3万7千条转发,成为当日热门转发第一名,很多人都在猜测到底是怎么样获得这么多转发,有的还问笔者到底花了多少钱,这里就简单总结一下具体的操作和传播过程供大家参考. 首先要说明这并不是一次有预谋的事件策划,更没有花钱去找什么大号转发,完全是偶然的灵机一动加上一点点运气.当然笔者认为偶然当中还是有必然,仔细分析过程对

如何正确理解“内容为王,外链为皇”这句话

从seo这个行业兴起以来,"内容为王,外链为皇"这句话就成为seoer的真理,随着搜索引擎算法的不断升级,这个真理还那么管用吗?今天小编就和大家来分享一下这几个月实战的一些想法,可能对,希望能对你有所帮助,如果说的不对,大家就一笑了之.那么怎么理解"内容为王,外链为皇"呢? 首先先说说"内容为王"吧!内容对于一个网站,不管在任何情况下都是最重要的,所以"内容为王"这是永远都不会变的真理.内容是一个网站的核心,是网站的灵魂,拥有

百度百科核心用户分享百科词条创建经验

先简单说说我的百度百科账户基本情况:经验值1460,财富值930,总贡献词条234,通过率85%,复杂版本129,四级,中级编辑,百度百科核心用户!什么是百度百科核心用户呢?百度"百科核心用户"百度的名片定义:百度百科核心用户是从百度百科用户中产生的优秀科友,需具备原创及信息整理能力,了解百科相关规则,能够为百科词条内容建设持续发光发热.那么如何能成为百度百科核心用户呢?1.等级4级以上;2.通过率85%以上;3. 拥有50个以上复杂编辑版本,同时满足以上三个条件的百科的科友,将获得加

医疗网站失败在内容而不是类型

自从百度算法更新医疗站.淘宝客网站这类的网站被大面积的拔毛,这段时间也有很多同学说医疗站不行呀,百度不喜欢了,淘宝客网站走向灭亡了等等,其实不然,闲云认为:医疗网站失败在内容的大量重复而不在于它的类型!6.28之后公司的几个医疗网站全部被k,暂且不说全国,本地区互相竞争的网站也是被k,所剩无几,留下来的也没有了排名,因为地区竞争力很小所以占据百度首页排名的基本都是大站的文章页面,而且正好河北省赶上了几起医疗大事故,医疗竞价也被叫停,无奈之下我们只能忍受风险重新做站!我们先来看看小地区医疗网站的常

iOS 中 AES256 的实现 - 更喜欢 C 实现,OC 封装的风格

iOS 中 AES256 的实现 - 更喜欢 C 实现,OC 封装的风格 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 这段话摘自维基百科: 高级加密标准(Advanced Encryptio

使用MSF批量探测内网MS17-010漏洞

本文讲的是使用MSF批量探测内网MS17-010漏洞,文章不是原创,只是整理下下方法.主要是考虑到很多企业内网中可能还有很多主机存在问题,想方便的探测下可能存在问题的主机,仅需要一个MSF. 第一步:通过msfupdate更新smb_ms17_010.rb模块,或者手动拷贝附件一到msf的模块目录下. Kali linux: /usr/share/metasploit-framework/modules/auxiliary/scanner/smb Windows: C:metasploit-fr

国外域名盘点:中国化域名汇总

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 全球域名一共有多少种?小叶也不知道具体数量.然而,域名种类如此之多,我们是否想过取其精华呢? 在上回撰写的<腾讯被山寨事件 引发的另类域名山寨思维>里,小叶便简单的提了几个有趣的中国化的国外域名,在此,小叶将互联先锋的1108种域名里的一些域名进行了整理.而其中便有着这么一下有趣的并且中国化域名后缀,下面便于与大家一起分享,供准备

微博营销之四两拨千斤 4万转发是怎样炼成的

中介交易 SEO诊断 淘宝客 云主机 技术大厅 前几天郭美美用蹩脚的英文发了一篇微博,引来众多网友的神级翻译,都是用讽刺调侃的语气表 达不满,使得郭美美的单条微博转发将近25万次,我借着这个东风 发了一条汇总翻译微博,24小时的时间获得3万7千条转发,成为当日热门转发第一名,很多人都在猜测到底是怎么样获得这么多转发,有的还问我到底花了多少 钱,这里就简单总结一下具体的操作和传播过程供大家参考. 首先要说明这并不是一次有预谋的事件策划,更没有花钱去找什么大号转发,完全是偶然的灵机一动加上一点点运气

Linux TCP通信出现CLOSE_WAIT后导致服务端进程挂掉

在前文中讲述了Linux服务端TCP通信出现CLOSE_WAIT状态的原因,这篇文章主要通过一个实例演示它个一个"恶劣"影响:直接使服务端进程Down掉. CentOS服务端建立监听端口 1 CentOS服务端建立监听端口 如上图所示,在虚拟机CentOS7服务器(192.168.1.178)中打开一个终端界面,建立8000端口的监听服务(PID:13035).所用代码如下,和上一篇文章中的程序大体一样,只是多了一个SIGPIPE信号处理以及自动回复(Auto response fro