How to close a TcpClient when the other end has been closed in .NET?
Question
I have a class that handles TcpClients. What the class should do is:
while the other end has not done a graceful close or we are stopping
{
receive a request
process it
send response
}
As I don't know when the other client will send a request I can not do a Read with a timeout set, so what I have until now is this:
While Not Me.Stopping()
Try
If tcpClient.Available >= My.Settings.minimumModBusTcpFrameSize Then
processer = New MessageProcesser(Me, tcpClient)
processer.ProcessMessage()
End If
Catch ex As TimeoutException
''#Do not nothing, the current message will timeout on origin too.
End Try
End While
The problem with this approach is that I never know when a client has done a remote call to Close().
Is there a way of solving this problem?
Solution 5
The solution is at this question:
How to read with a TcpClient until the other side closes or we stop in .Net
OTHER TIPS
I don't see why you can't do a Read
with a timeout... If the read times out you could retry it, whereas if Read
returns 0 then the connection has been closed.
EDIT: Yup, I've confirmed the behaviour here - a further read does indeed appear to fail. That's really strange... I'm leaving my answer here as the one I feel should be appropriate - hopefully at some point I'll have time to investigate it again.
Just a test to show the problem found implementing the Jon Skeet answer:
Public Class Form1
Private m_listener As Net.Sockets.TcpListener
Private m_client As Net.Sockets.TcpClient
Private m_stopping As Boolean
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim data As Byte()
Dim dataLength As Integer
ReDim data(512)
m_listener = New Net.Sockets.TcpListener(Net.IPAddress.Any, 502)
m_listener.Start()
m_client = m_listener.AcceptTcpClient()
m_client.GetStream().ReadTimeout = 1000
m_client.GetStream().WriteTimeout = 1000
While Not m_stopping
Try
dataLength = m_client.GetStream.Read(data, 0, data.Length)
If dataLength = 0 Then
MsgBox("Disconnected")
End If
Catch ex As Exception When TypeOf (ex) Is TimeoutException OrElse (Not ex.InnerException Is Nothing AndAlso TypeOf (ex.InnerException) Is Net.Sockets.SocketException AndAlso DirectCast(ex.InnerException, Net.Sockets.SocketException).ErrorCode = 10060)
''# Just retry
End Try
End While
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
m_stopping = True
End Sub
End Class
If you connect with a Telnet client you will get an InvalidOperationException because the socket has been closed after the timeout time (one second).
Jon's already answered, but if you have control of the client, then you could also add a request that means "please close the connection", so you can attempt a "graceful close", and just use Jon's "auto detect" approach if the client fails to close the connection tidily.
I have a similar case but with udpclient. I am catching a SocketException to find out if the remote endpoint is no longer available:
While True
Try
Dim RecievedBytes As Byte() = udp.Receive(mRemoteIP)
mMessage = System.Text.Encoding.ASCII.GetString(RecievedBytes)
RaiseEvent MessageRecieved()
Catch ex As Sockets.SocketException
MsgBox("Your firewall may be blocking you from recieving messages")
End Try
End While