Simple UDP example to send and receive data from same socket

Learn simple udp example to send and receive data from same socket with practical examples, diagrams, and best practices. Covers c#, udp development techniques with visual explanations.

C# UDP: Sending and Receiving Data on a Single Socket

Hero image for Simple UDP example to send and receive data from same socket

Learn how to implement a simple UDP client/server that uses the same socket for both sending and receiving data in C#.

User Datagram Protocol (UDP) is a connectionless protocol that offers a simple and efficient way to send and receive data over a network. Unlike TCP, UDP does not establish a persistent connection, making it suitable for applications where speed and low overhead are more critical than guaranteed delivery or ordered packets. This article will guide you through creating a C# application that can both send and receive UDP datagrams using a single UdpClient instance, simplifying your network communication logic.

Understanding UDP Communication with a Single Socket

When working with UDP, you typically bind a UdpClient to a local endpoint (IP address and port) to listen for incoming data. To send data, you specify a remote endpoint. The beauty of UDP is that once a UdpClient is bound to a local port, it can both send data to any remote endpoint and receive data from any remote endpoint on that same local port. This eliminates the need for separate send and receive sockets for many common scenarios, such as peer-to-peer communication or simple client-server interactions where the client also needs to receive responses.

sequenceDiagram
    participant App as C# Application
    participant Remote as Remote Peer

    App->>App: Bind UdpClient to Local Port
    Note over App: UdpClient is now listening
    App->>Remote: Send Data (to Remote IP:Port)
    Remote-->>App: Receive Data
    Remote->>Remote: Process Data
    Remote->>App: Send Response (to App's Local IP:Port)
    App-->>App: Receive Response
    Note over App: Same UdpClient handles both

Sequence diagram of UDP communication using a single socket.

Implementing the UDP Sender/Receiver in C#

To demonstrate this, we'll create a simple console application. This application will initialize a UdpClient, bind it to a specific local port, and then continuously listen for incoming messages while also providing a mechanism to send messages to a specified remote endpoint. We'll use asynchronous methods (ReceiveAsync) to avoid blocking the main thread while waiting for data.

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

public class UdpSenderReceiver
{
    private UdpClient _udpClient;
    private IPEndPoint _remoteEndPoint;
    private int _localPort;

    public UdpSenderReceiver(int localPort, string remoteIp, int remotePort)
    {
        _localPort = localPort;
        _remoteEndPoint = new IPEndPoint(IPAddress.Parse(remoteIp), remotePort);
        _udpClient = new UdpClient(_localPort);
        Console.WriteLine($"UDP Client initialized on local port {_localPort}.");
        Console.WriteLine($"Targeting remote endpoint: {_remoteEndPoint}.");
    }

    public async Task StartReceivingAsync()
    {
        Console.WriteLine("Listening for incoming UDP messages...");
        while (true)
        {
            try
            {
                var result = await _udpClient.ReceiveAsync();
                string receivedMessage = Encoding.UTF8.GetString(result.Buffer);
                Console.WriteLine($"Received from {result.RemoteEndPoint}: {receivedMessage}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error receiving: {ex.Message}");
                break;
            }
        }
    }

    public async Task SendMessageAsync(string message)
    {
        try
        {
            byte[] data = Encoding.UTF8.GetBytes(message);
            await _udpClient.SendAsync(data, data.Length, _remoteEndPoint);
            Console.WriteLine($"Sent to {_remoteEndPoint}: {message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error sending: {ex.Message}");
        }
    }

    public void Close()
    {
        _udpClient.Close();
        Console.WriteLine("UDP Client closed.");
    }

    public static async Task Main(string[] args)
    {
        // Configuration: Adjust these values as needed
        int localPort = 11000; // Port this application will listen on
        string remoteIp = "127.0.0.1"; // Target IP address
        int remotePort = 11001; // Target port

        // Create an instance of our sender/receiver
        var udpApp = new UdpSenderReceiver(localPort, remoteIp, remotePort);

        // Start receiving messages in the background
        var receiveTask = udpApp.StartReceivingAsync();

        Console.WriteLine("Type messages to send and press Enter. Type 'exit' to quit.");

        string? input;
        while ((input = Console.ReadLine()) != null)
        {
            if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
            {
                break;
            }
            await udpApp.SendMessageAsync(input);
        }

        udpApp.Close();
        Console.WriteLine("Application shutting down.");
    }
}

C# code for a UDP sender/receiver using a single UdpClient instance.

Key Considerations for UDP Communication

While using a single socket for both sending and receiving simplifies code, it's important to remember the inherent characteristics of UDP:

  • Connectionless: There's no handshake or persistent connection. Each datagram is sent independently.
  • Unreliable: Datagrams are not guaranteed to arrive, may arrive out of order, or may be duplicated. Your application must handle these possibilities if reliability is required.
  • No Flow Control: UDP doesn't manage the rate of data transfer, which can lead to packet loss if the receiver is overwhelmed.
  • Datagram Size: UDP datagrams have a maximum size (typically around 65,507 bytes, including headers). Larger messages must be fragmented by the application layer.

For applications requiring reliability, ordered delivery, or flow control, TCP is generally a better choice. However, for real-time applications like gaming, streaming, or IoT sensor data where occasional loss is acceptable for lower latency, UDP excels.