服务器遇到Broken Pipe崩溃

==============================第1篇=====================================================

最近产品线中又碰到Broken pipe的问题,之前也碰到过,但分析了下,原因是不同的。

情景一:

在一个网络传输的模块中,由于Broken pipe导致服务程序退出。

分析:经常写网络服务的的人也许知道,一般在启动等过程中由于系统不稳定一般会先忽略一些信号量,或者说捕捉一些信号量并作特殊处理。以防其直接导致程序退出。就比如SIGPIPE。

在socket交互过程中,建立连接后,如果client端意外中断,而此时恰好server端处于socket write过程时,会向主程序发送SIGPIPE信号,此时如果主程序不做特殊处理,系统默认的处理方式为退出进程。对于产生信号,我们可以在产生信号前利用方法 signal(int signum, sighandler_t handler) 设置信号的处理。

情景二:

这是今天刚遇到的,追查起来还挺麻烦的,因为报出Broken pipe错误的程序没有进行网络通信。大致形式类似与:

+ cat temp.data
+ ./XXX.exe
map.sh: line 108: 12915 Broken pipe             cat temp.data
     12916 Killed                  | ./XXX.exe >data.output

这是一个map reduce任务的map中的一段,其中XXX.exe是C++写的一个数据处理程序,没有网络交互。

面对问题的出现,当时的第一反应是怀疑那个节点的磁盘满了导致数据流堵住。

再次进行重试进行复现,结果发现还是出现Broken pipe ,并且是很多节点都这样。由此判断不是磁盘问题。然后查看XXX.exe的相关日志,发现他的日志是不完整的,在加载字典的过程中,只执行了一半便没有任何征兆地中断了。当时就突然想到一个问题,可能是内存不足。于是,调整map reducer的内存限制,再次重试,这次成功执行了。

由此,推断此次Broken pipe 的产生过程是这样的:

XXX.exe的内存寻求比如是2G,而我们设置了map reducer的内存上限为1G,结果在执行XXX.exe时,内存超限被直接干掉了。这时候相当于cat temp.data |./XXX.data 这一过程的cat管道下游的接受方死掉了,于是乎报出管道破裂的报警。

像这种,出现问题的表象与实质原因之间还是有一定距离的,似乎是八竿子打不到一起的。这种case在工作中处理还是挺恶心的。

 

==============================第2篇=====================================================

我写了一个服务器程序, 在Linux下测试时, 总是莫名退出. 最后跟踪到是write调用导致退出. 用gdb执行程序, 退出时提示"Broken pipe".

最后问题确定为, 对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程.

具体的分析可以结合TCP的"四次握手"关闭. TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端的socket是调用了close还是shutdown.

对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.

为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它, 给它设置SIG_IGN信号处理函数:

signal(SIGPIPE, SIG_IGN);

这样, 第二次调用write方法时, 会返回-1, 同时errno置为SIGPIPE. 程序便能知道对端已经关闭.

PS: Linux下的SIGALRM似乎会每1秒钟往后偏移1毫秒, 但Windows下经过测试完全准时, 不差1毫秒.


当然还有其他方法来处理SIGPIPE

     设置当前socket在进行写操作时不产生SIGPIPE。

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

这样做的好处在于:在某些情况 下我们并不需要一个全局的SIGPIPE handler。但是据说这种并不通用,在linux下没有相应的定义—-但我在mac下测试通过。

 

==============================第3篇=====================================================

Broken pipe产生的原因

1)broken pipe的字面意思是“管道破裂”。broken pip的原因是该管道的读端被关闭。

2)broken pipe经常发生socket关闭之后(或者其他的描述符关闭之后)的write操作中。

3)发生broken pipe错误时,进程收到SIGPIPE信号,默认动作是进程终止。

4)broken pipe最直接的意思是:写入端出现的时候,另一端却休息或退出了,因此造成没有及时取走管道中的数据,从而系统异常退出;

==============================第4篇=====================================================

Sokect中close和shutdown的区别

socket的关闭有close 和shutdown两种API,那么他们的区别在哪里呢?


close   ----- 在多进程的情况下,关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这个链接,能读或写这个socket id,直到所有的进程都进行了                            close,才真正关闭这个套接字,但当他真正执行关闭的时候是完全关闭,既不处理发送也不处理接收数据,如果对端发送数据过来会收到RST消息。

shutdown  --  即便在多进程的情况下面,也是直接进行关闭的,破坏了socket 链接,其他进程的链接也会被关闭,但他关闭的时候只关闭一般,即发送数据通道关闭,接收数据还                         是可以的。如果写则可能会收到一个SIGPIPE信号,这个信号可能直到socket buffer被填充了才收到。

 


  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值