Unit IV-ElementaryUDPSocket2015
Unit IV-ElementaryUDPSocket2015
These two functions are similar to the standard read and write functions, but three
additional arguments are required.
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from,
socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct
sockaddr *to, socklen_t addrlen);
The first three arguments, sockfd, buff, and nbytes, are identical to the first three arguments
for read and write: descriptor, pointer to buffer to read into or write from, and number of bytes to
read or write.
The to argument for sendto is a socket address structure containing the protocol address
(e.g., IP address and port number) of where the data is to be sent. The size of this socket address
structure is specified by addrlen.
The recvfrom function fills in the socket address structure pointed to by from with the
protocol address of who sent the datagram. The number of bytes stored in this socket address
structure is also returned to the caller in the integer pointed to by addrlen.
Note that the final argument to sendto is an integer value, while the final argument to
recvfrom is a pointer to an integer value (a value-result argument).
The final two arguments to recvfrom are similar to the final two arguments to
accept: The contents of the socket address structure upon return tell us who sent the datagram (in
the case of UDP) or who initiated the connection (in the case of TCP).
The final two arguments to sendto are similar to the final two arguments to connect:
the socket address structure is filled with the protocol address of where to send the datagram (in
the case of UDP) or with whom to establish a connection (in the case of TCP). Both functions
return the length of the data that was read or written as the value of the function. In the typical
use of recvfrom, with a datagram protocol, the return value is the amount of user data in the
datagram received. Writing a datagram of length 0 is acceptable. In the case of UDP, this results
in an IP datagram containing an IP header (normally 20 bytes for IPv4 and 40 bytes for IPv6), an
8-byte UDP header, and no data. This also means that a return value of 0 from recvfrom is
acceptable for a datagram protocol: It does not mean that the peer has closed the connection, as
does a return value of 0 from read on a TCP socket. Since UDP is connectionless, there is no
such thing as closing a UDP connection. If the from argument to recvfrom is a null pointer, then
the corresponding length argument ( addrlen) must also be a null pointer, and this indicates that
we are not interested in knowing the protocol address of who sent us data.
UDP Echo Server: main Function
Figure 1. Simple echo client/server using UDP.
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4{
5 int sockfd;
6 struct sockaddr_in servaddr;
7 if(argc != 2)
8 err_quit("usage: udpcli <IPaddress>");
9 bzero(&servaddr, sizeof(servaddr));
10 servaddr.sin_family = AF_INET;
11 servaddr.sin_port = htons(SERV_PORT);
12 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
13 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
14 dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));
15 exit(0);
16 }
Fill in socket address structure with server's address
An IPv4 socket address structure is filled in with the IP address and port
number of the server. This structure will be passed to dg_cli, specifying
where to send datagrams. A UDP socket is created and the function dg_cli
is called.
1 #include "unp.h"
2 void
3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
4{
5 int n;
6 char sendline[MAXLINE], recvline[MAXLINE + 1];
7 while (Fgets(sendline, MAXLINE, fp) != NULL) {
8 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
9 n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
10 recvline[n] = 0; /* null terminate */
11 Fputs(recvline, stdout);
12 }
13 }
There are four steps in the client processing loop:
Our client has not asked the kernel to assign an ephemeral port to its socket. (With a TCP client,
we said the call to connect is where this takes place.) With a UDP socket, the first time the
process calls sendto, if the socket has not yet had a local port bound to it, that is when an
ephemeral port is chosen by the kernel for the socket. As with TCP, the client can
call bind explicitly, but this is rarely done. Notice that the call to recvfrom specifies a null
pointer as the fifth and sixth arguments. This tells the kernel that we are not interested in
knowing who sent the reply. There is a risk that any process, on either the same host or some
other host, can send a datagram to the client's IP address and port, and that datagram will be read
by the client, who will think it is the server's reply. As with the server function dg_echo, the
client function dg_cli is protocol-independent, but the client main function is protocol-
dependent. The main function allocates and initializes a socket address structure of some
protocol type and then passes a pointer to this structure, along with its size, to dg_cli.
Lost Datagrams
Our UDP client/server example is not reliable. If a client datagram is lost (say it is discarded by
some router between the client and server), the client will block forever in its call to recvfrom in
the function dg_cli, waiting for a server reply that will never arrive. Similarly, if the client
datagram arrives at the server but the server's reply is lost, the client will again block forever in
its call to recvfrom. A typical way to prevent this is to place a timeout on the client's call to
recvfrom.
Just placing a timeout on the recvfrom is not the entire solution. For example, if we do time out,
we cannot tell whether our datagram never made it to the server, or if the server's reply never
made it back. If the client's request was something like "transfer a certain amount of money from
account A to account B" (instead of our simple echo server), it would make a big difference as to
whether the request was lost or the reply was lost. So required to add reliability to a UDP
client/server.
Summary of UDP Example
Figure 1 shows as bullets the four values that must be specified or chosen when the client
sends a UDP datagram.
Figure 1. Summary of UDP client/server from client's perspective.
The client must specify the server's IP address and port number for the call to sendto.
Normally, the client's IP address and port are chosen automatically by the kernel, although
we mentioned that the client can call bind if it so chooses. If these two values for the client
are chosen by the kernel, we also mentioned that the client's ephemeral port is chosen
once, on the first sendto, and then it never changes. The client's IP address, however, can
change for every UDP datagram that the client sends, assuming the client does not bind a
specific IP address to the socket. The reason is shown in Figure 8.11: If the client host is
multihomed, the client could alternate between two destinations, one going out the datalink
on the left, and the other going out the datalink on the right. In this worst-case scenario, the
client's IP address, as chosen by the kernel based on the outgoing datalink, would change
for every datagram. What happens if the client binds an IP address to its socket, but the
kernel decides that an outgoing datagram must be sent out some other datalink? In this
case the IP datagram will contain a source IP address that is different from the IP address of
the outgoing datalink.
The client sent 2,000 datagrams, but the server application received only 30 of these, for a 98%
loss rate. There is no indication whatsoever to the server application or to the client application
that these datagrams were lost. As said, UDP has no flow control and it is unreliable. It is trivial,
as shown, for a UDP sender to overrun the receiver. If looked at the netstat output, the total
number of datagrams received by the server host (not the server application) is 2,000 (73,208 -
71,208). The counter "dropped due to full socket buffers" indicates how many datagrams were
received by UDP but were discarded because the receiving socket's receive queue was full.This
value is 1,970 (3,491 - 1,971), which when added to the counter output by the application (30),
equals the 2,000 datagrams received by the host. Unfortunately, the netstat counter of the number
dropped due to a full socket buffer is systemwide. There is no way to determine which
applications (e.g., which UDP ports) are affected.
The number of datagrams received by the server in this example is not predictable. It depends on
many factors, such as the network load, the processing load on the client host, and the
processing load on the server host. If run the same client and server, but this time with the
client on the slow side and the server on the faster RS/6000, no datagrams are lost.
aix % udpserv06
^? we type our interrupt key after the client is finished
received 2000 datagrams
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4{
5 int sockfd;
6 socklen_t len;
7 struct sockaddr_in cliaddr, servaddr;
8 if (argc != 2)
9 err_quit("usage: udpcli <IPaddress>");
10 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
11 bzero(&servaddr, sizeof(servaddr));
12 servaddr.sin_family = AF_INET;
13 servaddr.sin_port = htons(SERV_PORT);
14 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
15 Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
16 len = sizeof(cliaddr);
17 Getsockname(sockfd, (SA *) &cliaddr, &len);
18 printf("local address %s\n", Sock_ntop((SA *) &cliaddr, len));
19 exit(0);
20 }
If the program is runned on the multihomed host freebsd, the following output:
freebsd % udpcli09 206.168.112.96
local address 12.106.32.254:52329
freebsd % udpcli09 192.168.42.2
local address 192.168.42.1:52330
freebsd % udpcli09 127.0.0.1
local address 127.0.0.1:52331.
The first time the program run, the command-line argument is an IP address that follows the
default route. The kernel assigns the local IP address to the primary address of the interface to
which the default route points.
The second time, the argument is the IP address of a system connected to a second Ethernet
interface, so the kernel assigns the local IP address to the primary address of this second
interface. Calling connect on a UDP socket does not send anything to that host; it is entirely a
local operation that saves the peer's IP address and port. Can see that calling connect on an
unbound UDP socket also assigns an ephemeral port to the socket. Unfortunately, this technique
does not work on all implementations, mostly SVR4-derived kernels. For example, this does not
work on Solaris 2.5, but it works on AIX, HP-UX 11, MacOS X, FreeBSD, Linux, and Solaris
2.6 and later.