UDP Socket Programming UDP Socket Programming An Introduction with Examples in C Prof. David Bernstein James Madison University Computer Science Department bernstdh@jmu.edu Review UDP: Not connection-orieneted Message-oriented Unreliabale Internet Programming Basics: Byte order Addresses and ports UDP Programming Both Parties: socket() The Receiving Party: bind() recvfrom() The Sending Party: sendto() Creating a Socket: socket() int socket(int domain, int type, int protocol) Purpose: Create a socket Details: domain AF_INET or AF_INET6 for IP type SOCK_DGRAM for UDP protocol 0 for UDP over IP Return The file descriptor on success; -1 on error #include
Setting-Up for Receiving Conceptually: The socket can be bound to any IP address on the host The socket must be bound to a particular port so that it can be "advertised" Initializing the sockaddr:
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(WELL_KNOWN_PORT); // For this service
address.sin_addr.s_addr = htonl(INADDR_ANY);
Binding to an Address and/or Port: bind() int bind(int fd, const struct sockaddr *addr, socklen_t addrlen) Purpose: Associate a socket with a particular address and/or port Details: fd The file descriptor of a socket addr The address/port to bind to addrlen The length of the address Return 0 on success; -1 on error #include Receiving a Datagram: recvfrom() ssize_t recvfrom(int fd, void *buffer, size_t buflen, int flags, struct sockaddr *src_addr, socklen_t *addrlen) Purpose: Receive a UDP datagram Details: fd The file descriptor of the UDP socket buffer The buffer to fill with the datagram's payload length The length of the buffer flags Flags (often 0) src_addr The address the datagram was sent from addrlen The length of the source's address Return The number of bytes received on success; -1 on error #include A Simple Sequential Text "Receiver" unixexamples/udp/weather_recorder.c
#include // For byte order conversions
#include // For memset()
#include
#include
#include "weather.h"
int
main(void)
{
char buffer[STATION_SIZE+TEMPERATURE_SIZE];
int fd, keep_going;
struct sockaddr_in address;
// Create a IP4/UDP socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address (of this host)
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(TEMPERATURE_PORT);
address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple
// Bind the socket to the address
bind(fd, (struct sockaddr *)&address, sizeof(struct sockaddr));
keep_going = 1;
while (keep_going)
{
// Receive the message (we don't care about the address of the sender)
recvfrom(fd, buffer, STATION_SIZE+TEMPERATURE_SIZE, 0, NULL, NULL) ;
if (buffer[3] == '!')
{
keep_going = 0;
}
else
{
write(STDOUT_FILENO, "Report from ", 12);
write(STDOUT_FILENO, buffer, STATION_SIZE);
write(STDOUT_FILENO, ": ", 2);
write(STDOUT_FILENO, buffer+STATION_SIZE, TEMPERATURE_SIZE);
write(STDOUT_FILENO, "\n\n", 2);
}
}
close(fd);
}
A Simple Sequential Text "Receiver" (cont.) A Conceptual Question: Is this "receiver" a client, a server, or neither? An Answer and Explanation: Because its role is to wait for and respond to a request (i.e., to record data), it is a server Setting-Up for Sending Conceptually: The datagram can be sent from any address/port so the socket needn't be bound The datagram must be sent to a particular address/port (so that information will have to be passed to the function that does the sending) Initializing the Destination sockaddr:
struct sockaddr_in destination;
memset(&destination, 0, sizeof(struct sockaddr_in));
destination.sin_family = AF_INET;
inet_pton(AF_INET, RECEIVER_ADDRESS, &(destination.sin_addr));
destination.sin_port = htons(RECEIVER_PORT);
Sending a Datagram: sendto() ssize_t sendto(int fd, const void *buffer, size_t buflen, int flags, const struct sockaddr *dest_addr, socklen_t *addrlen) Purpose: Send a UDP datagram to a particular address and port Details: fd The file descriptor of the UDP socket buffer The datagram's payload length The length of the payload flags Flags (often 0) dest_addr The address to send the datagram to addrlen The length of the destination address Return The number of bytes sent on success; -1 on error #include A Simple Text "Sender" unixexamples/udp/weather_station.c
#include // For byte order conversions
#include // For memset()
#include
#include
#include "weather.h"
int
main(int argc, char *argv[])
{
char buffer[STATION_SIZE+TEMPERATURE_SIZE];
char station[] = "JMU";
char temperature[TEMPERATURE_SIZE+1];
int fd, keep_going;
ssize_t input_length;
struct sockaddr_in destination;
if (argc > 1) strncpy(station, argv[1], STATION_SIZE);
// Create a IP4/UDP socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address of the receiver
memset(&destination, 0, sizeof(struct sockaddr_in));
destination.sin_family = AF_INET;
inet_pton(AF_INET, "134.126.125.234", &(destination.sin_addr));
destination.sin_port = htons(TEMPERATURE_PORT);
keep_going = 1;
while (keep_going)
{
write(STDOUT_FILENO, "Temperature (or [Enter] to QUIT): ", 35);
// Clear the outgoing payload
memset(buffer, 0, STATION_SIZE+TEMPERATURE_SIZE);
// Populate the outgoing payload
strncpy(buffer, station, 3);
input_length = read(STDIN_FILENO, temperature, TEMPERATURE_SIZE+1);
if (input_length > 1)
{
strncpy(buffer+3, temperature, input_length-1);
}
else
{
strncpy(buffer+3, "!!!!!", 5);
keep_going = 0;
}
// Send the datagram
sendto(fd, buffer, STATION_SIZE+TEMPERATURE_SIZE, 0,
(struct sockaddr *)&destination, sizeof(destination)) ;
}
close(fd);
}
Sending/Receiving and Clients/Servers A Conceptuial Question: Are all "senders" clients and all "receivers" servers? An Answer and Explanation: No! In fact, both clients and servers often both send and receive. A Server that Receives and Sends An Observation: In the example above, all communication is one-way A Different Example: The client requests a forecast for a particular location (using UDP) and the server responds (again using UDP) A Server that Receives and Sends (cont.) unixexamples/udp/forecast_server.c
#include
#include
#include
#include
#include
#include
#include "weather.h"
int
main(void)
{
char in[STATION_SIZE], out[TEMPERATURE_SIZE];
int fd;
struct sockaddr_in address, client_address;
socklen_t client_length = sizeof(struct sockaddr);
// Create an IP4/UDP socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address of this host
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(FORECAST_PORT);
address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple
// Bind the socket to the address
bind(fd, (struct sockaddr *)&address, sizeof(struct sockaddr));
// Process requests
while (1)
{
// Receive the request
recvfrom(fd, in, STATION_SIZE, 0,
(struct sockaddr *)&client_address, &client_length);
// Send the response
memset(&out, 0, TEMPERATURE_SIZE);
sprintf(out, "%5.1f", forecast_for(in));
sendto(fd, out, TEMPERATURE_SIZE, 0,
(struct sockaddr *)&client_address, client_length);
}
close(fd);
}
A Client that Sends and Receives unixexamples/udp/forecast_client.c
#include
#include
#include
#include
#include "weather.h"
int
main(int argc, char *argv[])
{
char in[TEMPERATURE_SIZE];
char out[] = "JMU";
int fd;
struct sockaddr_in server_address;
if (argc > 1) strncpy(out, argv[1], STATION_SIZE);
// Create an IP4/UDP socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address of the server
memset(&server_address, 0, sizeof(struct sockaddr_in));
server_address.sin_family = AF_INET;
inet_pton(AF_INET, "134.126.125.234", &(server_address.sin_addr));
server_address.sin_port = htons(FORECAST_PORT);
// Send the request
sendto(fd, out, STATION_SIZE, 0,
(struct sockaddr *)&server_address, sizeof(server_address)) ;
// Receive the response
recvfrom(fd, in, TEMPERATURE_SIZE, 0, NULL, NULL);
// Display the respons
write(STDOUT_FILENO, "Forecast: ", 10);
write(STDOUT_FILENO, in, TEMPERATURE_SIZE);
write(STDOUT_FILENO, "\n", 1);
close(fd);
}
Message Formats An Observation: In the examples above, all of the messages contain text The Rationale: The byte order doesn't matter and text is always represented using ASCII or Unicode An Alternative: Use common binary representations and network byte order A struct for a Binary Message unixexamples/udp/weather.h
#ifndef WEATHER_H
#define WEATHER_H
#define FORECAST_PORT 22801
#define RANGE_PORT 22802
#define TEMPERATURE_PORT 22807
#define TEMPERATURE_SIZE 5
#define STATION_SIZE 3
struct range {
uint16_t low;
uint16_t high;
};
float
forecast_for(char *station);
void
hton_range(const struct range *h, struct range *n);
void
ntoh_range(const struct range *n, struct range *h);
#endif
A Server with a Binary Message unixexamples/udp/range_server.c
#include
#include
#include
#include
#include
#include
#include "weather.h"
int
main(void)
{
char in[STATION_SIZE];
int fd;
struct range current, out;
struct sockaddr_in address, client_address;
socklen_t client_length = sizeof(struct sockaddr);
// Create a IP4/UDP socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address (of this host)
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(RANGE_PORT);
address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple
// Bind the socket to the address
bind(fd, (struct sockaddr *)&address, sizeof(struct sockaddr));
// Process requests
while (1)
{
// Receive the request
recvfrom(fd, in, STATION_SIZE, 0,
(struct sockaddr *)&client_address, &client_length);
// Create the response
current.low = (uint16_t)forecast_for(in);
current.high = (uint16_t)forecast_for(in);
// Convert the respons to network byte order
hton_range(¤t, &out);
// Send the response
sendto(fd, &out, sizeof(struct range), 0,
(struct sockaddr *)&client_address, client_length);
}
close(fd);
}
A Client with a Binary Message unixexamples/udp/range_client.c
#include // For byte order conversions
#include
#include // For memset()
#include
#include
#include "weather.h"
int
main(int argc, char *argv[])
{
char out[] = "JMU";
int fd;
struct range current, in;
struct sockaddr_in server_address;
if (argc > 1) strncpy(out, argv[1], STATION_SIZE);
// Create a IP4/UDP socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address of the server
memset(&server_address, 0, sizeof(struct sockaddr_in));
server_address.sin_family = AF_INET;
inet_pton(AF_INET, "134.126.125.234", &(server_address.sin_addr));
server_address.sin_port = htons(RANGE_PORT);
// Send the request
sendto(fd, out, STATION_SIZE, 0,
(struct sockaddr *)&server_address, sizeof(server_address)) ;
// Receive the response
recvfrom(fd, &in, TEMPERATURE_SIZE, 0, NULL, NULL);
// Convert from network byte order to host byte order
ntoh_range(&in, ¤t);
// DIsplay the converted response
printf("Low: %d\n", current.low);
printf("High: %d\n", current.high);
close(fd);
}
Sequential and Concurrent Servers A Question You Should Have Asked: What happens if a server receives a request while it is processing another request? The Answer: The request is buffered Sequential and Concurrent Servers (cont.) The Implication: The server only handles one request "at a time" Using Concurrent Processing to Handle Multiple Requests: Use multiple processes Use multiple threads A Server with Multiple Processes unixexamples/udp/multiprocessed_forecast_server.c
#include
#include
#include
#include
#include
#include
#include
#include "weather.h"
int
main(void)
{
char in[STATION_SIZE], out[TEMPERATURE_SIZE];
int ifd, ofd, pid;
struct sockaddr_in address, client_address;
socklen_t client_length = sizeof(struct sockaddr);
// Create an IP4/UDP socket
ifd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address of this host
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(FORECAST_PORT);
address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple
// Bind the socket to the address
bind(ifd, (struct sockaddr *)&address, sizeof(struct sockaddr));
// Handle requests (each in its own child process)
while (1)
{
// Receive the request
recvfrom(ifd, in, STATION_SIZE, 0,
(struct sockaddr *)&client_address, &client_length);
// Create the child process
pid = fork();
if (pid == 0)
{
// Close the child's copy of the incoming socket
close (ifd);
// Send the response
memset(&out, 0, TEMPERATURE_SIZE);
sprintf(out, "%5.1f", forecast_for(in));
ofd = socket(AF_INET, SOCK_DGRAM, 0);
sendto(ofd, out, TEMPERATURE_SIZE, 0,
(struct sockaddr *)&client_address, client_length);
close(ofd);
exit(0);
}
}
close(ifd);
}
A Server with Multiple Threads unixexamples/udp/threaded_forecast_server.c
#include
#include
#include
#include
#include
#include
#include
#include "weather.h"
// The arguments needed to send a response
struct thread_arguments {
char station[STATION_SIZE];
struct sockaddr client_address;
socklen_t client_length;
};
// The entry point for the threads
static void
*send_response(void *arg)
{
char out[TEMPERATURE_SIZE];
int fd;
struct thread_arguments *args;
// Cast the arguments
args = (struct thread_arguments *)arg;
memset(&out, 0, TEMPERATURE_SIZE);
sprintf(out, "%5.1f", forecast_for(args->station));
fd = socket(AF_INET, SOCK_DGRAM, 0);
sendto(fd, out, TEMPERATURE_SIZE, 0, &(args->client_address), args->client_length);
close(fd);
// Free the memory used by the arguments
// (Caller Allocates/Callee Frees)
free(args);
return NULL;
}
int
main(void)
{
char in[STATION_SIZE];
int fd;
pthread_t helper;
struct sockaddr_in address, client_address;
struct thread_arguments *args;
socklen_t client_length = sizeof(struct sockaddr);
// Create an IP4/UDP socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
// Initialize the address of this host
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(FORECAST_PORT);
address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple
// Bind the socket to the address
bind(fd, (struct sockaddr *)&address, sizeof(struct sockaddr));
// Process requests
while (1)
{
// Receive the request
recvfrom(fd, in, STATION_SIZE, 0,
(struct sockaddr *)&client_address, &client_length);
// Make a copy of the message and the client address
// for the thread that will handle the response
// (Caller Allocates/Callee Frees)
args = (struct thread_arguments *)malloc(sizeof(struct thread_arguments));
strncpy(args->station, in, STATION_SIZE);
memcpy(&(args->client_address), &client_address, client_length);
args->client_length = client_length;
// Create a thread to handle the response
pthread_create(&helper, NULL, send_response, (void *)args);
pthread_detach(helper);
}
close(fd);
}
Improving Concurrent Servers Pre-Forking: Instead of child processes when needed, create them at start-up (and place them in a pool) Connections are placed in a queue When a child process is done with a connection it doesn't terminate; instead it retrieves the next connection in the queue Pre-Threading: The same idea, but using threads instead of processes Socket Options: setsockopt() int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) Purpose: Set socket options Details: fd The file descriptor of the UDP socket level SOL_SOCKET, IPPROTO_IP, ... optname The option to set optval The value of the option optlen The size of the optval argument Return 0 on success; -1 on error #include Socket Options (cont.) Transaction-Related UDP Option Names: SO_SNDTIMEO and SO_RCVTIMEO allow you to set timeouts (i.e., how long a call will block before failing) SO_LINGER allows the socket to delay closing if there are data to send Setup-Related UDP Option Names: SO_REUSEADDR and SO_REUSEPORT allow you to re-use an address and/or port even if they are already bound (which can be useful when a server fails and must be restarted) SO_RCVBUF and SO_SNDBUF allow you to change buffer sizes Socket Options (cont.) A Partial Example
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval));