简体   繁体   中英

client server UDP connection between clients c#

I'm writing application which is based on UDP Hole Punching. I have a problem with establishing connection between clients. After each client sends something to server and server responses to each other with their IPs, clients aren't able to send anything to each other. Am I missing anything? Or my understanding of UDP Hole Punching is wrong? Yes, I've external IP for PC where server is.

server code :

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


namespace ConsoleApplication2
{
class Program
{

    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localEP = new IPEndPoint(IP, 80);
        UdpClient server = new UdpClient();
        server.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        server.ExclusiveAddressUse = false;
        server.Client.Bind(localEP);
        IPEndPoint temp;

        IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 80);

        Console.WriteLine("Dane servera : " + localEP);
        byte[] buffer = server.Receive(ref remoteEP);

        Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer));

        temp = remoteEP;

        remoteEP = new IPEndPoint(IPAddress.Any, 80);
        byte[] buffer2 = server.Receive(ref remoteEP);
        Console.WriteLine("Otrzymano dane od : " + remoteEP + " o treści " + Encoding.ASCII.GetString(buffer2));

        byte[] response = Encoding.ASCII.GetBytes(temp.ToString());
        server.Send(response, response.Length, remoteEP);
        byte[] response2 = Encoding.ASCII.GetBytes(remoteEP.ToString());
        server.Send(response2, response2.Length,temp );

    }
}
}

client 1:

namespace ConsoleApplication1
{
class Program
{
    public static IPEndPoint CreateIPEndPoint(string endPoint)
    {
        string[] ep = endPoint.Split(':');
        if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
        IPAddress ip;
        if (ep.Length > 2)
        {
            if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        else
        {
            if (!IPAddress.TryParse(ep[0], out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        int port;
        if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
        {
            throw new FormatException("Invalid port");
        }
        return new IPEndPoint(ip, port);
    }
    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localpt = new IPEndPoint(IP, 80);
        UdpClient client = new UdpClient();
        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        client.ExclusiveAddressUse = false;
        string powitanie = "ASUS";
        byte[] buffer = new byte[100];
        buffer = Encoding.ASCII.GetBytes(powitanie);
       // client.Connect(localpt);
        client.Send(buffer, buffer.Length,localpt);

        byte[] otrzymane = client.Receive(ref localpt);
        Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
        Console.Read();
        IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));


        byte[] buffer2 = client.Receive(ref TV);
       Console.WriteLine("Odpowiedz klienta : " + Encoding.ASCII.GetString(buffer2));
    }
}
}

client 2:

namespace ConsoleApplication1
{
class Program
{
    public static IPEndPoint CreateIPEndPoint(string endPoint)
    {
        string[] ep = endPoint.Split(':');
        if (ep.Length < 2) throw new FormatException("Invalid endpoint format");
        IPAddress ip;
        if (ep.Length > 2)
        {
            if (!IPAddress.TryParse(string.Join(":", ep, 0, ep.Length - 1), out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        else
        {
            if (!IPAddress.TryParse(ep[0], out ip))
            {
                throw new FormatException("Invalid ip-adress");
            }
        }
        int port;
        if (!int.TryParse(ep[ep.Length - 1], NumberStyles.None, NumberFormatInfo.CurrentInfo, out port))
        {
            throw new FormatException("Invalid port");
        }
        return new IPEndPoint(ip, port);
    }
    static void Main(string[] args)
    {
        IPAddress IP = IPAddress.Parse("xx.xx.xx.xxx");
        IPEndPoint localpt = new IPEndPoint(IP, 80);
        UdpClient client = new UdpClient();
        client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        client.ExclusiveAddressUse = false;
        string powitanie = "Samsung";
        byte[] buffer = new byte[100];
        buffer = Encoding.ASCII.GetBytes(powitanie);
       // client.Connect(localpt);
        client.Send(buffer, buffer.Length,localpt);

        byte[] otrzymane = client.Receive(ref localpt);
        Console.WriteLine("Odpowiedz servera : " + Encoding.ASCII.GetString(otrzymane));
        Console.Read();
        IPEndPoint TV = CreateIPEndPoint(Encoding.ASCII.GetString(otrzymane));

        client.Send(buffer, buffer.Length, TV);

    }
}
}

Without access to your exact environment and network, I am not convinced it will be possible to offer an answer with any confidence that it would be assured of addressing the issue. But here are something things to keep in mind:

  1. First and foremost, "hole punching" is not a well-defined feature with a technical specification or industry standard for support. There is never any guarantee that it will work, though of course many routers work in a way that allows it to. If you are sure your code is correct but is for some reason still not working, it is always possible that you are using one or more routers that simply won't work with the technique.
  2. Depending on the router behavior, it may or may not be sufficient for a client to have sent a datagram from a specific endpoint. A router may not route foreign datagrams to that endpoint until the recipient of the original outbound datagram has replied, ie two-way communication has in fact been established. Your current implementation appears to include code to allow the user testing the code to wait for both server replies before attempting to send to the other client, but do make sure you're taking advantage of that. Ie that you don't try to send from one client to the other until both clients have received the server response.
  3. In addition to the above, it may not be sufficient for the server to have sent datagrams to each client. A router may still discard datagrams received from an unknown endpoint. So a variation on the technique that is often required is that one client attempts to send a datagram to the other client, but also notifies that client via the server that it has done so (ie sends the server a datagram reporting this, and then the server sends a datagram to the intended recipient client to notify it).

    This datagram will be discarded, but the sending client's router doesn't know this, so when the other client replies directly to the sending client (having been notified by the server that it should), now the original sending client's router will pass the datagram to the that client. It saw the previous datagram that was intended for that other client, so it treats the inbound datagram from that other client as valid. From that point forward, both clients should be able to send to each other directly. Naturally, implementing this requires a more complicated application protocol than just forwarding IP addresses as your example here does.
  4. Your code examples use SocketOptionName.ReuseAddress , which is almost always wrong. It appears to me that only your server socket binds to an explicit address, so using this option probably wouldn't affect the outcome of the test (ie you still only have one socket on any given address, even if you are testing in a single machine). But if there is more to your testing environment, such that the socket address really is being reused, that can easily interfere with the correct operation of the code.

Finally, you ask in a comment (please put relevant information and questions in the question itself): "should I use udp.connect to the server and then udp.send between clients" . The answer is "no". First of all, using Connect() on a UDP socket is merely a convenience; UDP itself is connectionless, and so connecting a UDP socket is handled entirely within the framework. Second, in .NET when you "connect" a UDP socket, the framework will filter datagrams, restricting them to those received from the "connected" endpoint. This is exactly the opposite of what you want with hole-punching; ie you want to be able to receive datagrams from both the server and the other client.

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