Você está na página 1de 24

Chapter 8

Elementary UDP Sockets

UDP is connectionless, unreliable, datagram protocol, quite unlike the connectionoriented, reliable byte stream provided by TCP.
UDP server
socket()
bind()

UDP client
socket()

recvfrom()
sendto()

data (re
q

uest)

blocks until datagram


received from client

Process request

recvfrom()
close()

data (reply)

sendto()

#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);
Both return number of bytes read or written if OK, -1 on error.
sockfd is the socket descriptor of the host machine.
For now flags is set to 0 .
to argument in sendto( ) is the socket address structure of where the data is to
be sent.
from argument in recvfrom( ) will get the socket address of from whom we have
received the datagram.
The final argument addrlen is a value-result argument.
Writing a datagram of 0 length is OK. In UDP this results in a datagram with IP
header, 8-byte UDP header and no data.
This also means that if the function recvfrom() returns the value 0, it is OK for
UDP, it does not mean that the peer has closed the connection.
With UDP, though it is connectionless, there is no such thing as closing the UDP
connection.

UDP Echo Server


stdin
stdout

fgets
fputs

UDP
Client

sendto
recvfrom

int main (int argc, char **argv)


{
int sockfd;
struct sockaddr_in servaddr, cliaddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (SA*) &servaddr, sizeof(servaddr));
dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}

recvfrom
sendto

UDP
Server

dg_echo( ) function
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
for( ; ; ) {
len = clilen;
n = recvfrom (sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
sendto (sockfd, mesg, n, 0, pcliaddr, len);
}
}

There are some details to consider about the above code:


The function never terminates.
Since UDP is connectionless, there is nothing like end-of-file as with TCP.
This is an iterative server, not a concurrent server.
The main() function is protocol dependent and dg_echo( ) function is protocol
independent.

Summary of TCP client-server with two clients

client

connection

Server
child

fork

Listening
server

fork

Server
child

connection

client

TCP
TCP

connection

connection

TCP

There are 2 TCP connected sockets and each of the two connected sockets on the
server host has its own socket receive buffer.

Summary of UDP client-server with two clients

client

server

client

Socket receive
buffer

UDP
UDP

UDP
datagram

datagram

There is only one server process and it has a single socket on which it receives
all arriving datagrams and sends all responses.
That socket has a receive buffer into which all arriving datagrams are placed.

UDP Echo Client


int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if(argc != 2)
err_quit (usage: udpcli <IPaddress>);
bzero (&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
dg_cli(stdin, sockfd, (SA*) &servaddr, sizeof(servaddr));
}

IP address of server is given as command line argument while executing the


client.

dg_cli( ) function
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
while (fgets (sendline, MAXLINE, fp) != NULL)
{
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0; //null terminate
fputs (recvline, stdout);
}
}

With UDP socket, the first time the process calls sendto(), if the socket has not
yet had a port bound to it, an ephemeral port is chosen by the kernel for the
socket.
Last two arguments in recvfrom() being NULL imply that we are not interested in
knowing the address of from whom we are receiving the datagram.

Lost Datagrams

If a client datagram is lost, 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 servers reply is lost,
the client will again block forever in its call to recvfrom( ).
The only way to prevent this is to place a timeout on the clients call to recvfrom( )
But just placing a timeout is not entire solution, coz by placing a timeout we can
not tell whether our datagram never made it to the server or the servers reply
never made it back.
If the clients request was something like transfer a certain amount of money
from account A to account B. The above two possibilities does make a lot of
difference.

Verifying Received Response

Though in UDP, anyone can send any datagram to the echo client at any moment
of time, these might get mixed with the echo server reply that the client is waiting
for.
So the client can verify that the datagram it has received is from the same IP
address to whom it had sent the echo request (echo server).

void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)


{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = malloc(servlen);
while (fgets (sendline, MAXLINE, fp) != NULL)
{
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
if (len !=servlen || memcmp(pservaddr, preply_addr, len) != 0) {
printf(reply from %s ignored\n, sock_ntop(preply_addr,len) );
continue;
}
recvline[n] = 0; //null terminate
fputs (recvline, stdout);
}
}

The problem with the previous version of dg_cli is that if the host is multihomed,
then the program can fail.
The solution will be to check for responding hosts domain name instead of IP
address.

From clients IP datagram

TCP server

UDP server

Source IP address

accept()

recvfrom( )

Source port number

accept()

recvfrom( )

Destination IP address

getsockname( )

recvmsg( )

Destination port number

getsockname( )

getsockname( )

There are 4 pieces of information that a server might want to know fro an IP
datagram that arrives : Source IP address , Source port number, Destination IP
address, Destination port number.
TCP server always has easy access to all four pieces of information. And these
values remain constants for the lifetime of a connection.
Since UDP is connectionless, the destination IP address can change for each
datagram that is sent to the server. A UDP server can also receive datagrams
destined for one of the hosts broadcast or multicast addresses.

Connect( ) function with UDP


Calling connect( ) on a UDP socket does not result in anything like a TCP threeway handshake. Instead, kernel just records the IP address and port number of
peer, which is contained in the socket address structure passed to connect.
So now we have two types:
An unconnected UDP socket, default when we create a UDP socket.
A connected UDP socket, the result of calling connect( ) on a UDP socket.
With a connected UDP socket three things change, compared to the default
unconnected UDP socket:
We can no longer specify the destination IP address and port for an output
operation. That is, we do not use sendto( ) but will use write( ) or send( )
We do not use recvfrom( ) but use read( ) or recv( ) instead. The only
datagram returned by the kernel for an input operation for a connected UDP
socket are those arriving from the protocol address specified in connect( ).
The datagrams arriving from a protocol address other than the one with
which a connection is made are not passed to the connected socket this limits
a connected UDP socket to exchanging datagrams with one and only one
peer.
Asynchronous errors are returned to the process for a connected UDP
sockets only.

A UDP client or UDP server can call connect( ) only if that process uses the UDP
socket to communicate with exactly one peer.
Calling connect( ) multiple times for a UDP socket
A process with a connected UDP socket can call connect( ) again for that socket,
to either:
Specify a new IP address and port number
Unconnect the socket
We can not call connect( ) for a TCP socket.
To unconnect a UDP socket we call connect but set the family member of the
address structure as AF_UNSPEC. This might return an error of
EAFNOSUPPORT, but that is OK.

Server Not Running

Suppose we start the client and the server is not running.


Though UDP is connectionless, client will not see any error initially, we will type in
a line and that will be sent to the server using sendto( ).
After that the client will be blocked in a call to recvfrom( ), waiting for a reply that
will never appear.
After the specified timeout, ICMP error, is generated but is not returned to the
client process.
So the client will be blocked in a call to recvfrom( ) forever.
ICMP error is an asynchronous error, i.e. it is not returned for a UDP socket
unless the socket has been connected.
The reason for making ICMP error asynchronous:
** Suppose three datagrams are being sent by a client on a UDP socket to three
different machines.
** An ICMP destination unreachable or ICMP port unreachable is generated by
one the datagrams that are sent. If this error is delivered to the client, there is no
way itll get to know which of the three datagrams sent have generated the error.
** Making the error asynchronous will make sure that the error is delivered ONLY
if a connection exist on the UDP socket, which in turn implies that the UDP socket
can receive from only one of the machines with which a connection has been
made, and thus the error must have been from that address only.

dg_cli( ) function that calls connect( )


void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
connect(sockfd, (SA*)pservaddr, servlen);
while (fgets (sendline, MAXLINE, fp) != NULL)
{
write (sockfd, sendline, strlen(sendline));
n = read(sockfd, recvline, MAXLINE);
recvline[n] = 0; //null terminate
fputs (recvline, stdout);
}
}
In TCP if the server is not running and we start the client, the ICMP error will be
generated on the call to connect( ) in the client program coz connect( ) function
call starts the three way handshake.
But in UDP, if we start the above client program and the UDP server is not
running, the ICMP error will be generated only when we send the first datagram
and not on the call to connect() function.

Flow Control in UDP

To incorporate flow control in UDP, we will change our dg_cli to write a fixed
number of bytes to the server.
And on the server side , we will make sure that dg_echo counts and prints the
number of datagrams received.

dg_cli( ) that writes 2000 1400-byte(fixed) UDP datagrams to the server.


#define NDG 2000
#define DGLEN 1400
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int i;
char sendline[MAXLINE];
for(i=0; i< NDG; i++)
sendto(sockfd, sendline, DGLEN, 0 , pservaddr, servlen);
}

dg_echo() that counts and prints number of datagrams received from d client.
static void recvfrom_int(int);
static int count;
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
socklen_t len;
char mesg[MAXLINE];
signal(SIGINT, recvfrom_int);
for( ; ; ){
len=clilen;
recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
count++;
}
}
static void recvfrom_int(int signo)
{
printf(received %d datagrams\n, count);
}
Though in UDP a program never terminates, well have to explicitly terminate the
server with terminal interrupt, which will generate the SIGINT signal.
We want to catch the SIGINT signal and call the signal handler function
recvfrom_int( ), this is done by signal( ) function.

Another aspect in flow control is the size of the UDP socket receive buffer.
The number of UDP datagrams that are queued up by UDP for a given socket is
limited by the size of the socket receive buffer. We can change this using
SO_RCVBUF socket option.
dg_echo() that increases the size of the socket receive queue
void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
signal(SIGINT, recvfrom_int);
n = 240 * 1024;
setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
for( ; ; ){
len=clilen;
recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
count++;
}
}
static void recvfrom_int(int signo)
{
printf(received %d datagrams\n, count);
}

UDP Echo Client that uses connect( ) to determine outgoing interface(address)


int main(int argc, char **argv)
{
int sockfd;
socklen_t len;
struct sockaddr_in servaddr, cliaddr;
if(argc != 2)
err_quit (usage: udpcli <IPaddress>);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero (&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
connect(sockfd, (SA *)&servaddr, sizeof(servaddr));
len = sizeof(cliaddr);
getsockname(sockfd, (SA *)&cliaddr, &len);
printf(local address %s\n, sock_ntop((SA *)&cliaddr, len));
}

UDP Echo server that handles TCP an UDP both using select( )
int main(int argc, char **argv)
{
int listenfd, connfd, udpfd, nready, maxfdp1;
char mesg[MAXLINE];
pid_t childpid;
fd_set rset;
ssize_t n;
socklen_t len;
const int on = 1;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
//create listening TCP socket
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);

//create UDP socket


udpfd = socket(AF_INET, SOCK_DGRAM,0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(udpfd, (SA*)&servaddr, sizeof(servaddr));
signal(SIGCHLD, sig_chld);
FD_ZERO(&rset);
maxfdp1 = max(listenfd,udpfd) + 1;
for( ; ;) {
FD_SET(listenfd, &rset);
FD_SET(udpfd, &rset);
if((nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
if(errno == EINTR)
continue; //back to for()
else
err_sys(select error);
}

if(FD_ISSET(listenfd, &rset)) {
//tcp socket is ready
len = sizeof(cliaddr);
connfd = accept(listenfd, (SA*)&cliaddr, &len);
if((childpid = fork()) == 0) {
//child process
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
if(FD_ISSET(udpfd, &rset)) {
//udp socket is ready
len = sizeof(cliaddr);
n = recvfrom(udpfd, mesg, MAXLINE, 0 (SA *)&cliaddr, &len);
sendto(udpfd, mesg, n, 0, (SA*)&cliaddr, len);
}
}
}

Você também pode gostar