【问题标题】:Why is writing a closed TCP socket worse than reading one?为什么写一个封闭的 TCP 套接字比读一个更糟糕?
【发布时间】:2011-01-14 01:18:35
【问题描述】:

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

但是,当您编写一个关闭的 TCP 套接字时,操作系统会向您的应用发送 SIGPIPE,如果没有被捕获,它将终止该应用。

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

【问题讨论】:

  • 这里还发生了一些相当微妙的事情:TCP 连接可以是半关闭的,这意味着一侧已经关闭了套接字(发送了一个 FIN 数据包),但另一侧仍有数据要发送。如果您要在这个级别上扎根,请阅读:superuser.com/questions/298919/…

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


【解决方案1】:

+1 致Greg Hewgill 引导我的思考过程朝着正确的方向寻找答案。

SIGPIPE 在套接字和管道中的真正原因是过滤器习惯用法/模式适用于 Unix 系统中的典型 I/O。

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

cat someVeryBigFile | grep foo | doSomeThingErrorProne

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

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

带有套接字的类似物是inetd 服务器代替了外壳。

作为一个例子,我假设您可以将grep 转换为通过TCP 套接字运行的网络服务。例如inetd,如果你想在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 分叉,将套接字复制到STDINSTDOUTSTDERR,然后使用 foo 作为参数执行grep。如果您开始在 telnet 中输入行,grep 将回显那些包含 foo 的行。

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

【讨论】:

    【解决方案2】:

    通常,如果您正在写入套接字,您会期望另一端正在监听。这有点像一个电话 - 如果你正在说话,你不会期望对方简单地挂断电话。

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

    【讨论】:

    • 但这并不能真正告诉我为什么 writesend 不能像 readrecv 一样直接返回错误。为什么要像这样用SIGPIPE 打这个应用程序?操作系统如此极端的反应一定有更深层次的原因。假设我有一个刚刚收到RST 的套接字。如果我 read 它使用 ECONNRESET 得到 -1,为什么我写的时候不得到同样的结果呢?在这两种情况下,我都希望参与双方同意的 I/O,但没有达到我的预期。
    • @Robert:过去,Unix 上管道输入和输出的典型用例是“过滤”程序,它从输入管道读取并写入输出管道(grep 程序就是这样的)一个例子)。为了使这样的过滤器在输出不再监听时立即终止,SIGPIPE 信号默认行为被设置为终止程序。如果没有这个,过滤器将继续写入输出,直到其输入耗尽(可能需要一段时间)。
    • 告诉我这听起来是否正确:SIGPIPE 的真正原因是像 grep 这样的过滤程序通常会写入 STDOUT,这可能会被 shell 重定向到管道。由于过滤器程序不知道并且无法知道它的输出已被重定向,因此告诉它停止写入损坏管道的唯一方法是使用信号,因为很少检查写入STDOUT 的返回值.带有套接字的模拟将是 inetd 接受连接,生成服务器和 dup2 将套接字连接到 STDIN,STDOUT,STDERR!
    • @Robert:是的,听起来你已经掌握了。
    【解决方案3】:

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

    如果您正在从套接字读取数据(试图从管道中取出某些内容),那么尝试读取不存在的内容并没有什么坏处;你只是不会得到任何数据。事实上,正如你所说,你可能会得到一个 EOF,这是正确的,因为没有更多的数据要读取。

    但是,写入到这个关闭的连接是另一回事。数据不会通过,你可能会在地板上丢掉一些重要的通信。 (您不能将水从阀门关闭的管道中送出;如果您尝试,某处可能会爆裂,或者,至少,背压会将水喷到整个地方。)这就是为什么有更强大的提醒您这种情况的工具,即 SIGPIPE 信号。

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

    【讨论】:

      【解决方案4】:

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

      所以,有理由问为什么管道会这样。 Greg Hewgill 的回答对情况进行了总结。

      另一种看待它的方式是 - 什么是替代方案?没有写入器的管道上的“read()”是否应该发出 SIGPIPE 信号?当然,SIGPIPE 的含义必须从“在没有人阅读的情况下写入管道”改变,但这很简单。没有特别的理由认为它会更好。 EOF 指示(读取零字节;读取零字节)是对管道状态的完美描述,因此读取行为良好。

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

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

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

      【讨论】:

      • 所以基本上你说SIGPIPE 的存在是因为许多程序员在写入的情况下忽略了错误代码,这可能导致进程在实际上没有完成任何事情时占用系统资源?或者换一种说法,人们检查输入比检查输出更加细致,这就是readwrite 不对称的原因?
      • @Robert:是的,基本上。人们倾向于在输出设备不会消失或空间不足的假设下编写代码。当输出为管道且接收程序在输出结束前停止读取时,确保写入程序注意是很重要的。这是一种简单的机制,使程序更易于编写。
      • 那么有没有早于SIGPIPE 的时间?既然您说这在一定程度上是用户/程序员不良行为的结果,那么是否曾经有一个 Unix 版本在写入封闭管道时返回错误,然后他们将其更改为返回信号,或者是 @987654327 @ 从一开始就预料到不良行为?
      • @Robert S. Barnes:管道很早就被添加到了 Unix。根据 Dennis Ritchie 在“UNIX 分时系统的演变”中的说法,管道在 UNIX 的 PDP-7 版本上不可用,并在 1972 年(在第一个版本后 2-3 年)被添加到 PDP-11 版本中UNIX)。 (参考:“UNIX® SYSTEM: Readings and Applications, Volume II”,1987 年。这是 AT&T (Bell) 杂志致力于 Unix 的第二版,其中包含一些有趣的内容。它由 Prentice-Hall 出版为 ISBN 0 -13-939845-7 - amazon.com/Unix-System-Readings-Applications-UNIX-R/dp/…)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-05
      • 1970-01-01
      • 2022-12-22
      • 2010-09-10
      • 1970-01-01
      相关资源
      最近更新 更多