Preventing Socket Blocks
Sockets are delicate by nature. They are designed for a network that never fails, transfers data at infinite speeds, and thus always works under ideal conditions. However, this is the real world, and therefore some extra code must be employed to ensure that our sockets respond well to the hazards of everyday use.
One potentially big issue is that of not having enough data to read. Imagine a client-server system where messages can be of variable size and where you read data with recv calls. As a parameter, you pass the amount of data you want to read back from a call, but how are you supposed to know how much data there is without looking at the socket in the first place?
You could play it safe by reading data on a byte level: one byte at each call. However, that strategy would be slow and could potentially backfire on you. If no data is available, the socket would still block.
At least two different methods can cope with this situation. The first tries to take "sneak peeks" at sockets before actually reading any data from them, ensuring that we read as much as is available. The second allows us to directly request any number of bytes from the socket while making sure it does not block due to insufficient data.
In the first method, we will use the flags provided by the recv() call. These flags can have the following values:
0
Default value, no special behavior.
MSG_OOB
Used to handle Out-Of-Band data. OOB data is data marked as urgent by the sender. To send OOBs, the sender must specify MSG_OOB in the send call. Then, when the receiver uses the MSG_OOB flag on the recv call, the OOB data is retrieved as an individual element, outside of the sequence stream.
MSG_PEEK
Used to peek at the socket without reading data from it. A recv call with MSG_PEEK correctly returns the number of bytes available for a subsequent recv call, but does not remove them from the incoming queue.
Clearly, MSG_PEEK can be very useful to us. A code sequence like the following correctly ensures that we only read as much data as the socket effectively holds:
#define BUFFERSIZE 256
Char *buffer=new char[BUFFERSIZE] ;
int available=recv(sock,buffer,BUFFERSIZE,MSG_PEEK) ;
recv(sock,buffer,available,0);
This way we can prevent the blocking nature of sockets.
Another strategy is to convert our regular socket into a nonblocking socket. Nonblocking sockets do not get locked if there is not enough data in the incoming pipeline. They just return the call and inform us of this circumstance. To create a nonblocking socket, we first need to open it, and once the communications channel is set, modify its characteristics with the powerful fcntl() call. Here is a simple UNIX example that converts a socket passed as an argument to nonblocking:
void setnonblocking(int sock)
{
int opts = fcntl(sock,F_GETFL);
if (opts < 0)
{
perror("fcntl(F_GETFL)");
exit(EXIT_FAILURE);
}
opts = (opts | O_NONBLOCK);
if (fcntl(sock,F_SETFL,opts) < 0)
{
perror("fcntl(F_SETFL)");
exit(EXIT_FAILURE);
}
return;
}
|