当我向一个阻塞套接字写入数据时,如果对方读取速度较慢会发生什么?

28
假设我使用阻塞式套接字快速写入数据 [我已将所有数据存储在内存中]。另外,假设对方会非常缓慢地读取数据[比如在每次读取之间睡眠1秒钟]。
在这种情况下,写入方的预期行为是什么?写操作会阻塞直到对方读取足够的数据,还是会返回错误,例如连接重置?

1
一旦您方的缓冲区足够满,它将会阻塞。 - wildplasser
1
@wildplasser 这也是我认为会发生的情况,然而我正在使用 poco 的 c++ 库来实现,但写操作会抛出一个连接被对等方重置的异常... - Aviad Rozenhek
该库可以随意操作,这可能与套接字的状态有关,也可能无关。 - wildplasser
@wildplasser 'Connection reset by peer' 是源自操作系统的TCP条件。任何报告这种情况但实际上并非如此的库都应该立即被淘汰。我从未听说过这样的情况。 - user207421
如果你真正想问的是为什么会出现连接重置的情况,通常的原因是你向已被对等方关闭的连接写入了数据。 - user207421
@EJP:我知道它的意思。我想说的是,“连接重置”错误与OP中的问题(阻塞读取是否会阻塞?)完全无关。该库最多是不相关的,最坏的情况下是有缺陷的。 - wildplasser
2个回答

42
对于阻塞套接字,send()调用将会一直阻塞,直到所有数据都被拷贝到该连接的网络堆栈缓冲区中。此时不需要等待另一侧进行数据接收。该缓冲区的大小取决于具体实现。
当远端确认接收到数据后,数据就会从缓冲区清除。这是一个操作系统级别的事件,不依赖于远端应用程序是否真正读取了数据。该缓冲区的大小也是由具体实现决定的。
当远端缓冲区已满时,它会告诉本地堆栈停止发送。当远端缓冲区中的数据被清除后(通过远端应用程序进行读取),远端系统会通知本地系统发送更多的数据。
在这两种情况下,小型系统(如嵌入式系统)的缓冲区可能只有几KB或更小,而现代服务器的缓冲区可能会有几MB甚至更大。
一旦本地缓冲区中有空间,就会拷贝来自send()调用的更多数据。当所有这些数据都被拷贝后,该调用将返回。
除非连接真正重置,否则您不会收到“连接重置”错误(来自操作系统 - 库可以执行任何操作)。
因此...... 在发送了本地和远端缓冲区大小之和的数据后,远程应用程序读取数据的速度就不再重要。在那之后,您只能以远端一侧recv()的速度进行send()调用。

很好的答案。考虑到远程(客户端)和本地(服务器),如果远程缓冲区已满,它是否仍然能够将数据发送到本地?读取和写入缓冲区是共享的还是分开的? - WorM
传入和传出缓冲区(“窗口”)是完全独立的。一个方向上的停顿不会妨碍另一个方向上的通信。 - Brian White
这也意味着,如果发送端处理能力不足,进程可能会被“阻塞”,在 TCP 跟踪中看不到任何信息,因为没有数据包发出。 - cslotty
@cslotty,如果是这样的话,您应该会偶尔看到“零窗口探测”,即客户端向服务器发送单字节数据包以检查是否已释放空间,但此类通知可能已丢失。 - Brian White

8

输出(发送)缓冲区被填满直到它变满,send()会阻塞,直到缓冲区被释放足够多的空间来排队数据包。

正如send手册页面所说:

当消息无法适应套接字的发送缓冲区时,send()通常会阻塞,除非套接字已被置于非阻塞I/O模式。

请参考:http://manpages.ubuntu.com/manpages/lucid/man2/send.2.html


1
当发送缓冲区很大(比如1 MB),但接收缓冲区非常小(比如10个字节),并且写入器填充了发送缓冲区,而读取器没有读取任何内容时,会发生什么? - Aviad Rozenhek
2
操作系统仅在确认数据包已传送后才从发送缓冲区中删除数据包。如果接收方从未通知数据包已正确发送(在接收PUSH DATA后回复ACK),则发送方缓冲区永远不会被释放。 - Davide Berra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接