Search code examples
c#.netsocketsudpclient-server

Communicating with different ports in UDP


I just started socket programming with .net using C# and I had a question regarding an experimental server and client I was building on my machine.

My task is to make the client communicate with the server through port 2004, and the server with the client through port 2010. So meaning that the server receives messages in port 2004, and send them in port 2010. I however am struggling to implement this. Does this for example mean that the server has a local endpoint of 2004 and a remote endpoint of 2010? Or should I make 2 different sockets for this? Right now my code looks like this:

Client

Socket sock;

IPAddress ipAddress = IPAddress.Parse("127.0.0.1");

IPEndPoint clientEndpoint = new IPEndPoint(ipAddress, 2010);

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 2004);

EndPoint remoteEP = (EndPoint)sender;

sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
sock.Bind(clientEndpoint);

Server

IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 2010);
remoteEP = (EndPoint)remoteEndpoint;

IPEndPoint localEndpoint = new IPEndPoint(ipAddress, 2004);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(localEndpoint);

I've tried sniffing around other questions on Stackoverflow and also read the Socket documentation on learn.microsoft.com but I couldn't really figure out how to do this. I was also wondering how I would have to use ReceiveFrom and SendTo in this situation, but I am assuming I just receive and send to the remote endpoint?


Solution

  • I have never needed to use sockets in real life so this is mostly based on the basic knowledge of the protocol and the documentation.

    UDP is a unidirectional protocol, so if you want bi-directional communication you would setup a server and a client on each computer (or programs on the same computer), so yes, you would need two different sockets. Going by the socket example a server should look something like:

    using Socket listener = new(
        AddressFamily.InterNetwork,
        SocketType.Dgram,
        ProtocolType.Udp);
    
    listener.Bind(localEndpoint);
    listener.Listen(100);
    
    var handler = await listener.AcceptAsync();
    while (true)
    {
        // Receive message.
        var buffer = new byte[1_024];
        var receivedNoBytes = await handler.ReceiveAsync(buffer, SocketFlags.None);
        var receivedString = Encoding.UTF8.GetString(buffer, 0, receivedNoBytes );
        Console.WriteLine(receivedString);
    }
    

    and the client:

    using Socket client = new(
        AddressFamily.InterNetwork,
        SocketType.Dgram,
        ProtocolType.Udp);
    
    await client.ConnectAsync(remoteEndpoint);
    while (true)
    {
        // Send message.
        var message = Console.ReadLine();;
        var messageBytes = Encoding.UTF8.GetBytes(message);
        _ = await client.SendAsync(messageBytes, SocketFlags.None);
    }
    

    On one 'computer' you would use 2004 as the localEndpoint-port, and as the remoteEndpoint on the other 'computer'. So you probably want to take the listen and remote ports as command line arguments so you can run the same instance of your program for both.

    Note both the listener and the client run in separate infinite loops. So a single thread can either receive or send data, not both. If you want to both send and receive at the same time you could run each on a separate thread. Another alternative would be to interleave sending an receiving on a single thread, but that way you would need to wait for a response before you could send your next message. So I would use the two-thread solution, but you should to be somewhat comfortable with thread safety whenever using multiple threads.

    Also note that raw UDP has no real concept of a "message". If you want to an larger amount of data you need to create your own protocol to define the beginning and end of a message, as well as error handling etc. Higher level protocols will handle this for you, and are typically much easier to use.