You are here

TutorialLibraryTest ApplicationDownload
[Tutorial]LibraryTest ApplicationDownload

Error message

Deprecated function: The each() function is deprecated. This message will be suppressed on further calls in menu_set_active_trail() (line 2404 of /home/quantics/public_html/dp/includes/menu.inc).

Socket Tutorial

Chapter12 - Reading data from multiple sockets at the same time

C_Sockets::Read (S_ReadList *pList, unsigned int listLength, unsigned char *pBuffer, int *pLength, int waitTime_ms, S_ReadList *pIDRead, bool dontBlockIfData)

This function attempts to read pLength bytes of data that will be stored in buffer pBuffer. Reading attempt can be done on one or more sockets. These are specified by variable pList which must point to an array of structure S_ReadList. Each of these structures identifies a socket (parameter ID for the client / server and IDlink for the associated link). If IDlink is zero, then all links of the client / server identified by ID will be considered.

struct S_ReadList
{
   unsigned int ID;
   unsigned int IDlink;
};

If more than one socket is specified in pList (i.e. listLength > 1), reading attempt will occur on each socket, one after another. Once at least one byte has been received on a socket, it will be the only one to be read by after. Indeed, pBuffer must be filled with data from a single socket (the first that has something to send). Otherwise, data from multiple sockets could be mixed in pBuffer.

If no socket is responding or if the one who responded returned less data than expected (variable pLength), function waits up to waitTime_ms milliseconds to receive what is missing. A negative value of this parameter means function can potentially wait infinitely (blocking mode), while value 0 means function makes a single attempt and returns immediately (non-blocking mode).

If dontBlockIfData is true, function returns as soon as at least one byte has been received on a socket, even if value pLength is not reached and there is still time.

When function returns, pLength is modified to reflect the number of bytes actually received (from 0 to its initial value). Parameter pIDRead is also filled with the the socket identifier on which the reception was held so that the user knows which link data are coming from (if listLength is greater than 1).

Along with the local variables declaration, the time at which the user enters the function (t1) is stored.

S_Error er;
unsigned long arg;
unsigned int i, j, k, length;
unsigned int idx, numSock, greatestSock, numReserved;
unsigned int t1, t2;
int numRead;
int sock, idxRead;
int tabSock [FD_SETSIZE];
S_ReadList tabRL [FD_SETSIZE];
int result, error;

timeval tv;
fd_set fdset;

t1 = GetTime_tick ();
idxRead = -1;
numRead = 0;

Next step is not shown here because it is not really socket related. It consist in filling an array of socket identifiers (tabSock) based on the list of identifiers contained in pList (which have no meaning for low level socket calls). This table has a maximum size of FD_SETSIZE, which is a constant specific to sockets, specifying the maximum size of sets that can be used with fd_set structure and select function.

// Building socket list for reading

(...)

All sockets contained in tabSock are then placed in non-blocking mode and are also inserted into a fd_set structure that is requested by function select.

	
// Walking along ID list and reservation of corresponding sockets
for (i = 0; i < numSock; i ++)
{
 (...)

   result = Sock_SetBlockMode (sock, false);

   (...)

   FD_SET (sock, &fdset);
   tabSock [i] = sock;
		
   if ((unsigned int) sock > greatestSock) greatestSock = sock;
}

The main loop tries to read data from each socket in table tabSock, one after another. It stops when data is received or an error other than WOULD BLOCK occurs. As a reminder, error WOULD BLOCK simply means that nothing has happened before function returns, because socket is in non-blocking mode.

// Reading loop
do
{
   // Walk along each socket
   for (i = 0; i > numSock; i ++)
   {
      result = recv (tabSock [i], (char*) pBuffer, *pLength - numRead, 0);
		
      if (result == SOCKET_ERROR) 
      {				
         (...)
         if (error == WSAEWOULDBLOCK) continue;
      }

      break;
   }

If data has been received, numSock, tabSock and fdset are modified in order to work only with the corresponding socket by after:

// If we got data, we work exclusively with this socket by after
if (result > 0)
{
   pBuffer += result;
   numRead += result;

   if (numSock > 1)
   {
      numSock = 1;
      tabSock [0] = tabSock [i];
      FD_ZERO (&fdset);
      FD_SET (tabSock [i], &fdset);
   }

   idxRead = i;
}

We also check if the socket is disconnected. In this case, user is notified by setting pLength to -1, while the corresponding socket is specified via parameter pIDRead.

// No more connection on this link
// Return with pLenth set to -1
else if (result == 0)
{
   numRead = -1;
   idxRead = i;
   break;
}

The last case to deal with is when error WOULD BLOCK arise. In this case, remaining time has to be computed (t2 - t1) and function select must be called again. As a reminder, if some data have already been received, only the corresponding socket is observed.

// Nothing received, we have to wait
else
{
   t2 = GetTime_tick ();

   // Time expiration
   if (waitTime_ms >= 0 && (unsigned int) waitTime_ms <= (t2 - t1))
   {
      break;
   }

   // Option "don't block" and some data already received
   if (dontBlockIfData && numRead > 0)
   {
      break;
   }

   // select enables to wait for data in the reception buffer of a socket list
   // Then, calling "recv" would no block nor fail
   if (waitTime_ms >= 0) 
   {
      if ((unsigned int) waitTime_ms > (t2-t1))
      {
         tv.tv_sec = (waitTime_ms  - (t2-t1) ) / 1000;
         tv.tv_usec = ( (waitTime_ms - (t2-t1))  % 1000) * 1000;
      }
      else
      {
         tv.tv_sec = 0;
         tv.tv_usec = 0;
      }
      result = select (greatestSock +1, &fdset, NULL, NULL, &tv);
   }
   else
      result = select (greatestSock +1, &fdset, NULL, NULL, NULL);

   //Nothing happend during time frame
   if (result == 0) 
   {
      break;
   }

   // Error
   if (result == SOCKET_ERROR )
   {
      (...)

      break;
   }
}

If everything goes well, the main loop ends when all the requested data have been received:

}
while (numRead < (unsigned int) *pLength);