Sockets

Introduction in sockets

For communication over a network, almost every OS provides an API called the Sockets API. The diagram below shows the relation between the process, the socket and the network.

Principles behind the Socket API

As can be seen from the illustration, your application only talks to the socket through a file descriptor; like your average file. The programmer doesn't have to deal with protocol-specific stuff. The programmer only has to define two things: the type of socket and the protocol family.

Socket types

There are three types of sockets: stream sockets, datagram sockets and raw sockets. The last one will not be discussed here, see resources.

Stream sockets, SOCK_STREAM, which makes a connection, provides a two-way path for the data and then closes the connection. Applications for this are WWW Browsers, FTP, telnet, etc. Data is guaranteed for contents, delivery and the order it arrives.

Datagram sockets, SOCK_DGRAM, which is connectionless and just puts an adress on every packet it sends. Applications like tftp, BOOTP and router messages use datagrams. The delivery nor the order is guaranteed, but the contents are protected by a checksum. Any delivery or ordering check has to be done by the application.

Protocol Family

The protocol family, (also called namespace or domain) is the complete set of protocols which are used for communication over the network. Protocol families are TCP/IP, IPX/SPX, X.25, and many others. Each one has a constant (however this does not mean it's implemented). We will only look at the TCP/IP protocol family, also known as the internet namespace.

Overview of setting up sockets

The procedure for a server is as follows:

  1. Create a socket with socket(), which returns a file descriptor. socket() is the equivalent of installing a telephone wallport.
  2. Define which port to use with bind(). This function could be seen as the equivalent of asking your telephone company to assign a telephone number to the equipment at your address.
  3. Now call listen(), just like you plug in your telephone equipment in the wallport - ready to receive any calls.
  4. Finally, call accept(), start your answering machine which picks up the horn when anyone calls.

A client would not listen() and accept(), instead it would connect() to another socket.

When a connection is established, data can be send using send() and recv().

Now we have the general idea, we can examine these functions more carefully. But: the functions are not the problem. To pass the information to the functions, a couple of structs are used and this is quite awful if you do not know enough about them.

The structs

The place holder sockaddr

The following information tells you how to set up certain structs. These structs are specially designed as parameters to the above explained functions. Here they are, along with their purpose.

 struct sockaddr
 { 
       unsigned short    sa_family;   /* address format, AF_xxx       */
       char              sa_data[14]; /* 14 bytes of protocol address */
 };
    

The sa_family member is the adress format. It depends on the namespace as defined by the socket() call. If the protocol family is PF_ABCD, then the address format is AF_ABCD. The other member sa_data contains a port number as well as an internet address. The whole struct is really just a place holder for structs which are specialized for their purpose.

That is why a special struct is invented for each protocol family. These particular structs can be cast to your average struct sockaddr when you call a particular function. As an example, we will look at struct sockaddr_in and struct sockaddr_un.

The internet address holder struct sockaddr_in

 struct sockaddr_in 
 {
       sa_family_t     sin_family;         /* address format: AF_INET */
       u_int16_t       sin_port;        /* port in network byte order */
       struct in_addr  sin_addr;                  /* internet address */
 };

Two things are important with this one: one is that some systems store their bytes in another way than some other systems. Whatever your system, the bytes in the members sin_port and sin_addr have to be stored in Network Byte Order. If you keep this in mind, this is no problem because there are library functions which do this job. Check your man pages for htons(), htonl(), ntohs() and ntohl().

Another thing to watch is the member sin_addr of type struct in_addr. If you have a sockaddr_in called client, then "client.sin_addr.s_addr" references to its address. But you can't fill it straight. You have to feed it by means of a function from the inet_* family, the best being inet_aton().

 .
 .
 .
 #define N_PORT 7 
 int main(int argc, char[] argv)
 {
       int n_sin_size;                    /* the size of the client's address */
       struct sockaddr_in st_server_addr;      /* to store the server address */
       char sz_address[] = sz_argv[1];       /* this client takes an internet
                                                       address as an argument */
       .
       .
       .
       /* first commit the address information to a struct sockaddr_in; 
        * we have defined server_addr for that purpose. */ 
       st_server_addr.sin_family = AF_INET;
       st_server_addr.sin_port = htons(PORT);
       inet_aton(sz_address, &st_server_addr.sin_addr);   
       .
       .
       .
       /* An interesting function
        * is inet_aton(), it converts a string with an IP address in
        * standard numbers-and-dots notation to information that your
        * TCP/IP stack can use. */
       .
       .
       .
       /* Now we are ready to create a socket, communicate and close the
        * whole thing. */
       return 0;
 }
        

The functions

Calling socket() to get the file descriptor

The not-so-complicated call socket():

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

The protocol_family will be PF_INET in our examples. The type has to be either INET_STREAM or INET_DGRAM, as explained above. The combinations of protocol_family and socket type form the manner of communicating. Each combination has a default which almost always should be used by setting protocol to 0.

socket() just returns the file descriptor, or -1 on error, with errno set appropriately.

Tie the socket to a port and address with bind()

When your process fulfills a server function, you have to make it clear that your socket wants connections on this port and that address (think about a router with multiple addresses i.e. NICs). When your process fulfills a client function (i.e. you don't really care which port is used) then do not use bind. You let the socket API bind it to some available port, the so-called ephemeral port.

 int bind (int sockfd, struct sockaddr *my_addr, int addrlen);

The first parameter is your file descriptor as returned by socket(). The second parameter is the place holder for the struct sockaddr_in. The third address can be filled with sizeof(struct sockaddr). Click here for an example.

connect() to a machine on your WAN or LAN

If you are a server, you are not going to connect to anything. But if you are a client, then the only purpose you fulfill is connecting to the server using connect(). Just like you dial someone with your phone you need the telephone number of the target, i.e. the IP address.

 int connect (int sockfd, struct sockaddr *server_address, int addrlen);

Again, the first parameter is the socket's file descriptor. The second and third parameter are exactly the same as with bind().

Plug the telephone in the wallport with listen()

 int listen (int sockfd, int backlog);

This is the way of making your carefully set up socket ready for sending and receiving. Backlog can be the maximum number of connections that is currently trying to connect with the socket. Or it can be the number of connections that is ready to be accepted. Check your man pages!

Example programs

Datagram client

The program below uses datagrams to communicate. Datagram sockets do not guarantee delivery and this is easy to demonstrate, since starting the client without the corresponding server does not generate errors!

  /* dgram_client.c  */
  
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <sys/un.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <unistd.h>
  
  #define PORT 6650
  #define ADDRESS "10.17.16.212"
  
  int main(void)
  {
        int n_socket_fd;  /* file descriptor of the socket used to communicate */
        struct sockaddr_in st_server_addr;/* the address we will be sending to */
        struct sockaddr_in st_client_addr;       /* the address of this client */
        int n_bytes_sent;               /* how many bytes did we actually sent */
        int n_check;                    /* general variable for error checking */
        char msg[]="Message from client!\n";      /* the message which is sent */
  
        memset((char *)&st_client_addr,0,sizeof(st_client_addr));
        memset((char *)&st_server_addr,0,sizeof(st_server_addr));
  
        n_socket_fd = socket(PF_INET, SOCK_DGRAM, 0);
        if(n_socket_fd < 0)
        {
              perror("main: socket");
              exit(1);
        }
  
        st_server_addr.sin_family = AF_INET;
        st_server_addr.sin_port = htons(PORT);
        st_server_addr.sin_addr.s_addr = inet_addr(ADDRESS);
  
        n_check = connect(n_socket_fd, &st_client_addr, sizeof(st_client_addr) );
        if(n_check < 0)
        {
              perror("main: connect");
              exit(1);
        }
  
        n_bytes_sent = sendto(n_socket_fd, msg, strlen(msg), 0, &st_server_addr, 
                              sizeof(struct sockaddr));
        if(n_bytes_sent==-1)
        {
              perror("main: sendto");
              exit(1);
        }
  
        n_check = close(n_socket_fd);
        if(n_check < 0)
        {
              perror("main: close");
              exit(1);
        }
  
        return 0;
  }
  

Stream socket client

The program belows use stream sockets to send or receive data.

  /* stream_client.c  */
  
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <sys/un.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <unistd.h>
  
  #define PORT 6650
  #define ADDRESS "10.17.16.212"                  /* the address of the server */
  #define RECV_BUF_LEN 100           /* the number of chars which recv() grabs */
  #define N_TIMES_TO_SEND 1 
  
  
  int main(void)
  {
        int n_check;                    /* general variable for error checking */
        struct sockaddr_in server_addr;       /* to store the server's address */
        int n_socket_fd;                 /* the file descriptor for our socket */
        int n_bytes_sent;           /* how much bytes have actually been sent? */
        char sz_msg[]="Message from client!";    /* will be sent to the server */
        
        /* clear the space where the destination address will be put */
        memset((char *)&server_addr,0,sizeof(server_addr));
  
        /* create the socket */
        n_socket_fd = socket(PF_INET, SOCK_STREAM, 0);   
        if(n_socket_fd < 0)
        {
              perror("main: socket");
              exit(1);
        }
  
        /* set up the address information */
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(PORT);
        inet_aton(ADDRESS, &server_addr.sin_addr);
  
        /* connect to the address which is committed in variable server_addr */
        n_check = connect(n_socket_fd, &server_addr, sizeof(server_addr));
        if(n_check < 0)
        {
              perror("main: socket");
              exit(1);
        }
  
        n_bytes_sent = send(n_socket_fd, sz_msg, strlen(sz_msg), 0);
  
        if(n_bytes_sent == -1)
        {
              perror("main: send");
              exit(1);
        }
        if(n_bytes_sent < strlen(sz_msg) )
        {
              perror("main: send");
              exit(1);
        }
        if(n_bytes_sent > strlen(sz_msg) )
        {
              perror("main: send");
              exit(1);
        }
  
        close(n_socket_fd); 
        return(0);
  }

Servers

The GNU glibc manual has an excellent example of a server.

Resources