CSC 325 Operating Systems with an Emphasis on UNIX
Assignment 10

This assignment will introduce students to socket programming in the Linux environment.

Introduction to Sockets

Chapter 4 of our text introduces the idea of using sockets as a form of interprocess communication (IPC), within the same computer as well as between systems. A socket is a software endpoint that establishes bi-directional communication between a server program and one or more client programs, so a pair of processes communicating over a network employ a pair of sockets. In general, sockets use a client-server architecture in which the server program provides resources to a network of client programs. Client programs then send requests to the server program, and the server program responds to the request.
The socket associates the server program with a specific hardware port on the machine where it runs, so any client program in the network with a socket associated with that same port can communicate with the server program.

The most widely used implemenation of sockets on the Linux platform is based on the BSD socket model in which the socket is the method for accomplishing interprocess communication (IPC). What this means is a socket is used to allow one process to speak to another, very much like the telephone is used to allow one person to speak to another. While robust, well tested, and well-documented, this implementation is far from easy to use. Instead, our illustrious T.A., Matt Isaacs, has written a C++ class implementation of sockets that encapsulates the BSD interface in a simpler, more familar, C++ class interface. As with other classes, you will need both the header file, SocketClass.h and the header implementaton SocketClass.cxx as well as the main implementation, socketmain.cpp, .

Our T.A. designed this C++ class to work on BOTH Windows and Linux. This is acheived through compiler directives (identified the the '#' preceding them). These directives instruct the compiler to compile certain portions of code while ignoring others, based on certain parameters. Below is a list of some of the common directives and their use.

	#include <file> 	//include file from standard library
	#include "path/file" 	//include a programmer defined file
	#define PARAMETER	//define a valueless parameter
	#define PARAMETER value	//define a parameter and gives the value specified
				// also, the compiler replaces ALL occurences of PARAMETER
				// with the value specified
	#ifdef PARAMETER	//executes the following block of code if the specified
				// parameter has be #define-ed
	#ifndef PARAMETER	//same as above, but test for NOT defined
	#endif			//signifies the end of the if statement

The socketclass requires a couple of parameters to ensure correct operation. First, you must define which operating system you are compiling for. This is done by:

	#define LINUX	//defines our operating system as Linux-based
		or
	#define WINNT	//defines our operating system Windows NT-based
			//  i.e. Windows NT 4, Windows 2000, or Windows XP

If you are compiling under Linux, one of the following additional parameters are necessary:
	#define ITERANT		//defines as an iterant server
	#define CONCURRENT	//defines as a concurrent server

These parameters control the style of server that gets implemented. There are two basic styles of socket-based servers--those that process multiple requests simultaneously are called concurrent, and those that process requests one at a time on a first-come-first-server basis are called iterant.

Here is a look at the structure of the class:

class SocketClass
{
	//Constructors
        SocketClass();	//default constructor
        ~SocketClass();	//default deconstructor; we have to have it to ensure
			// that the resources used by our socket server are
			// released properly

	//Accessors
        const string getSendBuffer(int socketnum) {return s[socketnum].sendbuf;};
		//returns the data is waiting to be sent, or was recently sent
		// to the connected client

        const string getRecvBuffer(int socketnum) {return s[socketnum].recvbuf;};
		//retrieves the data that was last received from a connected client

        const int getBytesSent(int socketnum) {return s[socketnum].bytesSent;};
		//returns how much data was sent to the client in bytes

        const int getBytesRecv(int socketnum) {return s[socketnum].bytesRecv;};
		//returns how much data was received from the client in bytes

        const int getPort(int socketnum) {return ntohs (s[socketnum].service.sin_port);};
		//returns the port number associated with a socket

        const int getListenQDepth(int socketnum) {return s[socketnum].listen_q_depth;};
		//returns the depth of the listen queue, that is, how many requests
		// can "wait in line" while we are busy processing other stuff

        //Mutators
        void setSendBuffer (int socketnum, string buffer);
		//fills the buffer with data to send

        void setAddress (int socketnum, string address);
		//sets the IP address associated with a socket

        void setAddress (int socketnum);
		//Tells the socket to associate with ALL address on the system

        void setPort (int socketnum, unsigned int port);
		//sets the port associated with a socket

        void setListenQDepth(int socketnum, int depth);
		//sets the depth of our listen queue

        //Public Member Functions

        int SocketAlloc();
		//allocates a new socket and returns its identifier (next available)

        bool SocketAlloc(int socketnum);
		//reallocates a previosly defined socket

        int ServerOpenSocket(int socketnum);
		//Opens the specified socket, that is, makes it active

        void CloseSocket(int socketnum);
		//closes the socket and release it to the system
	void CloseSocket();
		//closes all sockets in the class, releasing them to the system

        void SendData (int socketnum);
		//send buffered data to client

        void RecvData (int socketnum, int buffer_size);
		//receive buffered data from client

        bool ClientConnect (int socketnum);
		//function that implements a client on a given socket

Here is a sample program using this interface. It is a "Hello World" program using sockets.

#include <iostream>
#include "SocketClass.h"

//These must be set in SocketClass.h
#define LINUX 		//define our OS as linux
#define CONCURRENT	//use the concurrent server model

using namespace std;

int main(int argc, char *argv[])
{
    SocketClass MyServer;	//new instance of socketclass
    int mysocket, theirsocket;  //store the ID of our socket and theirs
    				// (so we know where to write to)

    //We define our server to use the defaults in the class
    //  the socket is bound to all address
    //  and is listening on port DEFAULT_PORT

    mysocket = MyServer.SocketAlloc();				//Allocate the socket
    theirsocket = MyServer.ServerOpenSocket(mysocket);		//Open the socket, does not return until a client connects
    MyServer.setSendBuffer(theirsocket, "Hello World\n");	//Fill the buffer with the data to send
    MyServer.SendData(theirsocket);				//Send the data
    MyServer.RecvData(theirsocket, 10);				//Receive their 10 character response
    cout << MyServer.getRecvBuffer(theirsocket);		//print their response
    MyServer.CloseSocket();					//close our socket

    return 0;       //~SocketClass destructor is called automatically and makes sure all is in order
}

It can also be downloaded from sockethello.cpp.

 

An important change you must make to this code before running!!

You need to change the port that the program listens on, as you will have LOTS of trouble you try to use the same one as someone else on the same Linux box. Type set to see your user ID, which is listed as UID. It should be something like 5xy.

Inside of socketclass.h change the DEFAULT_PORT to 10 times your UID, so it will be 5xy0. This will guarantee that each person is using a unique port for their individual server.

Be sure to remember this UIDx10 number because you will need it later...

Compilation

There are two ways to compile a multipart program in the Linux environment: (1) directly from the command line using g++ (2) using a file called a "makefile". For each of these, we will need to connections to Linux, so start a second putty connection to cs.berea.edu.

We can compiling this code both ways.

Let's try compiling from the command line first:
  1. The way to compile a multipart program from the command line is to append the extra implementation files to the g++ command:
        g++ socketmain.cpp SocketClass.cxx -o sockettest
  2. As usual, the code can then be run by typing the name of the object-code file, which is:
        ./sockettest

At this point, you should have a server runnng and waiting for a client to connect.

Go ahead and type "Ctrl-C", to quit the server program, so that we can try compiling using the "makefile".

To compile this code with a "makefile", download the Makefile, which must be saved in a file named "Makefile" with no file extension. Check out the contents of the Makefile. You should see that the two source files that are being given to the compiler are exactly the same two source files are were given in the command line above and that the object code will be sockettest just as before. When working with a large number of files and libraries, the use of Makefile becomes the better choice for compilation because the command line becomes unwieldy.

  1. You can then compile the code by executing the "make" commande at the command line:
        make
  2. Of course as before, the code can then be run by typing the name of the object-code file, which is:
        ./sockettest

So.... Now that that we have compiled and are running the socketmain program as a server, we need to be able to connect to this server somehow.

To test this server program once running, execute the following command from your other Linux console / Windows command prompt:

	telnet 127.0.0.1 yourUIDx10


The first parameter is the IP address of the machine the server is running on. The address 127.0.0.1 has a special name. It is the loopback address and ALWAYS points to the local machine. If you wanted to connect to a remote machine (i.e. your neighbors computer), you would replace the loopback address with their IP address.

The telnet session is not very exciting, but it does allow you to see that the connection is made. To terminate this connection, type the escape sequence 'Ctrl-]' which will return control to the telnet program. Then type quit to exit the telnet session. You should see that the connection was closed.

To terminate the server program. Type "Ctrl-C".

The parameters to main, int argc and char *argv[], contain the command line used to execute the program, including the name of the program (as called) as well as any parameters.

	int argc is the number of parameters
	char *argv[] is an array of C-strings that store these parameters

 


Your Task:

The purpose of this assignment is to play around with the Socket object in C++ and compare it to the socket interface described in the book, which is for Java.

  1. Download the header file, SocketClass.h, the header implementaton file, SocketClass.cxx, the main implementation, socketmain.cpp into your Linux account on cs.berea.edu. Run this program by following the above set of instructions. Describe any troubles you encounter in your text document.

  2. Download sockethello.cpp into your Linux account on cs.berea.edu. Run this program by following the appropriately altered set of instructions. Describe any troubles you encounter in your text document.

  3. Read carefully through the socket implementation and the extensive documentation written by our T.A. The Java socket interface and the C++ socket interface provide basically the same set of features, but the C++ interface requires the programmer to do more things explicitly, whereas the Java interface hides many of these details. Read about the Java socet interface at http://java.sun.com/docs/books/tutorial/networking/sockets/. Compare the socket code on pages 117-120 and code from the Java tutorial with the C++ class and program written above. Give two examples of things that have to be done explicitly in sockets in C++ that do not appear in the Java code.

Save your text file as yourlastnameA10.txt.