This assignment will introduce students to socket programming in the Linux environment.
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
#define ITERANT //defines as an iterant server #define CONCURRENT //defines as a concurrent server
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.
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...
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: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.
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
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.
Save your text file as yourlastnameA10.txt.