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

Chapter 3 - Blocking or not blocking ?

The first thing you want to know when using low level socket functions in C/C++ is the notion of blocking call and non-blocking call. Indeed, it changes radically the way to use them.

A socket can be configured in either one or other mode at any time. I refer the reader to the consultation of the "ioctlsocket" and "fcntl" function manual for more details on the syntax and usage. The following function is extracted from the library and shows how to switch from one mode to another.

Under Windows :

int Sock_SetBlockMode (int socket, bool blocking)
{
   int result;
   unsigned long arg;
	
   if (blocking == true) arg = 0;
   else arg = 1;
	
   result = ioctlsocket (sock, FIONBIO, &arg); 
   return result;
}

Under Linux :

int Sock_SetBlockMode (int socket, bool blocking)
{
   int flags;
   int r;
	
   flags = fcntl (sock, F_GETFL);
	
   if (blocking == true)
      r = fcntl (sock, F_SETFL, flags & ~O_NONBLOCK);	
   else
      r = fcntl (sock, F_SETFL, flags | O_NONBLOCK);	
	
   return r;
}

The behavior of the socket function will be different depending on the mode in which it is configured:

Non blocking: a function called in non-blocking mode will return immediately whatever happens. The calling task does not remain blocked waiting for a given event (specific event depends on the function).

Blocking: a function called in blocking mode will return immediately only if the expected event has already taken place. Otherwise, the calling task is put into sleep (no consumption of CPU time) and will be awakened as soon as the event happen.

Shakespearean question then arises: "what mode should I use then ?"

To answer this question, let's take a clear example. Suppose we want to receive data from a remote application on our socket. This involves calling a function like "Receive" on the socket, passing as arguments the identifier of the socket, along with a buffer for data reception (the precise function will be seen below). Now let's see what the algorithm looks like, depending on whether the socket is blocking or not.

Non-blocking mode

do {
   result = Receive (numSocket, buffer, expectedDataLen)

   if (result == <Rien recu>) {
      <Attendre 100 ms>;
   }
   else {
      <Traiter données reçues>;
   }
}
while (Condition d'arrêt>)

The main loop of reception runs until any stop condition is met. Function Receive returns immediately. Therefore, if data had already been received on the socket, they are copied in the supplied buffer. Otherwise, no data is copied into the buffer. Therefore, function result must be tested in order to determine whether or not data have been received.

This pseudo-code algorithm is relatively simple. The only particular point is the waiting of 100 ms. The artificial muting of the task is indispensable. Otherwise the task would call again the Receive function immediately, in order to know if data have finally been received. The task would loop forever until these data arrive. Although the result is functionally correct, it would be a disastrous in term of efficiency and performance.

Indeed, this algorithm requires the processor to constantly test the stop condition. Because the program runs in a multitasking environment where the computer processor is shared between all the programs that run in parallel, this means that computation time that could have been distributed to another application is stupidly wasted in this simple loop. It may follow a slowdown of the machine that can be considerable.

By forcing the task to sleep for a given period, the stop condition is tested only once every 100 ms and the remaining time is redistributed to other applications by the operating system. How long to sleep is to choose carefully. Shorter time means the processor will be solicited more often. Longer time means poorer reactivity of our reception loop. Indeed, in our example, it may take up to 100 ms between data reception on the socket and the effective reading of these data. If the flow is important, the data may even saturate the internal reception queue of the socket.

Blocking mode

do {
   result = Receive (numSocket, buffer, expectedDataLen)

   if (result == <Rien recu>) {
   }
   else {
      <Traiter données reçues>;
   }
}
while (<Condition d'arrêt>)

The receiving loop is substantially identical to the previous version. However, its execution is dramatically different. Indeed, when the task enters the Receive function, it will not leave it until the expected number of data bytes (expectedDataLen) has been received. This can be immediate if these data are already available in the internal reception queue of the socket, or never if the application at the other end has decided to be silent.

Here the performance problem does not arise and we don't have to suspend the reception task ourself at each iteration. Indeed, the function Receive suspends itself if needed and no CPU time is therefore wasted. The event that wakes the task is the availability of the expected data (and not an arbitrary delay). Thus, there is no latency between the reception of data and the awakening of our reception task.

What is the best ?

In fact, none of the methods is perfect. The first suffers of alack of reactivity at the reception level, while the second, although highly reactive, may potentially block the task indefinitely. This behaviour can be very constraining for many reasons. For example, we might want to stop the reception loop after a while, when some conditions are met. But this is not possible, because the function Receive doesn't give back hand until expected data have been received (or if the connection is interrupted). Then, the stop condition would never be tested.

What we need is a way allowing the Receive function to block for a maximum adjustable time. With this approach, we would keep our responsiveness for data reception, but we would also be sure that the Receive function gives back hand from time to time, even if our expected data have not been received. Fortunately this method is possible, although more complex to implement, and is the subject of the next chapter.