Skip to content

Instantly share code, notes, and snippets.

@staticfloat
Last active January 4, 2023 21:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save staticfloat/fbe63faf52968a7e00378db12293247e to your computer and use it in GitHub Desktop.
Save staticfloat/fbe63faf52968a7e00378db12293247e to your computer and use it in GitHub Desktop.
UDP latency loopback test

With no sleep enabled, I consistently get extremely low send/recv times:

$ gcc -o udp_loopback_test_c udp_loopback_test.c && ./udp_loopback_test_c 
[1.0] send: 4.93us      recv: 7.41us
[2.0] send: 4.68us      recv: 7.04us
[3.0] send: 4.86us      recv: 7.58us
[4.0] send: 4.79us      recv: 7.60us
[5.0] send: 4.88us      recv: 7.03us
[6.0] send: 4.70us      recv: 7.57us
[7.0] send: 4.49us      recv: 8.02us
[8.0] send: 4.47us      recv: 7.23us
[9.0] send: 4.58us      recv: 7.15us

With the sleep enabled, I get much higher latency and higher variance in the timings:

$ gcc -DENABLE_SLEEP -o udp_loopback_test_c udp_loopback_test.c && ./udp_loopback_test_c 
[1.0] send: 23.85us     recv: 102.13us
[2.0] send: 35.41us     recv: 78.07us
[3.0] send: 70.47us     recv: 141.07us
[4.0] send: 29.90us     recv: 107.35us
[5.0] send: 45.17us     recv: 194.27us
[6.0] send: 32.49us     recv: 117.74us
[7.0] send: 32.25us     recv: 117.83us
[8.0] send: 35.48us     recv: 85.67us
[9.1] send: 33.86us     recv: 108.71us

This is consistent across languages (C, Rust) and operating systems (macOS, Linux)

// Server side implementation of UDP client-server model
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <time.h>
#include <pthread.h>
#define SERVER_PORT 5132
#define CLIENT_PORT 5133
double curr_timestamp() {
struct timespec monotime;
clock_gettime(CLOCK_MONOTONIC, &monotime);
return monotime.tv_sec + monotime.tv_nsec/1000000000.0;
}
// Driver code
void * server(void * dummy) {
struct sockaddr_in servaddr = {0}, cliaddr = {0};
// Creating socket file descriptor
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Filling server information
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(SERVER_PORT);
// Bind the socket with the server address
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
socklen_t len = sizeof(cliaddr);
char buffer[128] = {0};
double t_start = curr_timestamp();
while (1) {
int num_bytes = recvfrom(sockfd, (char *)buffer, sizeof(buffer), 0, (struct sockaddr *) &cliaddr, &len);
sendto(sockfd, (const char *)NULL, 0, 0, (const struct sockaddr *) &cliaddr, len);
}
return NULL;
}
void client() {
struct sockaddr_in servaddr = {0}, cliaddr = {0};
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(SERVER_PORT);
// Connect to speed up transmission
if (connect(sockfd, (const struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
perror("socket connect failed");
exit(EXIT_FAILURE);
}
// Keep track of some temporal characteristics
double t_start = curr_timestamp();
double last_print = t_start;
double avg_send_elapsed = 0.0, avg_recv_elapsed = 0.0;
// This buffer is useless, we never send or receive anything.
char buffer[128];
while (1) {
// This sleep causes a large increase in latency
//usleep(10000);
double t_pre_send = curr_timestamp();
if (send(sockfd, NULL, 0, 0) == -1) {
perror("send failed");
}
avg_send_elapsed = 0.9*avg_send_elapsed + 0.1*(curr_timestamp() - t_pre_send);
double t_pre_recv = curr_timestamp();
socklen_t len = 0;
int num_bytes = recvfrom(sockfd, (char *)buffer, sizeof(buffer), 0, (struct sockaddr *) &cliaddr, &len);
avg_recv_elapsed = 0.9*avg_recv_elapsed + 0.1*(curr_timestamp() - t_pre_recv);
double curr_time = curr_timestamp();
if (curr_time - last_print > 1.0) {
last_print = curr_time;
printf("[%.1f] send: %.2fus\trecv: %.2fus\n", curr_time - t_start, avg_send_elapsed*1000000, avg_recv_elapsed*1000000);
}
}
}
int main() {
pthread_t server_thread;
if (pthread_create(&server_thread, NULL, server, NULL) != 0) {
perror("Unable to create server thread!\n");
exit(EXIT_FAILURE);
}
client();
return 0;
}
use std::thread;
use std::time::{Duration,Instant};
use std::net::UdpSocket;
use lazy_static::lazy_static;
use std::io;
lazy_static! {
static ref T_START: Instant = Instant::now();
}
pub fn curr_timestamp() -> f64 {
return Instant::now().duration_since(*T_START).as_secs_f64();
}
const client_port: i64 = 5133;
const server_port: i64 = 5132;
fn server() {
let server = UdpSocket::bind(format!("localhost:{server_port}"))
.expect("failed to bind timeserver socket");
println!("Listening on port {server_port}");
let mut buff: [u8; 1] = [0];
loop {
let (_nbytes, src_addr) = server.recv_from(&mut buff)
.expect("unable to receive timserver packet");
if let Err(r) = server.send_to(&buff, &src_addr) {
println!("Unable to send response packet to {src_addr}, skipping\n");
println!("{r}");
continue;
}
}
}
fn client() {
// Create UDP socket, connecting to the time server
let client = UdpSocket::bind(format!("localhost:{client_port}"))
.expect("failed to bind timeserver socket");
// We'll never wait more than 100ms to receive a response from the server
client.set_read_timeout(Some(Duration::new(0, 100*1000*1000))).expect("failed to set timeout");
// We connect to avoid extra work on each UDP transmission
client.connect(format!("localhost:{server_port}")).expect("failed to connect");
let mut buff: [u8; 1] = [0];
let t_start = curr_timestamp();
let mut last_print = t_start;
let mut avg_send_elapsed = 0.0;
let mut avg_recv_elapsed = 0.0;
loop {
// This sleep causes a large increase in latency
//thread::sleep(Duration::from_millis(1));
let t_pre_send = curr_timestamp();
client.send(&buff).expect("failed to send request");
avg_send_elapsed = avg_send_elapsed * 0.9 + (curr_timestamp() - t_pre_send) * 0.1;
let t_pre_recv = curr_timestamp();
let (_nbytes, _src_addr) = match client.recv_from(&mut buff) {
Ok(ret) => ret,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
},
Err(e) => {
panic!("failed to receive reply: {e}")
}
};
avg_recv_elapsed = avg_recv_elapsed * 0.9 + (curr_timestamp() - t_pre_recv) * 0.1;
let curr_time = curr_timestamp();
if curr_time - last_print > 1.0 {
last_print = curr_time;
println!("[{:.1}s] send: {:.4}us\trecv: {:.4}us", curr_time, avg_send_elapsed*1000_000.0, avg_recv_elapsed*1000_000.0);
}
}
}
/// Testing tool for measuring `UdpSocket` latency
fn main() {
println!("Testing std::net::UdpSocket latency on loopback:");
thread::spawn(server);
client();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment