views:

280

answers:

5

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?

+2  A: 

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.

Jon Skeet
I'm finding a problem with the Read and IOException instead of TimeoutException. I'm just investigating and will return with the result.
SoMoS
Looks like when I have a timeout the TcpClient is disconnected and the socket closed so I can not retry the read again.
SoMoS
+1  A: 

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.

Jason Williams
+1  A: 

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
Microgen
No need, thanks anyway.
SoMoS
+2  A: 

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).

SoMoS