2
votes

I have been doing several hours of research on a topic that I thought would've been very trivial. So far I've come up empty handed and wanted to see what you guys think. I'm currently messing with XNA (which is actually quite irrelevant now that I think about it) building a client/server architecture for a "game" project. Really nothing more than playing around with networking, animations, etc.

Anyway I've read quite a bit about the differences between Synchronous and Asynchronous networks and the viability of threading synchronous applications to simulate asynchronous behavior and have decided to do just that. Now I know my code isn't pretty but I'm just testing right now. Here's how it's (sort of) set up:

Game is run in the main thread. On initialization->Connect to server. Send x position of player class's sprite object to server. Server receives, acknowledges with a print to console and sends the same data back. Data is read into a logfile.

I've begun work on a messenger class that will eventually read (or sniff) the packets coming from the server and dispatch them accordingly making draw/update calls as needed. My problem is that I cant figure out how to properly thread the connection method to have that run (and then block?) separate from the Send/Receive loop (or what I'd like to be a continuous loop).

Like I said I'm no expert, I'm just doing this for fun so I may be all over the place. Anyway here are the essentials code-wise:

Networking.cs

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;


namespace DrawTest
{
class Networking
{
    public void StartClient(Messenger m)
    {

    // Data buffer for incoming data.
    StreamWriter _con = new StreamWriter("data.txt");


    // Connect to a remote device.
    try {
        // Establish the remote endpoint for the socket.
        // This example uses port 11000 on the local computer.


        IPHostEntry ipHostInfo = Dns.Resolve("127.0.0.1");
        IPAddress ipAddress = ipHostInfo.AddressList[0];
        IPEndPoint remoteEP = new IPEndPoint(ipAddress,3000);

        // Create a TCP/IP  socket.
        Socket sender = new Socket(AddressFamily.InterNetwork, 
            SocketType.Stream, ProtocolType.Tcp );

        // Connect the socket to the remote endpoint. Catch any errors.
        try {

            sender.Connect(remoteEP);

            _con.WriteLine("Socket connected to {0}",
                sender.RemoteEndPoint.ToString());
            while (m.isAlive)
            {
                m.SocketStream(sender, _con);
            }

            if (Messenger.mPacket() == "close_socket")
            {
                _con.WriteLine("Connection closed by client.");
                sender.Shutdown(SocketShutdown.Both);
                sender.Close();
            }

        } catch (ArgumentNullException ane) {
            _con.WriteLine("ArgumentNullException : {0}",ane.ToString());
        } catch (SocketException se) {
            _con.WriteLine("SocketException : {0}",se.ToString());
        } catch (Exception e) {
            _con.WriteLine("Unexpected exception : {0}", e.ToString());
        }
        _con.Flush();
    } 

    catch (Exception e) {
            _con.WriteLine(e.ToString());
            _con.Flush();
        }
    }
}
}

Messenger.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace DrawTest
{
public class Messenger
{
    byte[] bytes = new byte[1024];
    byte[] incBuffer = new byte[1024];
    public bool isAlive = true;
    Vector2 position = new Vector2(0.0f, 0.0f);

    public Vector2 getPos()
    {
        return position;
    }
    public void setPos(Vector2 pos)
    {
        position = pos;
    }

    public void SocketStream(Socket s, StreamWriter logfile)
    {
        byte[] msg = null;
        int bytesSent = 0;
        int bytesRec = 0;
        msg = BitConverter.GetBytes(position.X);
        // Encode the data string into a byte array.
        bytesSent = s.Send(msg);

        // Receive the response from the remote device.
        bytesRec = s.Receive(incBuffer);
        //logfile.WriteLine(Messenger.mDecoder(incBuffer, bytesRec));
    }

    public string mDecoder(byte[] msg, int size)
    {
        string DecodedMessage;
        byte[] bytes = new byte[1024];
        DecodedMessage = Encoding.ASCII.GetString(msg, 0, size);
        if (DecodedMessage == "close_socket")
        {
            isAlive = false;
            return DecodedMessage;
        }
        return DecodedMessage;            
    }

    public static string mPacket()
    {
        return null;
    }

}
}

Think that should do it. The other code is relatively self-explanatory (abstract player/sprite classes and the typical XNA Game.cs)

Thanks in advance for any help!

1
You migth have a look at the Lindgren network library, that's designed to simplify and encapsulate this sort of thing, and includes some XNA-specific stuff. It wraps the sockets API to take care of a lot of the boilerplate networking code. See xnacoding.blogspot.com/2010/07/how-to-lidgren-network.html and - 3Dave

1 Answers

1
votes

You may do something along the lines of:

public void SendData(Socket s)
{
    byte[] msg = null;
    int bytesSent = 0;

    msg = BitConverter.GetBytes(position.X);

    // Encode the data string into a byte array.
    bytesSent = s.Send(msg);

}

void ReceiveData(Socket s)
{
    int bytesExpected = 1024; // somehow specify the number of bytes expected
    int totalBytesRec = 0; // adds up all the bytes received
    int bytesRec = -1; // zero means that you're done receiving

    while(bytesRec != 0 && totalBytesRec <  bytesExpected )
    {
        // Receive the response from the remote device.
        bytesRec = s.Receive(incBuffer);
        totalBytesRec += bytesRec;
    }
}

Back in your StartClient class, you should start your receive thread first then send the data:

// Start your receive thread first
Thread t = new Thread(()=>{ReceiveData(sender);});
t.IsBackground = true;
t.Start();

// Then send the data
SendData(sender);

// Wait for the thread to terminate (if you need to)
t.Join(30000);

// Once you close the socket, then it will throw an exception
// in the receive thread (which you should catch) and you can
// exit the thread, thus terminating the thread.

This is roughly how you would start a thread that performs the receive.

Update (based on comments)

I would recommend that you take a look at some of the Patterns for Multithreaded Network Server in C#.

The server side should start a new thread for every client connection accepted and a "connection handler" should take over and manage the sending/receiving of data from there on:

while(serverRunning)
{
    Socket clientSocket = serverSocket.Accept();

    // You can write your own connection handler class that automatically
    // starts a new ReceiveData thread when it gets a client connection
    ConnectionHandler chandler = new ConnectionHandler(clientSocket);

    // Have an on-client-disconnected event which you can subscribe to
    // and remove the handler from your list when the client is disconnected
    chandler.OnClinetDisconnectedEvent += new OnClientDisconnectedDelegate(OnClientDisconnected);

    mHandlerList.Add(chandler);

}

// When you're terminating the program, then just go through 
// the list of active ConnectionHandlers and call some method
// which tells them to close their connections with the clients
// and terminates the thread.

To be even more precise, you are likely to have the very similar behavior with the client's and the server's ReceiveData method: i.e. synchronously send a message back whenever they receive some message. Here is a more realistic example that might help you conceptualize it better:

void ReceiveData(Socket s)
{
    int bytesExpected = 1024; // somehow specify the number of bytes expected
    int totalBytesRec = 0; // adds up all the bytes received
    int bytesRec = -1; // zero means that you're done receiving

    while(bytesRec != 0 && totalBytesRec <  bytesExpected )
    {
        // Receive the response from the remote device.
        bytesRec = s.Receive(incBuffer);
        totalBytesRec += bytesRec;

        if(needToReply)
        {
            // Send another message
            SendData(s);
        }
    }
}

This is a long running thread, of course, so you would generally like to have it run for as long as the player is connected to the internet. The comment about closing the connection and terminating the thread is specifically for the situation where you need to have a graceful exit (i.e. the player quits the game or the rare case that server needs to be shut down).