重用UdpClient vs处理它

时间:2017-11-22 17:23:49

标签: c# sockets endpoint hole-punching

我正在创建一个聊天应用程序,其中用户位于路由器(NAT)后面。所以主要问题是向这些客户发送消息。服务器接收消息没有问题。

这是我的协议(规则):

  1. UdpServer侦听来自client A
  2. 的udp数据包
  3. Serverclient A接收数据包并回复数据包以打开NAT
  4. Server现在成为客户端(服务器不断向client A发送数据包,客户端回复服务器通知它收到了他的消息)我反转客户端和服务器现在NAT已经打开。这是因为client A始终可以通过tcp向服务器发送消息。

  5. 如果Server发现client A没有回复其经常发送的数据包,则会将client A标记为已断开连接并停止发送数据包。

  6. 这是代码

    // class that holds infor about client
    class ClientBehindNat
    {
         public IPEndPoint EndpointListening;
         public string Id;
    }
    

    侦听新客户端(连接)的线程:

    while(true)
    {
        IPEndPoint ep = new IPEndPoint(IPAddress.Any, 1234);
        // create a new udpServer
        using(var udpServer = new UdpClient())
        {
            // allow reusing port 1234
            udpServer.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            // wait to receive data
            var data = udpServer.Receive(ref ep);
    
            // analyze data make sure it is reliable....
    
            // maybe data is a reply from a client 
            if(data.IsReply())
            {
                 // mark that client replied
                 // ....
                 continue;
            }
            else
            {
                // save new client
                ConnectedClients.Add(new ClientBehindNat(){EndpointListening=ep});
            }
    
            udpServer.Connect(ep);
            udpServer.Send( // data notifying client that we got his message)
    
        }
    }
    

    不断向客户端发送udp数据包以保持nat打开的线程

    // send packets every 15 seconds in order to leave nat open
    timer.elapsed += (a,b)=> {
    
        foreach (var c in connectedClients)
        {
            // create a copy of udp server so that client can get our response
            using (var copySocket = new UdpClient())
            {
                // allow reusing port 1234
                copySocket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                copySocket.Client.Bind(new IPEndPoint(IPAddress.Any, 1234));
                // log that we will send this request
                // Requests[someId] = ....
    
                // send it
                copySocket.Send(someData, someData.Length, c.EndpointListening);
    
                // first thread should be responsible for receiving this response
    
                // dispose this client                            
            }          
        }
    }
    

    这是问题

    一切都很好但是每个请求创建一个新的Socket(UdpClient)是不是很糟糕?如果我收到100个请求,我会创建100个新套接字并处理所有这些。当我尝试重用udp客户端更新其端点时,我得到一个例外。我经常需要向Client AClient Z发送消息,这就是为什么我存储其端点以了解如何到达它。 如何重用udpClient更新其端点,以便我能够访问我感兴趣的客户端?

2 个答案:

答案 0 :(得分:1)

我应该再次建议直接使用Socket s,它会给你很大的灵活性。请查看以下示例,该示例使用一个套接字进行UDP连接以进行发送和接收:

    private static readonly List<EndPoint> clients = new List<EndPoint>();
    private static readonly SocketAsyncEventArgs receiveEventArgs = new SocketAsyncEventArgs();
    private static Socket socket;

    private static void Main(string[] args)
    {
        receiveEventArgs.Completed += OnReceive;
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        socket.Bind(new IPEndPoint(IPAddress.Any, 6000));
        Receive();

        while (true)
        {
            Thread.Sleep(3000);
            lock (clients)
            {
                foreach (var endPoint in clients)
                    socket.SendTo(Encoding.ASCII.GetBytes("PING"), endPoint);
            }
        }
    }

    private static void Receive()
    {
        receiveEventArgs.SetBuffer(new byte[256], 0, 256);
        receiveEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 6000);
        socket.ReceiveFromAsync(receiveEventArgs);
    }

    private static void OnReceive(object sender, SocketAsyncEventArgs e)
    {
        if (e.BytesTransferred > 0)
        {
            // Is reply?
            var isReply = true/false;
            if (isReply)
            {
                // Do domething
            }
            else
                lock (clients)
                {
                    clients.Add(e.RemoteEndPoint);
                }
        }
        else
        {
            lock (clients)
            {
                clients.Remove(e.RemoteEndPoint);
            }
        }
        Receive();
    }

答案 1 :(得分:0)

发现问题。当我回复客户时,我得到了回复,我正在这样做:

udpServer.Connect(ep); // <---------------- this was the problem
udpServer.Send(someData);

如果我从未连接并使用@Sourush声明的SneTo方法,它可以让我继续监听数据包。所以现在我有:

udpServer.Client.SentTo(data, ep)