You are here

TutorialLibraryTest ApplicationDownload
[Tutorial]LibraryTest ApplicationDownload

Socket Tutorial - Chapter 6

Chapter 6 - Specifications

From the following chapters, we will detail the most important parts of code of this socket management library. However, it is important to first determine what are its objectives and how the code will be organized.

List of functions to develop

Here is the list of functions provided by the library of this tutorial. These look like similar to low level functions that we have presented so far, but still offer highest level capabilities: timeout management, single thread multi-clients management, compilable code both Windows and Linux.

  • SK_NewServer : Creation of a new server socket;
  • SK_NewClient : Creation of a new client socket;
  • SK_DeleteCS : Clean up resources associated to a socket;
  • SK_Listen : Start listening on a server socket;
  • SK_Accept : Accept a connection on a server socket;
  • SK_Connect : Connect to a server, on a client socket;
  • SK_Disconnect : Close a connection related to a server or client socket;
  • SK_Write : Sending data;
  • SK_Read : Receiving data;

Details of these different functions are available in the Library section of this tutorial.

Timeout Management

This point has already been explained above. We will just add that our library should propose a parameter timeout for all functions involving potentially blocking socket calls: (accept, connect, send and recv). This is the case for the following functions: SK_Accept, SK_Connect, SK_Write and SK_Read.

Single Thread Multi-Clients Server

Function SK_Read of our library should be able to receive data from multiple sockets simultaneously, in the same thread. This feature can be very interesting if several clients are connected to the same server. Then it is possible to handle multiple connections from the same thread, instead of one thread by connection for most socket implementations.

Error Management

The library must not only manage the nominal case, ie when all goes smoothly. It must also be able to respond to a number of errors, and in any case, warn the user if a function call fails.

Thread safe

The library must also be thread safe, that is to say that the functions can be called in a multithreaded environment. To achieve this, the sensitive portions of code must be protected by critical sections.

Code compatible with both Windows and Linux

The socket function prototypes are virtually identical regardless of the operating system. Small nuances still exist and the code should therefore include conditional compilation to mask these differences.

It is possible to know the building environment with some symbols defined by preprocessor. Thus, under Windows, the WIN32 symbol is declared by the compiler, while in Linux, it is the symbol __linux__. Concretely, context is determined like this:


//--- List of available contexts ---
#define SOCK_CTX_ANY        0    /* Any context            */
#define SOCK_CTX_WIN32NT    1    /* Win32/NT MS C++ context*/
#define SOCK_CTX_LINUX      2    /* Linux C++ context      */

//--- Win32 - NT ---
#if defined(WIN32)
#define SOCK_CONTEXT   SOCK_CTX_WIN32NT

//--- Linux ---
#elif defined(__linux__)
#define SOCK_CONTEXT   SOCK_CTX_LINUX

//--- Any other ---
#else
#define SOCK_CONTEXT   SOCK_CTX_ANY
#endif

Symbol SOCK_CONTEXT can be set to 0.1 or 2 depending on the build environment. Thereafter, it is possible to exclude certain parts of code depending on the context. The excluded parties will not be read by the compiler and will not generate errors:

#if (SOCK_CONTEXT==SOCK_CTX_LINUX)

   (...)

#elif (SOCK_CONTEXT == SOCK_CTX_WIN32NT)	

   (...)

#endif

The different objects of our library

The first class we define is the class C_SCP (Socket Connection Point). It encapsulates several socket identifiers that are stored in a table. It may implement either a client or a server. In the first case, it will contain only one socket identifier, so a single connection. In server mode it can hold up to n socket identifiers, plus a special one dedicated for listening. Each accepted connection adds the new socket id into a table.

class C_SCP
{
public :
	
   C_SCP (unsigned int maxLink);
   ~C_SCP ();
	
   S_Error Listen (bool state, unsigned int maxWaiting, unsigned int port);
   S_Error Accept (int waitTime, unsigned int *pIDLink);
   S_Error Connect (int waitTime, char* addr, unsigned int port, unsigned int *pIDLink);
   S_Error Disconnect (unsigned int IDLink, int waitTIme_ms);

   (...)
	
   // object id
   unsigned int ID;

private :

   enum e_mode {M_FREE, M_CLIENT, M_SERVER};
   e_mode w_mode;

   // listening socket id (for a server)
   int w_listenSock;

   // Pointer to a table of "n" socket identifiers (n=1 for a client)
   int *w_pTabSockLink;
   unsigned int *w_pTabLinkID;
	
   (...)

   unsigned int w_maxLink;
};

Each C_SCP object is created within a single C_Sockets object. This late enables to create C_SCP objects either in Server or Client mode. It also implements the functions for reading and writing.

class C_Sockets
{
public :

   C_Sockets (unsigned int maxCS);
   ~C_Sockets ();

   S_Error NewServer (unsigned int maxLink, unsigned int *pID);
   S_Error NewClient (unsigned int *pID);
   S_Error DeleteCS (unsigned int ID);

   S_Error Listen (unsigned int ID, bool state, unsigned int maxWaiting, unsigned int port);
   S_Error Accept (unsigned int ID, int waitTime, unsigned int *pIDLink);
   S_Error Connect (unsigned int ID, int waitTime, char* addr, 
         unsigned int port, unsigned int *pIDLink);
   S_Error Disconnect (unsigned int ID, unsigned int IDLink);

   S_Error Write (unsigned int ID, unsigned int IDlink, unsigned char *pData, 
         int *pLength, int waitTime_ms);
   S_Error Read (S_ReadList *pList, unsigned int listLength, unsigned char *pBuffer, 
         int *pLength, int waitTime_ms, S_ReadList *pIDRead, bool dontBlockIfData = false);

private :

   unsigned long w_ID;

   // Table of SCP objects, along with mode (client or server)
   unsigned int w_maxCS;
   C_SCP **w_pTabSCP;
   bool *w_pTab_C_S;	
};