CS330 Sockets


Highlights of this lab:


Lab Code

To get the sample and exercise code, please use the following commands in your cs330 directory:
   curl -O -s https://www.labs.cs.uregina.ca/330/Sockets/Lab9.zip
   unzip Lab9.zip
   

Introduction to Sockets

Sockets are a form of interprocess communication that exists between two unrelated processes. If you think back to the lab on pipes, you first had to create the pipe (with file descriptors for the the read and write end). Next, you forked the process so that you had a child and parent who both knew about the pipe. For sockets, you do not need a child and parent relationship. You just need to know a commonly shared file(for AF_UNIX) or an internet address(for AF_INET).

"A socket is like the end point of a UNIX pipe...A single socket is used in exactly the same way as a file descriptor. When a socket is created, it is allocated a slot in the object descriptor table exactly as for an open file. After the socket has been linked to another socket, the slot number may be used as the file descriptor parameter in calls to the read and write functions." (page 262, The Berkeley UNIX Environment by Nigel Horspool).

Without getting too much into the gory details of the different kinds of sockets, we are narrowing these notes in on a specific implementation of the socket which allows us to communicate over the internet using streams (TCP--Transmission Control Protocol) as opposed to datagrams (UDP--User Datagram Protocol).


Client Server Model

The following is slightly modified version of the diagram on page 261 of "Unix Network Programming" by W. Richard Stevens:

client-server

The above diagram summarizes the approach used for the Internet domain and a TCP connection-oriented protocol. Again, this is only one approach to sockets. Notice that the server is started first; it will accept connections from the client. Once a connection is established, then communication can occur through the read and write system calls to the file descriptor associated with the socket.

Because we are talking about communication over the internet, we need to know the ip address and port of the server in order to do a bind() and a connect(). By analogy, the ip address is like a telephone number and the port is like an extension to that telephone number.

If you want to see the ip addresses of the various machines in computer science, you can try:

dig titan.cs.uregina.ca
dig google.ca

If you are curious about ports, you can see what services use what ports:

more /etc/services

For instance, a web server listens on port 80. What port does ftp listen on?

Ports up 1023 are reserved; you cannot bind to these ports. You will notice that the ports that you use in your code will be in the range of 32768 to 60999. This is determined by a file that configures some global parameters, which is viewable by typing:

cat /proc/sys/net/ipv4/ip_local_port_range

The following two sections will narrow in on the system calls demonstrated in the above diagram.


If you want to try out some socket code, you can right click on the following links and save it to your CS330 directory:

Or copy the files directly to your directory with the following commands:
cp /net/data/ftp/pub/class/330/Sockets/Sample/server.c server.c
cp /net/data/ftp/pub/class/330/Sockets/Sample/client.c client.c

Commands that you can try in this lab

ss -tulpn 
ss -tulpn | grep server (to see what port your server is listening on)

( -t is for TCP sockets; -u is for UDP sockets; -l is for listening sockets; -p shows process using socket; and -n doesn't resolve service names-it just shows numbers)


System Calls for Servers

The system calls we will focus on in this section are:

Let's look at some sample code and then discuss these system calls as used in this code. Notice that the system calls are in purple text:

/*****************************************************************
 Sockets Daemon Program

  This code was modified from Nigel Horspools, "The Berkeley
  Unix Environment".

  A daemon process is started on some host.  A socket is acquired
  for use, and it's number is displayed on the screen.  For clients
  to connect to the server, they muse use this socket number.
*****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

/* Use port number 0 so that we can dynamically assign an unused
 * port number. */
#define PORTNUM         0

/* Set up the location for the file to display when the daemon (okay,
 * server for you religious types) is contacted by the client. */
#define BBSD_FILE       "./test.bbs.file"
/*"/nfs/net/share/ftp/pub/class/330/test.file" */

/* Display error message on stderr and then exit. */
#define OOPS(msg)       {perror(msg); exit(1);}

#define MAXLINE 512

int main()
{
  struct sockaddr_in saddr;       /* socket information */
  struct hostent *hp;     /* host information */
  char hostname[256];     /* host computer */
  socklen_t slen;         /* length socket address */
  int s;                  /* socket return value */
  int sfd;                /* socket descriptor returned from accept() */
  char ch[MAXLINE];       /* character for i/o */
  FILE *sf;               /* various file descriptors */
  int bbf;
  int num_char=MAXLINE;
  
  /*
   * Build up our network address. Notice how it is made of machine name + port.
   */

  /* Clear the data structure (saddr) to 0's. */
  memset(&saddr,0,sizeof(saddr));

  /* Tell our socket to be of the IPv4 internet family (AF_INET). */
  saddr.sin_family = AF_INET;

  /* Aquire the name of the current host system (the local machine). */
  gethostname(hostname,sizeof(hostname));

  /* Return misc. host information.  Store the results in the
   * hp (hostent) data structure.  */
  hp = gethostbyname(hostname);

  /* Copy the host address to the socket data structure. */
  memcpy(&saddr.sin_addr, hp->h_addr, hp->h_length);

  /* Convert the integer Port Number to the network-short standard
   * required for networking stuff. This accounts for byte order differences.*/
  saddr.sin_port = htons(PORTNUM);
  
  /*
   * Now that we have our data structure full of useful information,
   * open up the socket the way we want to use it.
   */
  s = socket(AF_INET, SOCK_STREAM, 0);
  if(s == -1)
    OOPS("socket");

  /* Register our address with the system. */
  if(bind(s,(struct sockaddr *)&saddr,sizeof(saddr)) != 0)
    OOPS("bind");

  /* Display the port that has been assigned to us. */
  slen = sizeof(saddr);
  getsockname(s,(struct sockaddr *)&saddr,&slen);
  printf("Socket assigned: %d\n",ntohs(saddr.sin_port));

  /* Tell socket to wait for input.  Queue length is 1. */
  if(listen(s,1) != 0)
    OOPS("listen");

  /* Loop indefinately and wait for events. */
  for(;;)
  {
    /* Wait in the 'accept()' call for a client to make a connection. */
    sfd = accept(s,NULL,NULL);
    if(sfd == -1)
      OOPS("accept");
      
    /* Open our file for copying to the socket. */
    bbf = open(BBSD_FILE, O_RDONLY);
    if(bbf == -1)
      write(sfd,"No information, dude.\n", strlen("No information, dude.\n"));
    else
    {
      /*Read from file, write to socket*/
      while((num_char=read(bbf,ch,MAXLINE))> 0)
        if (write(sfd,ch,num_char) < num_char)
           OOPS("writing");
      close(bbf);
    }
    close(sfd);
  }

  return 0;
} 

Note:


socket()

#include <sys/types.h>
#include <sys/socket.h>

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

To do network I/O, the first thing a process must do is call the socket() system call, specifying the type of communication protocol desired.

Let's narrow in on the usage from above (connection-oriented protocol in the internet domain):

s = socket(AF_INET, SOCK_STREAM, 0);

bind()

#include <sys/types.h>
#include <sys/socket.h>

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

The bind() system call assigns a name to an unnamed socket. In the case of the server, it tells the system "this is my address and any messages received for this address are to be given to me".

Notice the usage from above:

bind(s,(struct sockaddr *)&saddr,sizeof(saddr))

listen()

#include <sys/socket.h>

int listen(int sockfd, int backlog); 

This system call is used by a connection-oriented server to indicate that it is willing to receive connections.

The usage above is:

listen(s,1)


accept()

#include <sys/types.h>
#include <sys/socket.h>

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

After a connection-oriented server executes listen() system call, an actual connection from some client process is waited for by having the server execute the accept() system call. If there are no connection requests pending, this call blocks the caller until one arrives.

Let's look at the usage from the code above:

sfd = accept(s,NULL,NULL);

System Calls for Clients

The system calls we will focus on in this section are:

Let's look at some sample code and then discuss these system calls as used in this code. Notice that the system calls are in purple text:

/*****************************************************************
 Sockets Client Program 

  This code is a modified version taken from Nigel Horspool's "The Berkeley
  Unix Environment".

  This client connects to a specified server (host) and receives
  information from it.
*****************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

/* Display error message on stderr and then exit. */
#define OOPS(msg)       {perror(msg); exit(1);}

#define MAXLINE 512

int main(int argc, char *argv[])
{
  struct sockaddr_in bba; /* socket information */
  struct hostent *hp;     /* host information */
  int slen;               /* host computer */
  int s;                  /* length socket address */
  int rfd;
  char ch[MAXLINE];       /* character for i/o */
  int num_char=MAXLINE;   /* number of characters */
  int port;               /* port to connect to */

  char portnum[20];
  char hostname[20];
  printf("\n hostname:");
  scanf("%s", hostname);

  printf("\n port number:");
  scanf("%s", portnum);

  /* Clear the data structure (saddr) to 0's. */
  memset(&bba,0,sizeof(bba));

  /* Tell our socket to be of the IPv4 internet family (AF_INET). */
  bba.sin_family = AF_INET;

  /* Acquire the ip address of the server */
  hp=gethostbyname(hostname);

  /* Acquire the port #. */
  port=atoi(portnum);

  /* Copy the server's address to the socket address data structure. */
  memcpy(&bba.sin_addr, hp->h_addr, hp->h_length);

  /* Convert the integer Port Number to the network-short standard
   * required for networking stuff. This accounts for byte order differences.*/
  bba.sin_port=htons(port);
  
  /* Now that we have our data structure full of useful information,
   * open up the socket the way we want to use it.
   */
  s = socket(AF_INET, SOCK_STREAM, 0);
  if(s == -1)
    OOPS("socket");
  if(connect(s,(struct sockaddr *)&bba,sizeof(bba)) != 0)
    OOPS("connect");

  /* read from the socket, write to the screen */
  while( (num_char=read(s,ch,MAXLINE)) > 0 )
    if ( write(1,ch,num_char) < num_char)
      OOPS("writing");
    close(s);

  return 0;
}

socket()

#include <sys/types.h>
#include <sys/socket.h>

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

This is the exact same system call and usage as the server:

s = socket(AF_INET, SOCK_STREAM, 0);

connect()

#include <sys/types.h>
#include <sys/socket.h>
int  connect(int  sockfd,  const  struct sockaddr *serv_addr, socklen_t addrlen);

The connect() system call establishes a connection between the local system (the client) and the foreign system (the server).

Our usage in the code above was:

connect(s,(struct sockaddr *)&bba,sizeof(bba))



References