在关闭之前等待 NetworkStream 完成发送数据
Wait for NetworkStream to Finish Sending Data before Closing It
我正在编写一个 Minecraft Classic 服务器,但在实施踢球数据包时遇到了一些问题。服务器应该发送踢球数据包,然后自行处理。主要问题是因为在“发送”之后写入网络流 returns(这是一个超级模糊的定义),玩家会话将尝试自行处理它的 tcp 客户端,可能会中断流踢数据包的数据到客户端 - 这正是目前正在发生的事情。
我想提一下,不可能将 the protocol specifications 更改为要求在应用程序端进行某种确认。
这就是数据包的发送方式。通常没有任何问题,但是 disconnecting/disposing 发送数据包后会中断网络流上的数据流。
public bool SendPacket(Packet packet)
{
try
{
packet.Send(networkStream); //this simply writes stuff to the network stream
return true;
}
catch
{
return false;
}
}
会话是这样处理的:
public void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (currentWorld != null)
currentWorld.LeaveWorld(this);
if (account != null)
server.AccountManager.Logout(account);
networkStream.Close();
client.Close();
Disconnected = true;
}
disposed = true;
}
}
我的临时解决方案是发送kick数据包后等待100ms:
public void Kick(string reason)
{
SendPacket(new DisconnectPlayerPacket(reason));
Thread.Sleep(100);
Dispose();
}
它适用于一些测试用例,但并非始终如此。客户端没有收到所有数据。我知道这一点是因为我已经调试了 ClassiCube 客户端(一个 Minecraft Classic 客户端),我知道它在 100% 的时间里都能正常工作。
无论哪种方式等待都不是可行的解决方案,因为我的服务器会话是在单个线程中处理的 - 这在大多数情况下都有效,因为 Minecraft Classic 数据包相对较短且易于处理。但是,每等待一秒,就会浪费一秒,因为无法处理其他数据包。
我想要一些方法来指示 tcp 客户端是否已完成发送数据 - 在这种情况下,这意味着客户端已成功接收到 kick 数据包。本质上,我想要 dispose 方法,本质上是在关闭连接之前等待接收到最后一个数据包。我有点了解 TCP 协议,我知道客户端应该在之后发送一个 ACK 信号,但据我所知,TcpClient 将这些东西抽象出来。
完整的 github 回购 is here for reference, but please note that all of this mostly takes place in PlayerSession.cs。
总之,如何判断网络流是否已发送完所有数据?
当您调用NetworkStream.Write
时,您正在排队发送数据。 OS 会在幕后为您发送。要知道对方是否真的收到并处理了您的数据,唯一的方法就是让他们向您发送某种确认数据包。
当您调用 NetworkStream.Dispose()
时,它会做两件事。首先,它会关闭两个方向的套接字,如果一切顺利,最终会导致远程方在收到您的所有数据后看到 EOF。
其次,它会关闭您的套接字句柄。但是,这只会关闭 user-mode 句柄。在幕后,OS 将使套接字保持活动状态以刷新数据。
您可以通过设置套接字的延迟选项来稍微控制这里的行为,它控制超时以及 Close()
是否会在等待超时时阻塞。
这种行为有时是合适的,但许多网络协议将依赖于 NetworkStream.Dispose
无法提供的行为,因此您需要在 Socket
上自行管理这些内容。这里的常见操作顺序(如果需要,交换 client/server)看起来像这样:
- 客户端调用 Shutdown(Send)。
- 客户端继续循环接收,但不发送。
- 由于客户端关闭,服务器看到 EOF。
- 服务器写入任何最终状态并调用 Shutdown(Send)。
- 服务器关闭他们的套接字。
- 客户端看到任何最终状态,后跟由于服务器关闭而导致的 EOF。
- 客户端关闭了他们的套接字。 (如果他们不在乎,客户可以自由地提前关闭他们的套接字)
我正在编写一个 Minecraft Classic 服务器,但在实施踢球数据包时遇到了一些问题。服务器应该发送踢球数据包,然后自行处理。主要问题是因为在“发送”之后写入网络流 returns(这是一个超级模糊的定义),玩家会话将尝试自行处理它的 tcp 客户端,可能会中断流踢数据包的数据到客户端 - 这正是目前正在发生的事情。
我想提一下,不可能将 the protocol specifications 更改为要求在应用程序端进行某种确认。
这就是数据包的发送方式。通常没有任何问题,但是 disconnecting/disposing 发送数据包后会中断网络流上的数据流。
public bool SendPacket(Packet packet)
{
try
{
packet.Send(networkStream); //this simply writes stuff to the network stream
return true;
}
catch
{
return false;
}
}
会话是这样处理的:
public void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (currentWorld != null)
currentWorld.LeaveWorld(this);
if (account != null)
server.AccountManager.Logout(account);
networkStream.Close();
client.Close();
Disconnected = true;
}
disposed = true;
}
}
我的临时解决方案是发送kick数据包后等待100ms:
public void Kick(string reason)
{
SendPacket(new DisconnectPlayerPacket(reason));
Thread.Sleep(100);
Dispose();
}
它适用于一些测试用例,但并非始终如此。客户端没有收到所有数据。我知道这一点是因为我已经调试了 ClassiCube 客户端(一个 Minecraft Classic 客户端),我知道它在 100% 的时间里都能正常工作。
无论哪种方式等待都不是可行的解决方案,因为我的服务器会话是在单个线程中处理的 - 这在大多数情况下都有效,因为 Minecraft Classic 数据包相对较短且易于处理。但是,每等待一秒,就会浪费一秒,因为无法处理其他数据包。
我想要一些方法来指示 tcp 客户端是否已完成发送数据 - 在这种情况下,这意味着客户端已成功接收到 kick 数据包。本质上,我想要 dispose 方法,本质上是在关闭连接之前等待接收到最后一个数据包。我有点了解 TCP 协议,我知道客户端应该在之后发送一个 ACK 信号,但据我所知,TcpClient 将这些东西抽象出来。
完整的 github 回购 is here for reference, but please note that all of this mostly takes place in PlayerSession.cs。
总之,如何判断网络流是否已发送完所有数据?
当您调用NetworkStream.Write
时,您正在排队发送数据。 OS 会在幕后为您发送。要知道对方是否真的收到并处理了您的数据,唯一的方法就是让他们向您发送某种确认数据包。
当您调用 NetworkStream.Dispose()
时,它会做两件事。首先,它会关闭两个方向的套接字,如果一切顺利,最终会导致远程方在收到您的所有数据后看到 EOF。
其次,它会关闭您的套接字句柄。但是,这只会关闭 user-mode 句柄。在幕后,OS 将使套接字保持活动状态以刷新数据。
您可以通过设置套接字的延迟选项来稍微控制这里的行为,它控制超时以及 Close()
是否会在等待超时时阻塞。
这种行为有时是合适的,但许多网络协议将依赖于 NetworkStream.Dispose
无法提供的行为,因此您需要在 Socket
上自行管理这些内容。这里的常见操作顺序(如果需要,交换 client/server)看起来像这样:
- 客户端调用 Shutdown(Send)。
- 客户端继续循环接收,但不发送。
- 由于客户端关闭,服务器看到 EOF。
- 服务器写入任何最终状态并调用 Shutdown(Send)。
- 服务器关闭他们的套接字。
- 客户端看到任何最终状态,后跟由于服务器关闭而导致的 EOF。
- 客户端关闭了他们的套接字。 (如果他们不在乎,客户可以自由地提前关闭他们的套接字)