I am writing a Minecraft Classic Server, and I am having some issues implementing the kick packet. The server is supposed to send the kick packet, and dispose of itself thereafter. The primary issue is that because writing to a network stream returns after it's is "sent" (which is a super-vague definition), the players session will try to dispose itself and it's tcp client, potentially interrupting the flow of the kick packet's data to the client - which is exactly what is happening at the moment.
I wanted to mention that it is impossible to change the the protocol specifications to require some sort of an acknowledgement on the application side.
This is how packets are sent. Usually there aren't any issues, but disconnecting/disposing after a packet is sent interrupts the flow of data on the network stream.
public bool SendPacket(Packet packet)
{
try
{
packet.Send(networkStream); //this simply writes stuff to the network stream
return true;
}
catch
{
return false;
}
}
This is how the session is disposed:
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;
}
}
My temporary solution was to wait 100ms after sending the kick packet:
public void Kick(string reason)
{
SendPacket(new DisconnectPlayerPacket(reason));
Thread.Sleep(100);
Dispose();
}
It worked for some of the test cases, but not all the time. The client doesn't receive all the data. I know this because I've debugged the ClassiCube client (a Minecraft Classic Client) which I know works 100% of the time.
Either way waiting is not a feasible solution because my server's sessions are handled in a single thread - this works most of the time because Minecraft Classic Packets are relatively short and easy to process. However, each second spent waiting is another second wasted being unable to process other packets.
I'd like some way to indicate whether a tcp client has finished sending data or not - in this case that means that the kick packet has been received successfully by the client. Essentially, I want the dispose method, in essence to wait for the last packet to be received before closing the connection. I am somewhat aware of the TCP Protocol, and I know the client should send an ACK signal afterwards, but as far as I'm aware TcpClient abstracts that stuff away.
The full github repo is here for reference, but please note that all of this mostly takes place in PlayerSession.cs.
In sum, how can I tell if a network stream has finished sending all data?
When you call NetworkStream.Write
, you are queuing up data to send. The OS will send it for you, behind the scenes. The only way to know if the other party has actually received and processed your data is actually to have them send some sort of acknowledgement packet to you.
When you call NetworkStream.Dispose()
, it does two things. First, it shuts down the socket in both directions, which if everything goes right will eventually cause the remote party to see EOF after they've received all your data.
Second, it closes your handle to the socket. But, this only closes the user-mode handle. Behind the scenes, the OS will keep the socket alive to flush out the data.
You can somewhat control the behavior here by setting the socket's linger options, which control the timeout and if Close()
will block or not while waiting for the timeout.
This behavior is sometimes appropriate, but many network protocols will rely on behavior that NetworkStream.Dispose
can't offer, and so you would need to manage this stuff yourself on the Socket
. A common order of operations here (swap client/server if needed) looks something like this: