Search code examples
c#multithreadingxnatcpclientstreamreader

Socket WriteLine/ReadLine getting mismatched


Usually I am able to resolve problems on my own, but once again I have to ask for help. I apologise in advance if something is incoherent, as it appears that I've got a bit of a fever.

I am currently making really simple game in XNA. It's a 2D overhead shooter. The only action is Player shooting Bullets. The thing is, I want to make it multiplayer (players in the same network).

I've considered following approach:

  1. One of players hosts server

    • Server holds information about position of every player and bullet
    • Server creates new World (composed of players and bullets) and updates it in a new thread like this:

      while (true)
          {
              lock (world) world.update();
              Thread.Sleep(worldUpdatePeriod);
          }
      
    • Server creates new Thread for every player that has to be handled

  2. Players connect to the server

    • They should periodically receive information from server what should they draw:

      class Asset
      {
         string type;
         Vector2 position;
      }
      
    • They should be able to send input from Keyboard and Mouse to move and shoot.

Now, to the point. Player is able to send three types of messages to the server:

  • "CAN READ"
  • "MOUSE"
  • "KEYBOARD"

    string header = "CAN READ";
    lock (streamWriter) { streamWriter.WriteLine(header); }
    

Server, in a separate Thread for every client waits for messages

streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
streamWriter.AutoFlush = true;

while (true)
{
    if (networkStream.DataAvailable)
    {
        string header;
        lock (streamReader)
        {
            header = Convert.ToString(streamReader.ReadLine());
            Console.WriteLine($"Header received: {header}");
            handleHeader(header);
        }
    }
}

Now if the header is, for example, "CAN READ"

  1. Server

    case "CAN READ":
        if (streamWriter.BaseStream.CanRead)
        {
            lock (streamWriter)
            {
                streamWriter.WriteLine(server.world.assets.Count);
    
                foreach (var asset in server.world.assets)
                {
                    streamWriter.WriteLine(asset.type);
                    streamWriter.WriteLine(asset.position.X);
                    streamWriter.WriteLine(asset.position.Y);
                }
            }
        }
        break;
    
  2. Client

    int assetCount = Convert.ToInt32(streamReader.ReadLine());
    List<Asset> receivedAssets = new List<Asset>();
    
    lock (streamReader)
    {
    
        for (int i = 0; i < assetCount; i++)
        {
            string type = Convert.ToString(streamReader.ReadLine());
            int x = Convert.ToInt32(streamReader.ReadLine());
            int y = Convert.ToInt32(streamReader.ReadLine());
    
            receivedAssets.Add(new Asset(type, new Vector2(x, y)));
        }
        return receivedAssets;
    }
    

Of course Client class and handleClient class have separate streamReaders nad streamWriters.

You get the idea. I am trying to match every WriteLine() from Server with ReadLine() from Client and so on. And it works fine! Until I am adding user input to the mixture.

Once I send keyboard input (matched as shown above), it appears to work, with occasional System.FormatException, meaning that some ReadLine() read wrong WriteLine(). I am able to catch that and retry.

And everything goes to hell once I send mouse input as well. Mismatching exceptions become waaay more often to the point where the 'game' is unplayable. So I am wondering if there's something wrong with my approach. I apologise for length of the question, I tried to be concise but also felt need to provide context. Thanks if you made it to the end, I'll be glad to answer any further questions as I am trying to solve it for the past few days.


Solution

  • I've read the link that Andrew provided me with and I've been able to make it work. How? Using both TCP Client (to set up connection, read input from client) and UDP Client (to broadcast assets to every client). Just posting that in case that someone encounters such problem in the future, which I highly doubt.

    Cheers!