Sockets and Distributed Mutual
Exclusion Lab: Help Index
[Lab description]
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.
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.
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:
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);
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 hang. Do 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.
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.
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
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.
Programming Resources: Data communication with send/recv
The two functions used to perform all of the communication in this lab are send and recv. send 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).
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.
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.
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.
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.
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); }
Contributors: Travis
Leithead and Mark Richards, Spring 2002
Last updatet: March 24, 2003