当你读取一个关闭的TCP套接字时,会得到一个常规错误,即它要么返回0表示EOF,要么返回-1并在errno
中返回一个错误代码,该错误代码可以使用perror
打印。
然而,当你写一个关闭的TCP套接字时,操作系统会向你的应用程序发送SIGPIPE
信号,如果未被捕获,将终止应用程序。
为什么写关闭的TCP套接字比读取更糟糕?
当你读取一个关闭的TCP套接字时,会得到一个常规错误,即它要么返回0表示EOF,要么返回-1并在errno
中返回一个错误代码,该错误代码可以使用perror
打印。
然而,当你写一个关闭的TCP套接字时,操作系统会向你的应用程序发送SIGPIPE
信号,如果未被捕获,将终止应用程序。
为什么写关闭的TCP套接字比读取更糟糕?
感谢Greg Hewgill帮助我正确地找到答案。
在Unix系统中,套接字和管道都会出现SIGPIPE
的真正原因是过滤器惯用语法/模式。
从管道开始。像grep这样的过滤程序通常写入STDOUT
并从STDIN
读取,这可能会被shell重定向到管道中。例如:
cat someVeryBigFile | grep foo | doSomeThingErrorProne
dup2
系统调用将STDIN
,STDOUT
和STDERR
重定向到适当的管道。由于过滤程序grep
不知道并且没有办法知道它的输出已被重定向,因此如果doSomeThingErrorProne
崩溃,则告诉它停止写入破损的管道的唯一方法是使用信号,因为很少检查对STDOUT
的写入返回值。inetd
服务器代替shell。例如,我假设您可以将grep
转换为通过TCP
套接字运行的网络服务。例如,如果要在TCP
端口8000上拥有grep服务器,则将其添加到/etc/services
中。grep 8000/tcp # grep server
然后将以下内容添加到/etc/inetd.conf
中:
grep stream tcp nowait root /usr/bin/grep grep foo
发送SIGHUP
到inetd
,并使用telnet连接到8000端口。这应该会导致inetd
分叉,将套接字复制到STDIN
、STDOUT
和STDERR
,然后使用foo作为参数执行grep
。如果您开始在telnet中键入行,则grep
将回显包含foo的行。
现在用一个名为ticker
的程序替换telnet,例如将实时股票报价流写入STDOUT
并在STDIN
上获取命令。有人通过telnet连接到8000端口并键入"start java"以获取Sun Microsystems的报价。然后他们起身去吃午饭。telnet莫名其妙地崩溃了。如果没有SIGPIPE
要发送,那么ticker
将永远发送报价,从未知道另一端的进程已经崩溃,并且不必要地浪费系统资源。
write
或send
不能像read
或recv
一样直接返回错误。为什么要用SIGPIPE
来打断应用程序呢?操作系统做出如此极端的响应肯定有更深层次的原因。比如说,如果我有一个套接字刚刚收到了一个RST
,如果我使用read
读取它,我会得到ECONNRESET的-1,那么为什么在写入时不直接得到相同的结果呢?在这两种情况下,我都期望进行协商I/O,而没有得到我期望的结果。 - Robert S. Barnesgrep
程序)。为了使这样的过滤器在输出不再监听时立即终止,SIGPIPE
信号的默认行为被设置为终止程序。如果没有这个功能,过滤器将继续向输出写入数据,直到其输入被耗尽(这可能需要一段时间)。 - Greg HewgillSIGPIPE
的真正原因是,像grep这样的过滤程序通常会写入STDOUT
,而shell可能会将其重定向到一个管道。由于过滤程序不知道也无法知道其输出已经被重定向,所以唯一的方式就是通过信号告诉它停止向破损的管道写入,因为很少有人检查对STDOUT
的写入返回值。与套接字类似的情况是inetd
接受连接,生成服务器并将套接字复制到STDIN
、STDOUT
、STDERR
上! - Robert S. Barnes把socket想象成发送和接收进程之间的一个大数据管道。现在想象一下,这个管道有一个关闭的阀门(socket连接已关闭)。
如果你从socket中读取数据(试图从管道中获取内容),那么尝试读取不存在的数据是没有害处的;你只是无法获得任何数据。实际上,你可能会像你所说的那样收到EOF信号,因为没有更多的数据可以读取了。
然而,写入到这个关闭的连接就不同了。数据将无法通过,你可能会丢失一些重要的通信内容。(如果你试图向关闭阀门的管道中注入水,可能会导致某个地方爆炸,或者至少会产生反向压力将水喷洒到各个地方。)这就是为什么有一个更强大的工具来提醒你这种情况,即SIGPIPE信号。
你总是可以忽略或阻止该信号,但你自己承担风险。
#define SO_NOSIGPIPE 0x1022 /* APPLE: No SIGPIPE on EPIPE */
SIGPIPE
存在是因为很多程序员在写操作时忽略了错误码,这可能会导致进程占用系统资源而实际上并没有完成任何事情?或者换句话说,人们更加细心地检查输入而不是输出,这就是read
和write
之间不对称的原因? - Robert S. BarnesSIGPIPE
之前有过这样的时期吗?由于您说这在某种程度上是用户/程序员不良行为的结果,是否曾经有一个 Unix 版本在向关闭的管道写入时返回错误,然后将其更改为返回信号,或者 SIGPIPE
从一开始就存在,以预防不良行为? - Robert S. Barnes