简体   繁体   中英

How to do async udp networking right?

As many others here on SO I'm trying to create a networking library. The requirements basically look like this:

  • work asynchronously and get ready for real-time applications (I have FPS games in mind)
  • use UDP and set up a thin protocol layer on top as necessary
  • work with IPv6 natively
  • support multiple platforms (read: I want Mono support!)

Now after some reading about how to do this (most inspiring was Gaffer on Games ) I set up my development environment, thought about how to do it and came up with this basic workflow:

  1. Initialize a socket and tell it to use "UDPv6"
  2. Bind that socket to a port and make exceptions not bother the user. There is a "Bound" propery that tells him that the socket is set up correctly.
  3. Find out about the max MTU the NICs on the local machine support.
  4. Initialize a number of SocketAsyncEventArgs, tell its Completed event to call the private dispatch method and set its buffer to the size of the max MTU from step 3.
  5. Call the Sockets ReceiveFromAsync method with the first SAEA object around.

When data comes in, I do the following:

  1. Call the ReceiveFromAsync method with the next free SAEA object
  2. Get buffer and Sender information from the current SAEA object and make it available again
  3. Fire a new event with the received message.

I did some testing on this approach and it is working quite good. I fired a message at it every 10 milliseconds with 200 bytes of data for 10000 cycles and there is pretty much no increase in CPU or memory load. Only NIC load is increasing. However I came up with some problems | questions:

  • When I dispose my PeerSocket class (that is holding the socket) I dispose every SAEA object. But since at least one of them is still listening for new messages, an ObjectDisposedException is thrown. Is there a way to tell it to stop listening?
  • The MTU may vary on the way to other peers, maybe the buffer of each SAEA object should use a different indicator for determining the buffers size?
  • I'm not sure how to handle fragmented datagrams yet. I will get on writing that "reliability header" into a datagram I am sending, but if a datagram is split I don't know about this header information, right?

The library will hopefully be of use to someone else one day and it's repository is publicly available. As of this question, the current commit can be found here

Wow, it is really huge subject. if you didn't learned about network sockets before you'd better learn. I can give you the gist but it is definitely not enough.

The Client:

public void Get()
    {
        string data;
        string input;
        IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050);

        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


        try
        {
            socket.Connect(ipep);
        }
        catch (SocketException e)
        {
            Console.WriteLine("Unable to connect to server");
            Console.WriteLine(e.ToString());
            return;
        }

        NetworkStream ns = new NetworkStream(socket);
        StreamWriter sw = new StreamWriter(ns);
        StreamReader sr = new StreamReader(ns);

        data = sr.ReadLine();
        Console.WriteLine(data);

        while (true)
        {
            input = Console.ReadLine();
            if (input == "exite")
                break;

            sw.WriteLine(input);
            sw.Flush();

            data = sr.ReadLine();
            Console.WriteLine(data);
        }
        Console.WriteLine("Disconnected from server...");
        socket.Close();
        ns.Close();
        sr.Close();
        sr.Close();
    }

The Server:

public void Get()
    {
        string data;
        IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);

        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        socket.Bind(ipep);
        socket.Listen(10);
        Console.WriteLine("Waiting for a client...");

        Socket client = socket.Accept();
        IPEndPoint newclient = (IPEndPoint)client.RemoteEndPoint;
        Console.WriteLine("Connected with: {0}, at Port: {1}", newclient.Address, newclient.Port);

        NetworkStream ns = new NetworkStream(client);
        StreamReader sr = new StreamReader(ns);
        StreamWriter sw = new StreamWriter(ns);

        string welcome = "Welcome to my test server";
        sw.Write(welcome);
        sw.Flush();

        while (true)
        {
            try
            {
                data = sr.ReadLine();
            }
            catch (IOException)
            {
                break;
            }
            Console.WriteLine(data);
            sw.WriteLine(data);
            sw.Flush();
        }
        Console.WriteLine("Disconnected from {0}", newclient.Address);
        sw.Close();
        ns.Close();
        sr.Close();
    }

Please try it out on Console application, see how it works.

Basically, the server opens the port (9050 in this example) waiting for the client connection then the client connects to the server and then starts the communication.

You mentioned you have to use UDP sockets, I presume you know about udp but if not you'd better check about the distinction between TCP and UDP especially about the way to verify that the data get to the desired destination (ad hoc concept and so on).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM