Monday, April 28, 2014

CREATING AN UDP SERVER - I

Introduction

We in current and next post show how to implement a server application with User Datagram Protocol (UDP) which is a fast, lightweight, unreliable mode of transport of data between TCP/IP hosts. The UDP messages are sent encapsulated in IP datagrams and provide a connectionless service with no ibuild guarantee of delievery or sequence preservation.

Creating UDP Sockets

In a TCP/IP network the endpoints between which the communication takes place are called sockets. Working with sockets is very similar to working with files in the sense that both are accessed using handles called file descriptors. But there are several differences also, like sockets have addresses while files dont. Also a socket can not be accessed randomly like a file is accessed with fseek(). Below we describe the function that is used to create a linux socket:


NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include     /* See NOTES */
       #include 

       int socket(int domain, int type, int protocol);

DESCRIPTION
       socket() creates an endpoint for communication and 
       returns a descriptor.
       
       The domain argument specifies a communication domain; 
       this selects the protocol family which will be used for
       communication.  

       The type argument specifies communication semantics.

       The  protocol  specifies  a  particular protocol to be 
       used with the socket.
   

Since we are going to study an UDP/IP server, the appropriate domain is 'AF_INET' which correspond to IPv4 Internet protocols, and the type is 'SOCK_DGRM' which supports the datagrams which are connectionless, unreliable messages of fixed maximum length. The last parameter 'protocol' is important where more than one protocols support a particular socket type within a given protocol family, thence this parameter is used to specify the particular one. In our case here, since only one protocol supports 'SOCK_DGRM' of 'AF_INET' family, this parameter is set to 0.

Naming a Socket

Now once a socket is created and its file descriptor returned, we need to associate an IP address and port number to it. The port numbers and IP addresses are represented by 2 and 4 bytes of data placed in packets for purpose of routing and multiplexing. This is done using bind function:


NAME
       bind - bind a name to a socket

SYNOPSIS
       #include           /* See NOTES */
       #include 

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

DESCRIPTION
 bind() assigns the address specified by addr to the 
 socket referred to by the file  descriptor  sockfd.
 
 addrlen specifies the size, in bytes, of the address 
 structure pointed to by addr.

RETURN 
        On success : 0
 On failure : -1, errno is set 
   

Note the datatype struct sockaddr for the addr parameter:

struct sockaddr {
  sa_family_t sa_family;
  char        sa_data[14];
}
   

In actual life, the actual structure used to hold information depends on the address family. It is passed to bind after doing a cast into struct sockaddr type. Note: Most of the time IP address of the server host is not known in advance, or there may even be more than one addresses associated with this host. In such cases we may set IP address to 'INADDR_ANY', which ensures that connections to a specified port will be directed to this socket, regardless of the address the address they are sent to. And if we dont want this kind of sweeping behavior, we may use bind to specify which IP address (among many of the host) will be binded to which port number.

Receive Queries

Once bind is done, the next step is to wait and recieve message. Here we shall use recvfrom function:

NAME
      recv, recvfrom, recvmsg - receive a message from a socket

SYNOPSIS
       #include 
       #include 
       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

DESCRIPTION
       The  recvfrom()  recieve messages from a socket, and may be used 
       to receive data on a socket whether or not it is connection-oriented.
       
       buf     : buffer to hold the incoming datagram.
       src_addr: an empty sockaddr struct, to recieve sender's address.
       addrlen : this variable must be initialized to size of src_addr, and
       is modified on return to indicate actual size of the source address.

RETURN
 All three routines return the length of the message on successful 
 completion.  If a message is  too  long  to fit  in  the  supplied  
 buffer,  excess bytes may be discarded depending on the type of 
 socket the message is received from.
   

Note that when src_addr is NULL, nothing is filled in about the sender of the message and the parameter addrlen is not used and is NULL too. This mode is used in situations when we are not interested in knowing the protocol address of who sent us the data.

Serve Information to Client

After doing a recieve, a server may need to 'serve back' information back to the client. For this we will use sendto function, and here is the corresponding man page:


NAME
       send, sendto, sendmsg - send a message on a socket

SYNOPSIS
       #include 
       #include 

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

DESCRIPTION
       The system calls send(), sendto(), and sendmsg() are used to 
       transmit a message to another socket.

       sockfd: file descriptor of sending socket
       buf   : buffer to hold the send infromation.
       len   : length of the message
       flags : 
       dest_addr : Address of the destination socket.
       socklen_t : 

RETURN
       On Success: number of characters sent
       On Error  : -1, errno is set
   

Note that is is perfectly legal to write a datagram of length 0. In case of UDP this leads to an IP datagram containing an IP header, UDP header but no data. By extension this means that a return of value 0 from recvfrom is fine, and does not indicate (unlike connection oridented services) a that peer has closed connection.

Summary

We now summarize the working of a simple UDP server with following sequence of steps:

  • Create a socket object. Use socket()
  • Associate IP address and port to the socket. Use bind()
  • Receive datagram from client process. Use recvfrom()
  • Serve (if you want) datagram to client process. Use sendto()
We in our next post shall present the actual implementation of a simple echo server and explain each line. After that we shall move on to see the implementation of an UDP client process.

No comments:

Post a Comment