Sockets and Distributed Mutual Exclusion Lab: Help Index
[Lab description]

  1. Definitions
  2. Programming Resources (Sockets)
  3. Hints
  4. Using Pipes

Definitions: Sockets

    Sockets are similar to pipes, in that they allow programs to send and receive information.  Unlike pipes, sockets may be used to transfer information over long distances through networks, and the Internet.  This makes sockets a very powerful communication tool.  We use sockets in this lab to allow many clients to connect and communicate with each other on a network.

    For the low-level sockets that we will be using in this lab, you need only the name or IP address of a remote computer, and a port number (see Ports) to establish a connected socket.  You will use the function connect to establish a connection with a remote listening server (see Connecting to a server), and accept, on the server side, to return the socket that connected (see Setting up a listening server).  These two functions initialize and return sockets (respectively).  Once two sockets are connected, they may exchange information (see Data communication with send/recv).  The information is simply a stream (array) of bytes, that we interpret as character strings.

back to Help Index


Definitions: Ports

    Ports are like tunnels through which sockets may be established.  A port is nothing more than an integer index, so there are many, many ports.  When sockets connect to each other, they must use a common port.  Sockets may binda port, in which they reserve exclusive use of it on that computer.  Just because that port is bound, does not mean that only one socket can connect to it.  In fact, bound ports may host many connected sockets (for example, port 80 is reserved for internet communication use).  In this lab, a program that has a bound socket that is listening for connections is referred to as a server.  Programs that use sockets to connect to servers are called clients.  Clients that connect to other clients are called peers. You must choose a port number greater than 1024, unless you are running as root.

back to Help Index


Programming Resources: General process structure

    After connecting with all other clients, you are ready to begin the main loop.  The overall structure of a process should be something like:

  1. Sleep for a random time period
  2. Send out Request messages to others to enter the critical section
  3. Wait for a grant from each process to enter the critical section
  4. Start the critical section
  5. Update the number in file.dat
  6. Finish the critical section
  7. Grant all pending requests
  8. Go back to 1
Keep in mind that this is only the main thread, there should be other threads listening for messages from other processes.

back to Help Index


Programming Resources:Threads in Linux

Linux supports many different kinds of threads for application developers.  I would recommend using the threads functions that are part of the pthread library.  To use the following function, you must remember to include the <pthread.h> header file.

pthread_t thread_handle;

pthread_create(&thread_handle, NULL, MyFunctionPointerName, (void*)argument);

The function that is passed into pthread_create, should be of global scope, and be of the form:

void*
MyFunctionPointerName(void* argument_32bitpointer)


or the compiler will complain about your function pointer.

please read the man page for this function for the details.

back to Help Index


Programming Resources: Setting up a listening server

    Listening server code should generally be placed inside of a thread (see the manual page for pthread_create) because servers generally do a lot of waiting before any action happens.  To do this, we'll put our server in an infinite loop--this can only be done in a thread to avoid having our program hangDo not expect to understand all of the following code.  This lab is solely an introduction to sockets and servers, etc.  If you like this sort of thing, take CS 460 for more information.  Setting up the server takes 5 steps.

  1. Create the socket that we will use to bind the port, and listen for other connections, using the function socket
    socket(        /* returns a integer socket handle or -1 if error. */
    AF_INET,        /* the address family specification (internet based) */
    SOCK_STREAM,    /* two-way communication using TCP */
    0);            /* no specific protocol for this socket */
  2. Set up the sockaddr_in structure that will be used in the functions bind and accept
    sockaddr_in svr_info; /* structure for port/address information for socket functions */
    bzero(&svr_info, sizeof(sockaddr_in)); /* Clear out the memory of this structure */
    svr_info.sin_family = AF_INET; /* Matches the socket family spec. type */
    svr_info.sin_port = htons((unsigned short) my_port); /* Set the port to bind (my_port is an int) */
    svr_info.sin_addr.s_addr = htonl(INADDR_ANY); /* Sets the basic socket address to ANY */
  3. Attempt to bind the socket with the function bind
    bind(                /* returns SOCKET_ERROR if this fails */
    listen_socket,        /* a SOCKET handle, previously created with the socket function */
    (sockaddr*)&svr_info, /* Our server information struct cast as a SOCKADDR pointer */
    &len);    /* The size of the server information struct where len = sizeof(svr_info))*/
  4. Set our newly-bound socket to be a listening socket with the function listen
    listen(        /* returns 0 for success,
    SOCKET_ERROR for failure */
    listen_socket, /* our bound SOCKET object */
    1);           /* maximum number of sockets in pending connection queue */
  5. Start an infinite loop, and within it, place the function acceptaccept will block (code execution will not continue) until the connect function (see Connecting to a server) connects a socket to this server at the bound port.  Then the connected socket is returned.  The returned socket is used to communicate with the client.
    accept(              /* returns INVALID_SOCKET if an error occurs, otherwise a SOCKET object that is used in communications */
    listen_socket,        /* our bound, listening SOCKET object */
    (SOCKADDR*)&svr_info, /* Our server information struct cast as a SOCKADDR pointer */
    sizeof(svr_info));    /* The size of the server information struct */

    In the following server code sample, the value my_port is an integer that represents the port that this server should listen on.

    For further information about the functions socket, bind, listen, and accept look them up in the man pages under their respective names.

back to Help Index


Programming Resources: Connecting to a server

    To connect to a listening server, you need to know the name or IP address of the remote machine to connect to (or the name/IP of your current computer--see Getting the name/IP address of your computer), and the port number that the peer or server program is using.  Once you have that information you may proceed to connect to a listening server.  After you are done with all of the communication on the socket, call closesocket

  1. Create a socket that will be used to communicate with the server, as shown in step 1 of Setting up a listening server.
  2. Set up the sockaddr_in structure that will be used with connect.  The HOSTENT structure can be filled in by gethostbyname or gethostbyaddr (see Getting the name/IP address of your computer).
    sockaddr_in server; /* the information structure for the server (to be filled out) */
    hostent *host = gethostbyname(computer_name); /* Gets the host information from the remote computer name stored in computer_name */
    bzero(&server, sizeof(sockaddr_in));
    memcpy(&server.sin_addr, host->h_addr_list[0], host->h_length);
    server.sin_family = AF_INET; // Set the server's family
    server.sin_port = htons((unsigned short) my_port); /* Set the port to connect to... */
  3. Attempt to connect to the server with connect
    connect(           /* returns SOCKET_ERROR if failure to connect, otherwise 0 */
    server_socket,      /* The socket that I will use to communicate with the server */
    (sockaddr*)&server, /* the server information struct cast as a SOCKADDR pointer */
    sizeof(server));    /* The size of the server information struct */

    The following code sample illustrates how to connect to a listening server.  The value my_port is the port number of the server, and computer_name is the name of the computer hosting the listening server.

    To close a socket, call the function closesocket which takes the name of the socket you created with socket

    For further information on the functions connect, and closesocket look up their respective names in the man pages.

back to Help Index


Programming Resources: Data communication with send/recv

    The two functions used to perform all of the communication in this lab are send and recvsend is used to send a character array over a socket, and recv will, as the name suggests, receive that information.  If there is no information to receive, then the function recv will block (code execution will not continue) until some data is received, or the socket connection is broken (see Detecting when a peer terminates).  Before either of these functions can be called, a valid socket connection must be established (see Setting up a listening server, or Connecting to a server).

back to Help Index


Programming Resources: Getting the name/IP address of your computer

    To retrieve the name (host name) of the computer that you're sitting at:

    To get the IP address (as a character array, using the '.' [dot] notation) of the computer that you're sitting at:

    For further information on the functions gethostname, gethostbyname, gethostbyaddr,inet_ntoa, look up their names in the man pages.

back to Help Index


Programming Resources: Code Example

    The following code example shows how it is possible for two processes to communicate with one another: sockets.cpp. Compile the source code and run it in two terminal windows. At the first command line type localhost 3000 -s to indicate that you want the server to run on this computer and that you want to bind to port 3000. At the second command line type localhost 3000 -c to indicate that you want connect to the server running on this machine that is listening on port 3000. Feel free to run the client program over and over and see the input/output for both the server process and the client process. Please note that the server process will not terminate unless you do so with something like Ctrl C.

back to Help Index


Programming Resources: Detecting when a peer terminates

    The best way to detect when a peer or server terminates is by checking the return value of the recv function (see Data communication with send/recv).  Because recv blocks until data is received, if the function ever returns for any other reason than having received valid information, you know that the client to which the socket was connected has terminated.  This termination can occur in one of two ways--gracefully, or non-gracefully. 

    In the graceful termination, the socket on the other end will be closed with closesocket.  This causes the recv function to return 0--meaning zero bytes were received.

    In the non-graceful scenario, the socket that you're connected to just dies because the host program terminated abnormally.  In this situation, recv returns the value -1 indicating that something unexpected happened to the socket.

    You should handle both of these situations in your code to properly adjust the distributed mutual exclusion algorithm when one peer terminates or leaves.

back to Help Index


Hints: Peer connection management

    It might be very useful to create a global array (or vector) where you can store status information on each peer.  For example, what peers have responded to request messages, and what peers have pending replies, etc.  Because each response thread needs to know on what socket to send out replies, you might have each response thread contain a pointer to its associated peer in the global array (that also contains the "outbound" socket).  Grouping all of the information relative to a peer in some sort of structure and then placing them in an array is probably the best way to go.  Consider protecting this array with a mutex, and think carefully how to manage this array when clients are dynamically deleted.


Using Pipes

The command mkfifo(filename, permissions) will create a pipe named filename in the current directory. You can open it like any other file - open(filename,O_RDWR) and then read from it or write to it.

In this example, process 0 and process 1 are communicating using a pipe.

#include <stdio.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
  int fd, id;
  char inbuffer[32], outbuffer[32];

  id = atoi(argv[1]);   /* get the id from the command line*/

  if(id==0)
    mkfifo("a_pipe",0644);  /* only one process creates the pipe */
                            /* standard chmod settings */
                            /* everybody has read permission */
                            /* the user has write permission */

  fd=open("a_pipe",O_RDWR);  /* both processes open the pipe */
  if(fd == -1)
  {
    perror("pipe failed");
    exit(-1);
  }

  if(id==0)    /* process 0 writes to the pipe */
  {
    sprintf(outbuffer,"I am process %d \n",id);
    write(fd,outbuffer,sizeof(outbuffer));
  }

  if(id==1)   /* process 1 reads from the pipe */
    read(fd,inbuffer,sizeof(inbuffer));

  printf("%s",inbuffer);   /* both processes print */
                           /* this is empty for process 0 */

  getchar();    /* so it doesn't just exit (and close the pipe) */
  close(fd);
}

back to Help Index


Contributors: Travis Leithead and Mark Richards, Spring 2002
Last updatet: March 24, 2003