代码托管在 gitos 上,请使用下面的命令获取:

git clone

如果你已经 clone 过这个代码了,请使用 git pull 更新一下。

本次实验是接着上一篇文章的实验继续的,所以大家最好两篇连着看。上一篇文章的地址:​​《连接断开异常(服务器进程终止)》​​。

1. 相关程序

本次实验所使用的程序路径是 ​​unp/program/echo/exception_sigpipe​​。这个程序与之前的 processzombie 并没有什么本质上的不同,几乎完全一样,只不过,我们做了两个修改

  • 捕捉了 SIGPIPE 信号
  • 客户端部分稍稍做了修改,如下:
void doClient(int sockfd) {
int nr, nw;
char buf[4096];

while(fgets(buf, 4096, stdin)) {
// 只是将以前的一行 writen 分成了两次
nw = writen(sockfd, buf, 1);
sleep(1);// 等待 1 秒,保证收到 RST 段
nw = writen(sockfd, buf + 1, strlen(buf + 1));
if (nw < strlen(buf + 1)) puts("short write");

nr = readline(sockfd, buf, 4096);
if (nr == 0) {
puts("peer closed");
break;
}
else if (nr < 0) ERR_EXIT("readline");

write(STDOUT_FILENO, buf, nr);
}
}

这样修改的目的是什么呢?我们的实验步骤其实上上一篇文章完全一样,即让服务器进程终止,客户端再不知道服务器终止的情况下,又向服务器发送数据。

客户端分成 2 次 writen,第一次发送 1 个字节,第二个发送 n - 1 个字节,两次 writen 之间休眠 1 秒。这样做的目的是希望在第二次 writen 前收到对端发来的 RST 段。

2. 实验结果


39-连接断开异常(引发 SIGPIPE)_sigpipe


图1 服务器端,杀死子进程



39-连接断开异常(引发 SIGPIPE)_异常_02


图2 客户端,第二次发送 hehe 收到 SIGPIPE 信号


3. 结果分析

3.1 为什么服务器收到两次 SIGCHLD

很奇怪的是,服务器端在杀死子进程时,我们收到了两个 SIGCHLD 信号,不知道大家有没有注意,在上一篇文章里实际上也是这种情况。因为当你按下 CTRL Z 的时候,会导致父进程和子进程都停止。

而 SIGCHLD 信号产生需要满足以下任意一个条件之一:

  • 子进程结束
  • 子进程停止
  • 子进程恢复执行(man 手册中并没有写这种情况,不知道是不是和内核有关,希望大家进行测试并进行留言)

因此,一次收到 SIGPIPE 是因为子进程停止,另一次是因为子进程终止。

3.2 为什么收到 SIGPIPE

接下来,我们分析为什么客户端会接收到 SIGPIPE。当 flower 服务器结束的时候,sun 客户端第一次 writen 后,服务器会回送 RST 端,接着 sun 等待了 1 秒,这一秒时间,足以让 sun 接收到 RST 段,此时 sun 又执行了一次 writen,于是引发了 SIGPIPE,同时 writen 错误,errno = EPIPE.

​《Unix 网络编程》​​ 是这样解释的:

当一个进程向某个已收到 RST 段的套接字执行写操作时,内核向该进程发送一个 SIGPIPE 信号。

4. 总结

  • 向收到 RST 套接字执行写,会引发 SIGPIPE