为啥写入此套接字会丢弃读取缓冲区(这真的是发生了啥)?
Posted
技术标签:
【中文标题】为啥写入此套接字会丢弃读取缓冲区(这真的是发生了啥)?【英文标题】:Why does writing to this socket discard the read buffer (and is that really what's happening)?为什么写入此套接字会丢弃读取缓冲区(这真的是发生了什么)? 【发布时间】:2015-07-07 20:19:36 【问题描述】:考虑以下 sn-p:
QTcpServer server;
server.listen(QHostAddress::LocalHost);
QTcpSocket clientSocket;
clientSocket.connectToHost(server.serverAddress(), server.serverPort());
// ...wait for connection to succeed...
QTcpSocket *serverSocket = server.nextPendingConnection();
serverSocket->write("test");
serverSocket->close();
这个 sn-p 的目标是通过创建一个 QTcpServer
来创建两个连接的套接字,该 QTcpServer
侦听传入连接并与另一个 QTcpSocket
连接到它。建立连接后,数据将写入其中一个套接字并关闭。
QAbstractSocket::close()
invokes disconnectFromHost()
documentation for QAbstractSocket::disconnectFromHost()
明确指出:
如果有等待写入的未决数据,QAbstractSocket 将进入 ClosingState 并等待直到所有数据都已写入。
下一个 sn-p 短暂进入事件循环处理挂起的事件,然后尝试从另一个套接字读取字符串:
QCoreApplication::processEvents();
qDebug() << clientSocket.readAll();
这会打印出"test"
,一切都很好。或者也许不是。如果我通过预先调用write()
来修改之前的sn-p,则无法再从套接字读取数据:
clientSocket.write("test");
QCoreApplication::processEvents();
qDebug() << clientSocket.readAll();
这将打印一个空字符串 (""
),而不是上一次运行的预期值。为什么socket这次不能读取值了?
注意:此仅在 Windows 平台上体现。 Qt 的 Linux 和 Mac OS X 版本都没有表现出这种行为,而是在两种情况下都打印预期值。
第二点:如果你想玩代码,这里有一个工作要点:https://gist.github.com/nathan-osman/ee6116d120903db84384
第三点:这是 Wireshark 捕获 TCP 交换的屏幕截图:
【问题讨论】:
我的猜测:由于套接字的另一端已关闭,我希望尝试写入它会使套接字处于错误状态,并可能导致它自动关闭。之后是否仍然可以从套接字读取数据可能取决于实现。 @HarryJohnston 第一次写入和关闭操作直到QCoreApplication::processEvents();
行才发生(我用调试器验证了这一点)。不过,它们可能仍然是乱序处理的。
如果不能保证排序,它可能仅在 UNIX 上工作,因为 Qt 恰好选择在处理 serverSocket.close() 之前处理 clientSocket.write()。但这也可能是由于底层套接字实现的差异。
@HarryJohnston 我添加了 TCP 交换的屏幕截图,以防有助于阐明事件的顺序。第一个write()
立即得到确认,然后在客户端套接字写入任何内容之前发送并确认FIN
数据包。
这与 Linux / MacOSX 上的事件序列相比如何?
【参考方案1】:
我对 Qt 不是很熟悉,但是在 Windows 上,没有 Fork。您正在尝试在同一线程上适应并行任务,但您应该让客户端在与服务器不同的线程上运行。
【讨论】:
OP 也没有在其他平台上使用 fork()。 我从未在 Linux 上工作过,也许 API 正在这样做? Windows 支持异步 I/O,我希望 Qt 能够利用它,因此您不一定需要多个线程。【参考方案2】:您正在以同步方式编写异步代码,但无法确保您等待足够长的时间让事情发生。调用processEvents
仅仅意味着“做任何当前可以做的工作”而不是“做工作和/或等待工作,直到没有更多的工作可能到达”。你把它当作后者,但它的意思是前者。
最简单的解决方法是不要这样做。
将槽或函子连接到相关的套接字信号,并将控制权返回给事件循环。永远不要使用waitForXxx
函数,它们根本没有被充分指定以允许在所有情况下使用。不要介意重新进入事件循环时将面临的可怕的重入问题。
如果您的代码运行的线程中没有事件循环,请自行启动:
QEventLoop loop;
...
connect(serverSocket, &QAbstractSocket::disconnected,
&loop, &QEventLoop::quit);
loop.exec(); // this will keep executing until the socked is disconnected
【讨论】:
好点。我有一个例子here 虽然它使用QTRY_COMPARE
而不是processEvents()
,但它仍然表现出相同的行为。 QTRY_COMPARE()
创建一个事件循环并运行它,直到两个值相等或达到超时。
@NathanOsman 尝试完全不关闭发送套接字,看看是否接收到数据。如果是这样,那么在 Windows 上可能存在某个地方的实现错误或平台限制。不过,您确实需要在您的问题中提供一个完整的、独立的、单文件的测试用例。
但我做到了。链接就在问题的底部。以上是关于为啥写入此套接字会丢弃读取缓冲区(这真的是发生了啥)?的主要内容,如果未能解决你的问题,请参考以下文章