简体   繁体   English

如何正确进行异步udp网络?

[英]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)异步工作并为实时应用程序做好准备(我考虑的是 FPS 游戏)
  • use UDP and set up a thin protocol layer on top as necessary使用 UDP 并根据需要在顶部设置一个薄协议层
  • work with IPv6 natively本机使用 IPv6
  • support multiple platforms (read: I want Mono support!)支持多个平台(阅读:我想要 Mono 支持!)

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:现在在阅读了一些关于如何做到这一点(最鼓舞人心的是Gaffer on Games )之后,我设置了我的开发环境,思考如何做到这一点并提出了这个基本的工作流程:

  1. Initialize a socket and tell it to use "UDPv6"初始化套接字并告诉它使用“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.了解本地计算机支持的 ​​NIC 的最大 MTU。
  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.初始化多个 SocketAsyncEventArgs,告诉它的 Completed 事件调用私有调度方法并将其缓冲区设置为步骤 3 中的最大 MTU 的大小。
  5. Call the Sockets ReceiveFromAsync method with the first SAEA object around.使用第一个 SAEA 对象调用 Sockets ReceiveFromAsync 方法。

When data comes in, I do the following:当数据进来时,我执行以下操作:

  1. Call the ReceiveFromAsync method with the next free SAEA object使用下一个空闲的 SAEA 对象调用 ReceiveFromAsync 方法
  2. Get buffer and Sender information from the current SAEA object and make it available again从当前 SAEA 对象中获取缓冲区和发送者信息并使其再次可用
  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.我每 10 毫秒向它发送一条消息,其中包含 200 字节的数据,持续 10000 个周期,并且 CPU 或内存负载几乎没有增加。 Only NIC load is increasing.只有 NIC 负载在增加。 However I came up with some problems |然而我想出了一些问题| questions:问题:

  • When I dispose my PeerSocket class (that is holding the socket) I dispose every SAEA object.当我处置我的 PeerSocket 类(即持有套接字)时,我处置了每个 SAEA 对象。 But since at least one of them is still listening for new messages, an ObjectDisposedException is thrown.但由于其中至少有一个仍在侦听新消息,因此抛出 ObjectDisposedException。 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? MTU 可能在通往其他对等方的路上有所不同,也许每个 SAEA 对象的缓冲区应该使用不同的指标来确定缓冲区大小?
  • 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.基本上,服务器打开端口(本例中为 9050)等待客户端连接,然后客户端连接到服务器,然后开始通信。

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).你提到你必须使用 UDP 套接字,我想你知道 udp 但如果不是你最好检查一下 TCP 和 UDP 之间的区别,特别是关于验证数据到达所需目的地的方式(临时概念等等上)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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