【问题标题】:Reuse UdpClient vs Disposing it重用 UdpClient 与处置它
【发布时间】:2018-05-06 12:59:40
【问题描述】:

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

这是我的协议(规则):

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

  4. 如果Server 看到client A 没有回复它不断发送的数据包,它会将client A 标记为已断开连接并停止发送数据包。

这是代码

// 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 以便能够访问我感兴趣的客户端?

【问题讨论】:

  • 为什么不改变c的值而不是创建一个新的UdpClient呢?你用那个方法有什么异常吗?
  • 由于某种原因,如果我更改端点的值,客户端不会收到数据包。也许我做错了什么要继续努力。
  • 我建议转移到Socket,尤其是SendTo 方法。

标签: c# sockets endpoint hole-punching


【解决方案1】:

我应该再次推荐直接使用Sockets,它给你很大的灵活性。查看以下示例,该示例使用一个套接字进行发送和接收的 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();
    }

【讨论】:

    【解决方案2】:

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

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

    如果我从不连接并使用@Sourush 所说的 SneTo 方法,它使我能够继续侦听数据包。因此,现在我有:

    udpServer.Client.SentTo(data, ep)
    

    【讨论】:

      猜你喜欢
      • 2014-08-10
      • 2011-08-01
      • 2012-03-07
      • 2011-11-06
      • 1970-01-01
      • 2014-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多