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.
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));
}
}
}
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();
}
}
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)
}
}
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 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)?;
}
}
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
" 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/
.