UDP

General

Introduction

UDP is the other major protocol in the internet world at layer 4. While TCp has elaborate mechanisms to support order, recover from lost packets and avaoid overloading the network, UDP has none of these. It ia basically a thin layer above IP, adding the concept of ports from the sending and receiving ends, allowing the packets to be directed to the correct application.

UDP does not guarantee that packets arrive in order - it doesn't guarantee that packets arrive at all. This doesn't work well for applications that rely on messages and responses arriving and in order. A typical client-server reliance on


	  prepare and send msg
	  receive msg and react
      
must be modified to

	  prepare and send msg
	  try to receive
	      if received, then react
	      if timeout, decide how to handle it
      

So why use UDP? It's faster and more lightweight. It is good for broadcast situations where responses are not required. It is good for multimedia streaming where a light protocol is best.

It is also good for building new protocols that rely on getting messages through most of the time, but don't need or require the particular guarantees (and overheads) of TCP. QUIC is a good example of this.

Java

Echo client

The client creates a DatagramSocket and uses it to connect() to the remote server. Data is sent in a DatagramPacket, with a payload in bytes. Lines read from stdin are converted to bytes (more detail in the next chapter), and are then send() to the server. A reply is read by receive(), its payload extracted and converted to a string.

If a read fails because a packet is lost, a timeout exception is triggered, and here just reports an error message.

The code is EchoClient.java


// adapted from "https://www.baeldung.com/udp-in-java

import java.net.*;
import java.io.IOException;
import java.io.*;
import java.nio.charset.StandardCharsets;


public class EchoClient {

    public static void main(String[] args){
	DatagramSocket socket = null;
	InetAddress address = null;
	int ONE_SECOND = 1000;
	int SERVER_PORT = 2000;
	
	byte[] buf;

        if (args.length != 1) {
            System.err.println("Usage: Client address");
            System.exit(1);
        }

        try {
            address = InetAddress.getByName(args[0]);
        } catch(UnknownHostException e) {
            e.printStackTrace();
            System.exit(2);
        }

        try {
            //socket = new DatagramSocket(SE
            socket = new DatagramSocket();
	    socket.connect(address, SERVER_PORT);
        } catch(IOException e) {
            e.printStackTrace();
            System.exit(3);
        }

	BufferedReader consoleReader =  
	    new BufferedReader(new InputStreamReader(System.in));
	
	String line = null;
	while (true) {
	    line = null;
	    try {
		System.out.print("Enter line:");
		line = consoleReader.readLine();
		System.out.println("Read '" + line + "'");
	    } catch(IOException e) {
		e.printStackTrace();
		System.exit(6);
	    }

	    if (line.equals("BYE"))
		break;

	    buf = line.getBytes(StandardCharsets.UTF_8);
	    DatagramPacket packet 
		= new DatagramPacket(buf, buf.length, address, 2000);
	    try {
		socket.send(packet);
	    } catch(IOException e) {
		System.err.println("Can't send to server");
		continue;
	    }
	    
	    try {
		socket.receive(packet);
	    } catch(SocketTimeoutException e) {
		System.err.println("Timeout read from server");
		continue;
	    } catch(IOException e) {
		System.err.println("Failed read from server");
		continue;
	    }
	    System.out.println(new String(packet.getData(), StandardCharsets.UTF_8));
	}
    }
}

Echo server

The following server is adapted from A Guide To UDP In Java


// adapted from "https://www.baeldung.com/udp-in-java

import java.net.*;
import java.io.IOException;

public class EchoServer {
 
    private DatagramSocket socket;
    private byte[] buf = new byte[2048];
 
    public EchoServer() {
	try {
	    socket = new DatagramSocket(2000);
	} catch(SocketException e) {
	    System.err.println("can't start server");
	    System.exit(1);
	}
	
	while (true) {
	    DatagramPacket packet 
		= new DatagramPacket(buf, buf.length);

	    try {
		socket.receive(packet);
	    } catch (IOException e) {
		// ignore
		continue;
	    }
	    
	    InetAddress address = packet.getAddress();
	    int port = packet.getPort();

	    // send using the length of data received
	    packet = new DatagramPacket(buf, packet.getLength(), address, port);

	    try {
		socket.send(packet);
	    } catch (IOException e) {
		// ignore
		continue;
	    }
	}
    }
 
    public static void main(String[] args) {
        new EchoServer();


    }
}

Resources

Go

Echo client

The client resolves a UDP address and then creates a conection using net.DailUDP(). It reads and writes to the connection. It sets a timeout on each read using SetReadDeadline().

The code is EchoClient.go:


/* UDP EchoClient
 */
package main

import (
	"fmt"
	"net"
	"os"
	"runtime"
	"bufio"
	"time"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
		os.Exit(1)
	}
	service := os.Args[1]

	console :=  bufio.NewReader(os.Stdin)

	udpAddr, err := net.ResolveUDPAddr("udp", service)
	checkError(err)

	conn, err := net.DialUDP("udp", nil, udpAddr)
	checkError(err)

	eol_len := 1 // POSIX: \n
	if runtime.GOOS == "windows" {
		eol_len = 2 // Windows: \r\n
	}
	
	for {
		line, _ := console.ReadString('\n')
		fmt.Println("Line was " + line)
		if line[:len(line) - eol_len] == "BYE" {
			break
		}

		_, err = conn.Write([]byte(line))
		checkError(err)

		conn.SetReadDeadline(time.Now().Add(15 * time.Second))
		// see https://ops.tips/blog/udp-client-and-server-in-go/#receiving-from-a-udp-connection-

		var buf [512]byte
		n, err := conn.Read(buf[0:])
		if err != nil {
			fmt.Println("Error receiving data")
			continue;
		}
		fmt.Println("received: '" + string(buf[0:n]) + "'")
	}
	os.Exit(0)
}

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
		os.Exit(1)
	}
}

Echo server

Go listens on a UDP port, and then reads from and writes to the UDP conection. The code is EchoServer.go:


/* UDPEchoServer
 */
package main

import (
	"fmt"
	"net"
	"os"
)

func main() {

	service := ":2000"
	udpAddr, err := net.ResolveUDPAddr("udp", service)
	checkError(err)

	conn, err := net.ListenUDP("udp", udpAddr)
	checkError(err)

	for {
		handleClient(conn)
	}
}

func handleClient(conn *net.UDPConn) {

	var buf [512]byte

	nread, addr, err := conn.ReadFromUDP(buf[0:])
	if err != nil {
		return
	}
	fmt.Println("received: %d", nread)
	conn.WriteToUDP(buf[:nread], addr)
}

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
		os.Exit(1)
	}
}

Rust

Echo server

Rust has a minimal UDP package in std::net::UdpSocket. This is good enough for basic use. It uses the functions recv_from() and send_to() to read and write from a UDP socket. The socket is bound to 0.0.0.0:2000 for UDP4 only or [::]:2000 for dual stack UDP4 or UDP6.

The project is created by cargo new echoserver and built and run from within the directory echoserver The file src/main.rs should contain EchoServer.rs:


//use std::io::{self, Read, Write, BufRead};
use std::net::UdpSocket;
//use std::env;
//use std::str;

fn main() -> std::io::Result<()> {
    //let socket = UdpSocket::bind("0.0.0.0:2000")?; // for UDP4
    let socket = UdpSocket::bind("[::]:2000")?;  // for UDP4/6
    let mut buf = [0; 2048];

    loop {
        // Receives a single datagram message on the socket.
	// If `buf` is too small to hold
        // the message, it will be cut off.
        let (amt, src) = socket.recv_from(&mut buf)?;

        // Redeclare `buf` as slice of the received data
	// and send data back to origin.
        let buf = &mut buf[..amt];
        socket.send_to(buf, &src)?;
    }
}

Echo client

The EchoClient is similar to the TCP case, except for the actual communication (of course!). One pecularity of the Rust implementation of UDP is the requirement to bind the client to an address/port. Even in C, you don't have to do that! I've chosen to use the wildcard address :: and the zero port to lead to an ephemeral port as [::]:0. Apart from that, it uses send_to() and recv_from() as in the server.

The code is EchoClient.rs:



use std::io::{self, BufRead};
use std::net::UdpSocket;
use std::env;
use std::str;

fn main() -> std::io::Result<()> {

    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage {} hostname", args[0]);
        std::process::exit(1);
    }
    let hostname = &args[1];

    let socket = UdpSocket::bind("[::]:0")?;  // for UDP4/6
    //socket.connect(hostname.to_string() + &":2000").expect("couldn't connect to address");

    // from https://stackoverflow.com/questions/30186037/how-can-i-read-a-single-line-from-stdin
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
	let line = line.unwrap();
	println!("Line read from stdin '{}'", line);
	if &line == "BYE" {
	    break;
	}

	socket.send_to(line.as_bytes(), hostname.to_string() + &":2000")
	    .expect("Error on send");

	let mut buf = [0; 2048];
	let (amt, _src) = socket.recv_from(&mut buf)?;

	let echo = str::from_utf8(&buf[..amt]).unwrap();
	println!("Echo {}", echo);
    }
    Ok(())
}


Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/NetworkProgramming/ .

If you like this book, please contribute using PayPal