尽管远程套接字已关闭,但 NetworkStream.Write and Flush 仍成功



我遇到一个问题,当我尝试将数据写入远程TCP服务器时,连接被切断,但我无法检测到何时发生这种情况。

我目前的假设是WriteAsync正在缓冲数据,但是我也假设FlushAsync应该强制它通过线路(并导致IOException)。

我尝试这样做的真正原因是我需要做一些低级 HTTP 流量,并且我想在发送请求正文之前验证 HTTP 标头是否正确发送。

我有一个简单的单元测试来说明我面临的问题。

public sealed class SocketDisconnectTests
{
[Fact]
public async Task ShouldNotWriteIfDisconnected()
{
var listener = new TcpListener(IPAddress.IPv6Any, 0);
listener.Server.SetSocketOption(
SocketOptionLevel.IPv6, 
SocketOptionName.IPv6Only, 
false);
listener.Start();
try
{
// do not await here, let it happen asynchronously
var readAndCloseTask = ReadAndCloseAsync(listener);
using (var clientSocket = new Socket(
SocketType.Stream, 
ProtocolType.Tcp))
{
clientSocket.NoDelay = true;
await clientSocket.ConnectAsync(
"localhost", 
((IPEndPoint) listener.LocalEndpoint).Port);
using (var clientStream = new NetworkStream(
clientSocket, 
true))
{
var buf = new byte[] {1, 2, 3, 4};
await clientStream.WriteAsync(buf, 0, buf.Length);
await clientStream.ReadAsync(buf, 0, buf.Length);
Assert.Equal(4, buf[0]);
Assert.Equal(3, buf[1]);
Assert.Equal(2, buf[2]);
Assert.Equal(1, buf[3]);
// ensure remote closed the connection
await readAndCloseTask;
// this assertion always fails as WriteAsync and 
// FlushAsync complete without exception
await Assert.ThrowsAsync<IOException>(async () =>
{
await clientStream.WriteAsync(buf, 0, buf.Length);
await clientStream.FlushAsync();
});
}
}
}
finally
{
listener.Stop();
}
}
private static async Task ReadAndCloseAsync(TcpListener listener)
{
using (var socket = await listener.AcceptSocketAsync())
{
socket.NoDelay = true;
using (var stream = new NetworkStream(socket, true))
{
var buffer = new byte[4];
await stream.ReadAsync(buffer, 0, buffer.Length);
var reverse = buffer.Reverse().ToArray();
await stream.WriteAsync(reverse, 0, reverse.Length);
}
}
}
}

当你使用socket.NoDelay时,你正在关闭Nagle算法,否则该算法将确保/验证数据是否被接收。

通过设置socket.NoDelay,您说您不需要收件人在收到数据后会返回的 ACK。如果套接字被告知不要查找该 ACK,为什么它会报告缺少 ACK?

编辑

根据已经应用的调整,socket.NoDelay没有效果。根据 OP 中的注释,执行 Web 连接不需要显式尝试管理 TCP 级连接池,WebClient并且HttpWebRequest两者都内置了连接管理器。对于HttpWebRequest,可以通过将KeepAlive属性设置为false来关闭连接池(默认情况下处于打开状态)。

HttpClient也使用类似的连接池,因此不需要滚动自己的连接池。

鉴于连接池已经处理完毕,确保服务器在发送正文之前接收标头信息的意图最好通过简单地发出 2 个 Web 请求来完成,一个具有基本的标头详细信息,另一个在第一个请求成功后使用主体。不需要在TCP级别工作。

最新更新