发送Linux C中的ECONNRESET

use*_*894 3 c sockets linux econnreset

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

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

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

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

Ste*_*ich 7

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

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

  • EPIPE:客户端发送第一个字节.服务器读取字节并关闭连接后,客户端将发送更多数据,然后再发送一些数据.最新的发送调用将触发EPIPE/SIGPIPE.
  • ECONNRESET:客户端首先发送多个字节.服务器将读取单个字节并使用套接字接收缓冲区中的更多字节关闭连接.这将触发来自服务器的连接RST数据包,并在下次发送时客户端将收到ECONNRESET.

  • @user3563894:客户不具备相关知识。不同之处在于,在 ECONNRESET 的情况下,对等方仅发送 RST(硬关闭套接字缓冲区内的数据),而在 EPIPE 的情况下,首先发送 FIN(正常关闭)。仅在对等方收到更多数据后才发送 RST。这种差异(RST 与 FIN 后跟 RST)可以在客户端看到。 (2认同)

Mec*_*cki 6

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

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

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

  1. 一方只能向另一方发出信号表示它已完成发送,但表示整个连接已完成的唯一方法是发送RST. 因此,如果一侧想要关闭连接并希望优雅地关闭它,它会首先发送一个FIN信号,表示它不会再发送新数据,然后给另一侧一些时间停止发送数据,允许进行中数据通过并最终发送一个FIN。但是,如果对方不想停下来,继续发送和发送呢?这种行为是合法的,因为 aFIN并不意味着连接需要关闭,它只意味着一侧完成。结果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));
Run Code Online (Sandbox Code Playgroud)

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

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