reconnect tcp

R

RedLars

After reviewing several posts on this forum it appears to be that the
best way to deal with tcp disconnect
is to send data and handle any exceptions.

Below is a small example that I have used for test purposes. If I
remove the network cable during
sending a list of packets to the server I get several different
SocketExceptions. It seems like if the
network cable is out for less than roughly 15 seconds I get
SocketExceptions with socketErrorCode 10060
and 10035 (10060 is received first and only once). In this case the
tcp connection is not lost and the
application can continue to re-use the connection. However, if the
cable is out for more than 15 seconds
I get all SocketExceptions with SocketErrorCode 10060, 10035 and after
15 seconds I get 100054 (10060 is received first and only once). In
this case the connection is lost and it appear that the application
needs to re-connect to the server.

How do I know when to re-connect to the server? It seems redundant to
reconnect if connection isnt lost.

private byte[] SendTransaction(byte[] request)
{
try
{
/* PerformTransaction uses TcpClient.GetStream to get a
networkStream.
* This code is located in a client application that communicates
with a server via
* request and response packages. PerformTransaction uses
TcpClient.GetStream to get a hold
* of a NetworkStream. It uses Read, ReadBytes, Write and WriteBytes
to send\recv the packets.
*/
return networkprotocol.PerformTransaction(request);
}
catch(IOException)
{
/* error handling missing */
}
}
 
R

RedLars

It's not clear from your post why you are getting error # 10035.  You  
should only see that when using non-blocking sockets.  Are you using  
non-blocking sockets?  That is, have you set the Socket.Blocking property  
to "false"?

As for the more general question, if a send or receive method on a Socket 
instance throws a fatal exception, then that connection is done.  You need  
to close the socket and reconnect.  The only non-fatal exception you  
should see in .NET is error # 10035 (WSAEWOULDBLOCK), and then only if  
you're using the Socket directly and you've configured the Socket instance  
to be a non-blocking socket (which in .NET is mostly pointless, because  
.NET offers much better asynchronous i/o methods).

What little code you posted seems to imply that you're not even using the 
Socket instance directly, but instead are using it via a NetworkStream and  
TcpClient.  In this case, any exception is likely to be fatal.  Even if  
NetworkStream put the Socket into the non-blocking state (and I don't  
think it should), it shouldn't be letting the WSAEWOULDBLOCK exception to 
filter back to your application code.

Finally note that for some exceptions, it may _appear_ that you are able  
to continue using the Socket, but in fact doing so could result in data  
corruption.  My recollection was that .NET already prevents this access 
and that it's only an issue when using Winsock via the native API.  Butif  
you're seeing a fatal exception from a Socket and yet are still able to  
use the Socket for i/o after the exception, I doubt this is a legal use of  
the Socket.

Of course, without a concise-but-complete code example, it's impossible to  
make any specific, concrete statements about what you should or should not  
do.  If you want a good answer, you need to post a concise-but-complete 
code example that reliably demonstrates the issue.

Pete– Skjul sitert tekst –

– Vis sitert tekst –

To illustrate the issue I have made a small client \ server
application. Code is attach at the end of post.

Here is the output from the client application (arguments are ip,
port, timeout). Shortly after starting the client application the
network cable is unplugged for a few seconds then reconnected again.
It appear that the client during that time gets a few exceptions but
then manages to continue sending and recving packets from server. It
appear like the exceptions are "non-fatal" and does not warrent a
reconnect.

Appreciate any input and please let me know if anything is unclear.

C:\Temp>client 157.237.193.87 6789 2000
Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
IOExceptionUnable to read data from the transport connection
Stack
at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32
offset, Int32 size)
at Client.Program.Main(String[] args) Client\Program.cs:line 40
SocketInnerException10060

Client send ID=421, len=5, data=10 20 11 22 33
IOExceptionUnable to read data from the transport connection:
Stack at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32
offset, Int32 size)
at Client.Program.Main(String[] args) in Client\Program.cs:line 40
SocketInnerException10035

Client send ID=421, len=5, data=10 20 11 22 33
IOExceptionUnable to read data from the transport connection
Stack at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32
offset, Int32 size)
at Client.Program.Main(String[] args) in Client\Program.cs:line 40
SocketInnerException10035

Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
IOExceptionUnable to read data from the transport connection
Stack at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32
offset, Int32 size)
at Client.Program.Main(String[] args) in Client\Program.cs:line 40
SocketInnerException10035

Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83
Client send ID=421, len=5, data=10 20 11 22 33
Client recv ID=124, len=5, data=77 88 66 72 83

Here is the output from server app (arguments port, timeout)

C:\temp>server 6789 2000
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33
Server send ID=124, len=5, data=77 88 66 72 83
Server recv ID=421, len=5, data=10 20 11 22 33

namespace Server
{
class Program
{
static void Main(string[] args)
{
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any,
Int32.Parse(args[0]));
TcpListener listener = new TcpListener
(endpoint);
listener.Start();
while (true)
{
Socket s = listener.AcceptSocket();
ClientHandler ch = new ClientHandler(s, Int32.Parse
( args[1]) );
}
}
}

class ClientHandler
{
private Socket client;
private int timeout;

public ClientHandler(Socket client, int timeout)
{
this.timeout = timeout;
this.client = client;
Thread clientThread = new Thread(new ThreadStart
(ClientSession));
clientThread.Start();
}

public void ClientSession()
{
int id = 124;
byte[] buffer = { 0x77, 0x88, 0x66, 0x72, 0x83 };
int length = 5;
NetworkStream stream = null;
try
{
stream = new NetworkStream(client);

while (true)
{
int rxDataCounter = 0;
int receiveBytes = 8;
byte[] recvHeader = new byte[receiveBytes];
while (rxDataCounter < receiveBytes)
{
int localCounter = stream.Read(recvHeader,
rxDataCounter, receiveBytes - rxDataCounter);
if (localCounter == 0)
{
Console.WriteLine("Error Reading Header");
return;
}
rxDataCounter += localCounter;
}

int recv_id = IPAddress.NetworkToHostOrder
(BitConverter.ToInt32(recvHeader, 0));
int recv_len = IPAddress.NetworkToHostOrder
(BitConverter.ToInt32(recvHeader, 4));

rxDataCounter = 0;
byte[] recvData = new byte[recv_len];
while (rxDataCounter < recv_len)
{
int localCounter = stream.Read(recvData,
rxDataCounter, recv_len - rxDataCounter);
if (localCounter == 0)
{
Console.WriteLine("Error Reading data");
return;
}
rxDataCounter += localCounter;
}

Console.WriteLine(string.Format("Server recv ID=
{0}, len={1}, data={2}", recv_id, recv_len, DumpBytes(recvData)));

Thread.Sleep(timeout);

Console.WriteLine(string.Format("Server send ID=
{0}, len={1}, data={2}", id, length, DumpBytes(buffer)));
stream.Write(BitConverter.GetBytes
(IPAddress.HostToNetworkOrder(id)), 0, 4);
stream.Write(BitConverter.GetBytes
(IPAddress.HostToNetworkOrder(length)), 0, 4);
stream.Write(buffer, 0, 5);
}
}
catch (IOException io)
{
Console.WriteLine("IOException" + io.Message);
Console.WriteLine("Stack" + io.StackTrace);
SocketException se = io.InnerException as
SocketException;
if (se != null) Console.WriteLine
("SocketInnerException" + se.NativeErrorCode.ToString());
}
finally
{
if (stream != null) stream.Close();
}
}

public static string DumpBytes(byte [] buffer)
{
System.Text.StringBuilder builder = new StringBuilder();
for (int i = 0; i < buffer.Length; i++)
{
builder.AppendFormat("{0:X2} ", buffer);
}
return builder.ToString();
}
}
}


namespace Client
{
class Program
{
static void Main(string[] args)
{
int timeout = Int32.Parse(args[2]);
TcpClient tcpClient = new TcpClient();
tcpClient.NoDelay = true;
tcpClient.ReceiveTimeout = 5000;
tcpClient.Connect(args[0], Int32.Parse(args[1]));
Stream stream = tcpClient.GetStream();

int id = 421;
byte[] buffer = { 0x10, 0x20, 0x11, 0x22, 0x33 };
int length = 5;

while(true)
{
try
{
Console.WriteLine(string.Format("Client send ID=
{0}, len={1}, data={2}", id, length, DumpBytes(buffer)));
stream.Write(BitConverter.GetBytes
(IPAddress.HostToNetworkOrder(id)), 0, 4);
stream.Write(BitConverter.GetBytes
(IPAddress.HostToNetworkOrder(length)), 0, 4);
stream.Write(buffer, 0, 5);

int rxDataCounter = 0;
int receiveBytes = 8;
byte[] recvHeader = new byte[receiveBytes];
while (rxDataCounter < receiveBytes)
{
int localCounter = stream.Read(recvHeader,
rxDataCounter, receiveBytes - rxDataCounter);
if (localCounter == 0)
{
Console.WriteLine("Error Reading Header");
return;
}
rxDataCounter += localCounter;
}

int recv_id = IPAddress.NetworkToHostOrder
(BitConverter.ToInt32(recvHeader, 0));
int recv_len = IPAddress.NetworkToHostOrder
(BitConverter.ToInt32(recvHeader, 4));

rxDataCounter = 0;
byte[] recvData = new byte[recv_len];
while (rxDataCounter < recv_len)
{
int localCounter = stream.Read(recvData,
rxDataCounter, recv_len - rxDataCounter);
if (localCounter == 0)
{
Console.WriteLine("Error Reading data");
return;
}
rxDataCounter += localCounter;
}

Console.WriteLine(string.Format(
"Client recv ID={0}, len={1}, data={2}",
recv_id, recv_len, DumpBytes(recvData)));

}
catch (IOException io)
{
Console.WriteLine("IOException" + io.Message);
Console.WriteLine("Stack" + io.StackTrace);
SocketException se = io.InnerException as
SocketException;
if (se != null) Console.WriteLine
("SocketInnerException" + se.NativeErrorCode.ToString());
}
Thread.Sleep(timeout);
}
}

public static string DumpBytes(byte[] buffer)
{
System.Text.StringBuilder builder = new StringBuilder();
for (int i = 0; i < buffer.Length; i++)
{
builder.AppendFormat("{0:X2} ", buffer);
}
return builder.ToString();
}
}
}
 
R

RedLars

Thanks for the code example.  I don't see any significant structural  
problems in the code, so at least you've got that.  :)

But, there is still the issue that you continue to try to use the Socket  
after an exception is thrown.

Even though the exceptions _seem_ to be non-fatal, I don't believe you can  
trust that they are.  Certainly, in the normal Winsock API, once you get a  
disconnect error, you're supposed to stop using the socket.  Why the  
disconnect winds up translated into a WSAEWOULDBLOCK on subsequent uses of  
the socket, I'm not sure.  That seems like a bug to me.

As well, I have been looking at the NetworkStream and Socket  
implementations in Reflector and I can see that on fatal return code from 
the Winsock socket, the Socket state is set to "disconnected", but there's  
no code in the Socket class to prevent you from trying to call Receive()  
(which NetworkStream does on your behalf) using a Socket instance that's  
been disconnected.  That also seems like a bug to me.

So, we've got plenty of bugs: Winsock is returning WSAEWOULDBLOCK as the  
error when in fact it should be returning one of the "connection reset"  
error codes; the .NET Socket class isn't treating a receive on a  
disconnected socket as an error; NetworkStream doesn't invalidate itself  
somehow when the Socket instance it's wrapped throws a fatal error; and  
your own code continues to try to use a Socket instance (via the  
NetworkStream) after it's thrown a fatal exception during a receive  
operation.

None of the other bugs would be real issue for code that uses Socket and  
NetworkStream correctly.  That is, once you've gotten the first fatal  
error, while it can be argued (and I would argue) that the managed classes  
at the very least should be handling the state more robustly, you really  
shouldn't be continuing to use the Socket.

I would even say that the .NET classes should be conservative, and let the  
client code (i.e. your own code) determine what's a "fatal" exception and 
what's not, except that Socket already has code to do that, and it  
disconnects the Socket on a fatal error (non-fatal errors, according to  
that code, are: WSAEWOULDBLOCK, WSA_IO_PENDING, or WSAENOBUFS...that is  
consistent with my understanding of Winsock).  Given that the Socket class  
is doing _some_ management of state according to error values, IMHO it  
should be doing all the necessary management so that the client code never  
can try to do the wrong thing, and gets a more useful error when they do.

But the short answer is: just because it _seems_ to work fine, that  
doesn't mean you're doing the right thing.  You really should be  
abandoning the connection when you get the first fatal error.  The fact 
that the socket appears to continue to be usable is just good fortune on  
your part, and under different send and receive patterns, you could easily  
get corrupted data (note that in your example, one of the significant  
mitigating factors is that you've disabled Nagel, and have very long  
delays -- which you inaccurately call "timeouts" -- between the i/o  
operations...these will tend to hide any defects in the code that are  
dependent on the exact send and receive pattern, but that doesn't mean you  
can _count_ on them hiding those defects).

Pete

Thanks for your response.

So basically, once I receive an SocketException on a tcp connection I
should discard the connection regardless of socketErrorCode and
establish a new connection?
 
R

RedLars

That depends on the actual error code.  But yes, for a fatal error, you 
should.

I listed the three errors .NET considers non-fatal: WSAEWOULDBLOCK,  
WSA_IO_PENDING, or WSAENOBUFS.  In truth, the only one you're likely to 
see legitimately is the first, and then only if you explicitly set the  
socket to non-blocking i/o (i.e. set Socket.Blocking to "false").  I say  
"legitimately", because you've uncovered a situation where you seem to be 
getting that error even though you didn't set the socket to non-blocking.

So, assuming you didn't intentionally set the socket to non-blocking, a  
convenient simplification is to treat any exception as fatal.

Pete

Cheers Pete
 

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