为什么写一个封闭的TCP套接字比读一个更差?

时间:2010-02-07 08:12:27

标签: language-agnostic sockets network-programming network-protocols

当你读取一个关闭的TCP套接字时,你会得到一个常规错误,即它返回0表示EOF或-1,以及errno中的错误代码,可以用perror打印。

但是,当您编写一个已关闭的TCP套接字时,操作系统会将SIGPIPE发送到您的应用程序,如果未捕获该应用程序将终止该应用程序。

为什么写封闭的TCP套接字比读取它更糟糕?

4 个答案:

答案 0 :(得分:12)

+1到Greg Hewgill以正确的方向引导我的思维过程以找到答案。

套接字和管道中SIGPIPE的真正原因是过滤器习语/模式,它适用于Unix系统中的典型I / O.

从管道开始。像grep这样的过滤程序通常会写入STDOUT并从STDIN读取,这可能会被shell重定向到管道。例如:

cat someVeryBigFile | grep foo | doSomeThingErrorProne

shell在分叉然后执行这些程序时可能会使用dup2系统调用将STDINSTDOUTSTDERR重定向到相应的管道。

由于过滤程序grep不知道并且无法知道它的输出已被重定向,因此如果doSomeThingErrorProne崩溃则告诉它停止写入损坏的管道的唯一方法是带有信号,因为如果检查到STDOUT的写入的返回值很少。

带有套接字的模拟将是代替shell的inetd服务器。

作为示例,我假设您可以将grep转换为在TCP套接字上运行的网络服务。例如,如果您希望inetd端口8000上有grep服务器,请使用TCP,然后将其添加到/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分叉,将套接字复制到STDINSTDOUTSTDERR,然后以foo作为参数执行grep。如果你开始在telnet中输入行grep将回显那些包含foo的行。

现在用名为ticker的程序替换telnet,例如将实时股票报价流写入STDOUT并在STDIN上获取命令。有人telnet到端口8000并输入“start java”来获取Sun Microsystems的报价。然后他们起床去吃午饭。 telnet莫名其妙地崩溃了。如果没有SIGPIPE发送,那么ticker会永远发送引号,永远不会知道另一端的进程已经崩溃,并且不必要地浪费系统资源。

答案 1 :(得分:10)

通常如果你正在写一个套接字,你会期望另一端正在收听。这有点像电话 - 如果你说话,你不会指望对方只是挂断电话。

如果您正在从套接字读取,那么您期望另一端要么(a)向您发送信息,要么(b)关闭套接字。如果您刚刚向另一端发送了类似QUIT命令的内容,则会发生情况(b)。

答案 2 :(得分:7)

将套接字视为发送和接收进程之间的大数据管道。现在假设管道有一个关闭的阀门(套接字连接已关闭)。

如果你正在从插座中读取(试图从管道中取出某些东西),那么尝试阅读不存在的东西是没有害处的;你不会得到任何数据。事实上,正如你所说,你可以获得一个正确的EOF,因为没有更多的数据可供阅读。

然而,到这个封闭的连接是另一回事。数据无法通过,您可能最终放弃了一些重要的通信。 (你不能用一个关闭的阀门将水送到管道;如果你尝试,某些东西可能会在某处爆裂,或者至少,背压会在整个地方喷水。)这就是为什么有一个更强大的工具提醒您这种情况,即SIGPIPE信号。

您可以随时忽略或阻止信号,但这样做需要您自担风险。

答案 3 :(得分:3)

我认为答案的很大一部分是'因此套接字的行为与传统的Unix(匿名)管道相似。那些也表现出相同的行为 - 见证信号的名称。

那么,那么问管道为什么会这样做是合理的。 Greg Hewgill的回答总结了这种情况。

另一种看待它的方式是 - 替代方案是什么?如果没有编写器的管道上的'read()'是否应该发出SIGPIPE信号?当然,SIGPIPE的含义必须从'写在管道上而不是读它'上改变,但这是微不足道的。没有特别的理由认为它会更好; EOF指示(读取零字节;读取零字节)是管道状态的完美描述,因此读取行为很好。

'write()'怎么样?好吧,一个选项是返回写入的字节数 - 零。但这不是一个好主意;它意味着代码应该再次尝试,并且可能会发送更多的字节,但情况并非如此。另一种选择是错误 - write()返回-1并设置适当的错误。目前尚不清楚是否有一个。 EINVAL或EBADF都是不准确的:文件描述符是正确的并且在此端打开(并且应该在写入失败后关闭);没有什么可读的。 EPIPE意味着'破碎的PIPE';因此,有一个关于“这是一个套接字,而不是一个管道”的警告,这将是适当的错误。如果忽略SIGPIPE,可能是errno返回。这样做是可行的 - 只需在管道损坏时返回适当的错误(并且永远不会发送信号)。然而,一个经验事实是,许多程序不会过多地关注它们的输出位置,并且如果你将一个读取数千兆字节文件的命令输入到在前20 KB之后退出的进程,但它没有注意其写入的状态,那么它将花费很长时间才能完成,并且会在这样做的同时浪费机器的努力,而通过发送它不忽略的信号,它将很快停止 - 这绝对有利。如果你需要,你可以得到错误。因此,信号发送对管道环境中的o / s有好处;和套接字相当密切地模拟管道。

有趣的是:在检查SIGPIPE的消息时,我找到了套接字选项:

#define SO_NOSIGPIPE 0x1022 /* APPLE: No SIGPIPE on EPIPE */