Issues with non-blocking sockets

B

BMermuys

Hi,
[inline]

Michi Henning said:
Yes. I read the documentation too. What I'm suggesting is that, no matter
how well documented, the behavior is wrong. It doesn't make sense
to throw an exception for expected behavior (getting no data from a
non-blocking socket), but to not throw for unexpected behavior
(when the connection is lost).


No. With the above code, I would loop indefinitely waiting for data if the
connection is lost, because Available returns zero in that case, even though
the documentation says that it throws an exception (which it does not).


Sorry, but I don't get it. The behavior of Receive() is simply back to front:
it throws an exception for the expected behavior, and doesn't throw an
exception for unexpected behavior.

All this behaviour is mostly because the socket class isn't a new clean
desing but a wrapper around the winsock API. And the api function recv sets
the last error to WSA_WOULDBLOCK when you read on a non-blocking socket
which hasn't any data available.
..NET wraps this error/warning code inside a SocketException.

In the winsock API , recv never returns 0 when no data is available. As for
as I know, receive should only return 0 when the client gracefully
disconnected.
That's simply throwing rocks in my
path because it makes a my control structures a mess. And Available
doesn't behave as the documentation states it should.

If the behavior were right, I wouldn't have to pay the extra cost of
making yet another system call before every attempted read, just to
see whether the connection is still up. Besides, doing this is unnecessary:
I *can* work out whether the connection is lost by checking whether
Select() returned a readable socket, but the physical read returned zero
bytes.

Yes, that's the way you should do it, only read on readable sockets returned
from Select, and if receive throws or returns 0 then the connection is lost.

Greetings
 
M

Michi Henning

Hi,

below is a small program that reads from a non-blocking socket.
To try this, compile and run the program in a window.
Then, from another window, telnet to 127.0.0.1, port 12345.

Here is the code:

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;

class Server
{
static void Main(string[] args)
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345));
s.Blocking = true;
s.Listen(1);
Socket c = s.Accept();
c.Blocking = false;
while(true)
{
ArrayList readList = new ArrayList();
readList.Add(c);
ArrayList errorList = new ArrayList(readList);
Socket.Select(readList, null, errorList, 1000000);
Console.WriteLine("Socket is " + (readList.Count == 0 ? "not readable" : "readable"));
if(errorList.Count != 0)
{
Console.WriteLine("Select indicated an error");
}
Console.WriteLine("Available is " + c.Available);
byte[] buf = new byte[1];
try
{
int rc = c.Receive(buf);
Console.WriteLine("Read returns " + rc);
}
catch (System.Exception ex)
{
Console.WriteLine("got an exception: " + ex.GetType().Name);
}
}
}
}

Once the telnet session connects, the server prints (once per second):

Socket is not readable
Available is 0
got an exception: SocketException

This is surprising. Even though the behavior agrees with the documentation,
why does Receive() throw an exception when I try to read from a *non-blocking*
socket and no data is available? Not getting any data from a non-blocking socket
is an expected outcome, so it would be far better if Receive() returned zero bytes
instead of throwing.

Now, here comes the interesting bit. From a third window or the task manager, kill
the telnet session to force disorderly connection closure.

The server now goes into an infinite loop with the following output:

Socket is readable
Available is 0
Read returns 0

Here is what disturbs me:

- The Available property is zero. However, the documentation states that
"If the remote host shuts down or closes the connection, Available
throws a SocketException."

This exception is not thrown.

- The Receive() call succeeds and returns zero bytes. This seems to be exactly
the wrong way around: if the socket is functional, but no data is available,
Receive() throws, and if the socket is dysfunctional, Receive() returns zero bytes.

In particular the Receive() behavior makes a mess of the coding logic: if no data is
available (which is normal for a non-blocking socket), I have to write a catch handler
to deal with the expected outcome. However, if the connection is lost, I don't get
an exception, and I now have to remember whether Select() returned a readable descriptor
earlier, but Receive() returned zero bytes to detect connection loss.

Why is this so? The control constructs that result from this are ugly, to say the least.
It seems like throwing rocks into the path of programmers...

Cheers,

Michi.
 
S

Steve Lutz

Michi:

Check out the Socket documentation:

Your are setting the socket to be non-blocking. ( c.Blocking = false;).
Socket.Recieve will cause an exception.
From the documentation:
If no data is available for reading, the Receive method will block until
data is available. If you are in non-blocking mode, and there is no data
available in the in the protocol stack buffer, the Receive method will
complete immediately and throw a SocketException. You can use the Available
property to determine if data is available for reading. When Available is
non-zero, retry the receive operation.

In order for the code to work as you have it, it should be something like:
if (c.Available > 0)
{
int rc = c.Receive(buf);
// do stuff with the data
}
As to the 2nd problem are you having, you should check the socket status
using Socket.Connected first. Also, you should wrap all your important code
into try-catch structures, so that you can catch exceptions and handle them.
This is just good programming practice. Example:

try
{
if (c.Connected)
{
if (c.Available > 0)
{
int rc = c.Receive(buf);
// do stuff with the data
}
}
}
catch (Exception se)
{
Console.Writeline(se.Message);

}




Michi Henning said:
Hi,

below is a small program that reads from a non-blocking socket.
To try this, compile and run the program in a window.
Then, from another window, telnet to 127.0.0.1, port 12345.

Here is the code:

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;

class Server
{
static void Main(string[] args)
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345));
s.Blocking = true;
s.Listen(1);
Socket c = s.Accept();
c.Blocking = false;
while(true)
{
ArrayList readList = new ArrayList();
readList.Add(c);
ArrayList errorList = new ArrayList(readList);
Socket.Select(readList, null, errorList, 1000000);
Console.WriteLine("Socket is " + (readList.Count == 0 ? "not readable" : "readable"));
if(errorList.Count != 0)
{
Console.WriteLine("Select indicated an error");
}
Console.WriteLine("Available is " + c.Available);
byte[] buf = new byte[1];
try
{
int rc = c.Receive(buf);
Console.WriteLine("Read returns " + rc);
}
catch (System.Exception ex)
{
Console.WriteLine("got an exception: " + ex.GetType().Name);
}
}
}
}

Once the telnet session connects, the server prints (once per second):

Socket is not readable
Available is 0
got an exception: SocketException

This is surprising. Even though the behavior agrees with the documentation,
why does Receive() throw an exception when I try to read from a *non-blocking*
socket and no data is available? Not getting any data from a non-blocking socket
is an expected outcome, so it would be far better if Receive() returned zero bytes
instead of throwing.

Now, here comes the interesting bit. From a third window or the task manager, kill
the telnet session to force disorderly connection closure.

The server now goes into an infinite loop with the following output:

Socket is readable
Available is 0
Read returns 0

Here is what disturbs me:

- The Available property is zero. However, the documentation states that
"If the remote host shuts down or closes the connection, Available
throws a SocketException."

This exception is not thrown.

- The Receive() call succeeds and returns zero bytes. This seems to be exactly
the wrong way around: if the socket is functional, but no data is available,
Receive() throws, and if the socket is dysfunctional, Receive() returns zero bytes.

In particular the Receive() behavior makes a mess of the coding logic: if no data is
available (which is normal for a non-blocking socket), I have to write a catch handler
to deal with the expected outcome. However, if the connection is lost, I don't get
an exception, and I now have to remember whether Select() returned a readable descriptor
earlier, but Receive() returned zero bytes to detect connection loss.

Why is this so? The control constructs that result from this are ugly, to say the least.
It seems like throwing rocks into the path of programmers...

Cheers,

Michi.
 
M

Michi Henning

Steve Lutz said:
Your are setting the socket to be non-blocking. ( c.Blocking = false;).
Socket.Recieve will cause an exception.
From the documentation:
If no data is available for reading, the Receive method will block until
data is available. If you are in non-blocking mode, and there is no data
available in the in the protocol stack buffer, the Receive method will
complete immediately and throw a SocketException.

Yes. I read the documentation too. What I'm suggesting is that, no matter
how well documented, the behavior is wrong. It doesn't make sense
to throw an exception for expected behavior (getting no data from a
non-blocking socket), but to not throw for unexpected behavior
(when the connection is lost).
In order for the code to work as you have it, it should be something like:
if (c.Available > 0)
{
int rc = c.Receive(buf);
// do stuff with the data
}

No. With the above code, I would loop indefinitely waiting for data if the
connection is lost, because Available returns zero in that case, even though
the documentation says that it throws an exception (which it does not).
As to the 2nd problem are you having, you should check the socket status
using Socket.Connected first.

Sorry, but I don't get it. The behavior of Receive() is simply back to front:
it throws an exception for the expected behavior, and doesn't throw an
exception for unexpected behavior. That's simply throwing rocks in my
path because it makes a my control structures a mess. And Available
doesn't behave as the documentation states it should.

If the behavior were right, I wouldn't have to pay the extra cost of
making yet another system call before every attempted read, just to
see whether the connection is still up. Besides, doing this is unnecessary:
I *can* work out whether the connection is lost by checking whether
Select() returned a readable socket, but the physical read returned zero
bytes. So the extra system call definitely isn't needed. But I shouldn't have
to go through all these contortions and, if the API were designed better,
I wouldn't have to.

What really should be happening is that both Available and Receive()
should throw if the connection is lost, and not throw if no data is available.
That way, at least I could write a decent loop that isn't littered with
exception handlers and awkward control structures.
Also, you should wrap all your important code
into try-catch structures, so that you can catch exceptions and handle them.
This is just good programming practice.

Yes, I'm aware of the need to handle exceptions. I distilled the example
to the bare minimum to illustrate my point.

Cheers,

Michi.
 
M

Michi Henning

All this behaviour is mostly because the socket class isn't a new clean
desing but a wrapper around the winsock API. And the api function recv sets
the last error to WSA_WOULDBLOCK when you read on a non-blocking socket
which hasn't any data available.
.NET wraps this error/warning code inside a SocketException.

Right. I know that's where the problem originally came from. It's just sad
to see that these mistakes are blindly being repeated, even for a new
API that need not be burdened with such legacy issues.

If no-one is willing to fix such things, at least the discrepancy between
the behavior and the documentation of Available should be fixed.

Cheers,

Michi.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top