6

根据Unix 网络编程,当一个套接字向关闭的套接字写入两次(在一个 FIN 数据包之后)时,它在第一次成功发送,但从另一台主机接收到一个 RST 数据包。由于主机收到 RST,因此套接字被销毁。因此在第二次写入时,接收到 SIGPIPE 信号,并返回 EPIPE 错误。

但是,在发送手册页中可以返回 ECONNRESET,这意味着收到了一个 RST 数据包。当它返回 ECONNRESET - 没有信号返回。

ECONNRESET在哪些情况下可以退回?为什么在这种情况下没有 SIGPIPE 信号?

注意:我在这里检查了类似的问题。但是,当我在我的 linux 计算机上运行时,发送返回 EPIPE 错误,而不是 ECONNRESET。

4

2 回答 2

12

如果对等方在套接字缓冲区中仍有未处理的数据时关闭了连接,它将返回一个 RST 数据包。这将导致在套接字上设置一个标志,并且下一次发送将返回 ECONNRESET 作为结果。如果连接被对等方关闭且没有未处理的数据,则在发送时返回 EPIPE(或触发 SIGPIPE)。在这两种情况下,本地套接字仍然打开(即文件描述符有效),但底层连接已关闭。

示例:想象一个读取单个字节然后关闭连接的服务器:

  • EPIPE:客户端发送第一个字节。在服务器读取字节并关闭连接后,客户端将发送更多数据,然后再发送一些数据。最新的发送调用将触发 EPIPE/SIGPIPE。
  • ECONNRESET:客户端首先发送多于一个字节。服务器将读取一个字节并关闭与套接字接收缓冲区中更多字节的连接。这将触发来自服务器的连接 RST 数据包,并且在下一次发送时,客户端将收到 ECONNRESET。
于 2015-10-10T12:21:23.467 回答
7

TCP 连接可以看作是两个端点之间的两条数据管道。一条数据流水线用于从 A 向 B 发送数据,一条数据流水线用于从 B 向 A 发送数据。这两条流水线属于一个连接,但它们不会相互影响。在一个管道上发送数据对在另一管道上发送的数据没有影响。如果一个管道上的数据是对先前在另一个管道上发送的数据的回复数据,这只有您的应用程序会知道,TCP 对此一无所知。TCP 的任务是确保数据可靠地从管道的一端传输到另一端,并且尽可能快,这就是 TCP 所关心的。

FIN一旦一方完成发送数据,它就会通过向另一方发送一个设置了标志的数据包来告诉另一方它已完成。发送FIN标志的意思是“我已经发送了所有我想发送给你的数据,所以我的发送管道现在关闭了”。您可以通过调用在代码中有意触发它shutdown(socketfd, SHUT_WR)。如果对方随后调用recv()套接字,它不会收到错误,但接收会说它读取零字节,这意味着“流结束”。流结束不是错误,它只意味着不再有数据到达那里,无论您多久调用recv()该套接字。

当然,这并不影响其他管道,所以当A -> B关闭时,B -> A仍然可以使用。即使您关闭了发送管道,您仍然可以从该套接字接收。但是,在某些时候,B 也将完成发送数据并传输 a FIN。一旦两个管道都关闭了,整个连接就关闭了,这将是一个正常的关闭,因为双方都能够发送他们想要发送的所有数据并且没有数据应该丢失,因为只要有传输中未经确认的数据,对方不会说它已经完成,而是等待该数据首先可靠地传输。

或者,有一个RST标志会立即关闭整个连接,无论对方是否已完成发送,也无论是否有未确认的数据在传输中,因此RST很有可能导致数据丢失。由于这是一种可能需要特殊处理的特殊情况,因此程序员知道是否是这种情况会很有用,这就是存在两个错误的原因:

EPIPE- 您无法通过该管道发送,因为该管道不再有效。但是,您在损坏之前发送的所有数据仍然可靠地传递,您只是无法发送任何新数据。

ECONNRESET- 您的管道坏了,可能是您之前尝试发送的数据在传输过程中丢失了。如果这是一个问题,你最好以某种方式处理它。

但是这两个错误并没有一对一地映射到FINandRST标志。如果您在系统认为没有数据丢失风险的情况下收到RST通知,则没有理由无缘无故地让您绕弯子。RST因此,如果您之前发送的所有数据都被确认正确接收,然后当您尝试发送新数据时连接被关闭,则不会丢失任何数据。这包括您尝试发送的当前数据,因为该数据没有丢失,从未在途中发送,这是不同的,因为您仍然拥有它,而您之前发送的数据可能不再存在。如果您的汽车在公路旅行中抛锚了,那么这与您仍然在家的情况完全不同,因为您的汽车发动机甚至无法启动。所以最终是你的系统决定一个RST触发 aECONNRESET或 a EPIPE

好的,但是为什么对方首先要给你发一个RST呢?为什么不总是以 结束FIN?嗯,有几个原因,但最突出的两个是:

  1. 一方只能向另一方发出它已完成发送的信号,但发出整个连接已完成的唯一方法是发送一个RST. 所以如果一方想要关闭一个连接并且它想要优雅地关闭它,它会首先发送一个FIN信号,表示它不会再发送新数据,然后给另一方一些时间来停止发送数据,允许在飞行中要传递的数据并最终发送 a FIN。但是,如果对方不想停下来继续发送又发送怎么办?这种行为是合法的,因为这FIN并不意味着连接需要关闭,它只意味着一侧已经完成。结果是FIN紧随其后的是RST最终关闭该连接。这可能会导致飞行中的数据丢失,也可能不会,只有遗嘱的接收者RST才能肯定地知道,好像数据丢失了,它一定是站在他这边的,因为发送者RST肯定不会再发送任何数据了之后FIN。对于recv()呼叫,这RST没有任何效果,因为FIN之前有一个信号“流结束”,因此recv()将报告已读取零字节。

  2. 一侧应关闭连接,但仍有未发送的数据。理想情况下,它会等到所有未发送的数据都发送完毕,然后发送一个FIN,但是,它允许等待的时间是有限的,并且在该时间过去之后,仍然有未发送的数据。在那种情况下,它不能发送 a FIN,因为那FIN将是一个谎言。它会告诉对方“嘿,我发送了我想发送的所有数据”,但这不是真的。本来应该发送的数据,但由于要求立即关闭,因此必须丢弃该数据,结果,本方将直接发送RST. 这是否RST触发ECONNRESET呼叫send()再次取决于事实,如果RST是否有未发送的数据。ECONNRESET但是,它肯定会在下一次调用时触发错误recv(),告诉程序“对方实际上想向您发送更多数据,但它不能,因此其中一些数据丢失了”,因为这可能又是一个以某种方式处理的情况,因为您收到的数据肯定是不完整的,这是您应该注意的事情。

如果你想强制一个套接字总是直接关闭RST而不是FIN/FINFIN/ RST,你可以将 Linger 时间设置为零。

struct linger l = { .l_onoff = 1, .l_linger = 0 };
setsockopt(socketfd, SOL_SOCKET, SO_LINGER, &l, sizeof(l));

现在套接字必须立即关闭并且没有任何延迟,无论多么少,立即关闭 TCP 套接字的唯一方法是发送一个RST. 有些人认为“为什么要启用它并将时间设置为零?为什么不只是禁用它呢? ”但禁用具有不同的含义。

逗留时间是close()调用可能会阻塞以执行挂起的发送操作以优雅地关闭套接字的时间。如果启用 ( .l_onoff != 0),调用close()可能会阻塞长达.l_linger几秒钟。如果您将时间设置为零,它可能根本不会阻塞,因此会立即终止 ( RST)。但是,如果您禁用它,则close()也永远不会阻塞,但系统可能仍会在关闭时徘徊,但这种徘徊发生在后台,因此您的进程不会再注意到它,因此也无法知道套接字何时真正关闭,因为socketfd即使内核中的底层套接字仍然存在,它也会立即失效。

于 2019-06-25T20:55:38.183 回答