2

I am trying to write a small client which will listen to a server on a certain port. When I run my code it seems to hang, then it will only give me the first message and not receive anymore after? Im wrecking my brains here. Any help appreciated.

    Option Strict On

Imports System.Net
Imports System.Net.Sockets
Imports System.Text

Public Class Form1

    'Form Controls.  
    Private lblPort As New Label
    Private txtPort As New TextBox
    Private lblIp As New Label
    Private txtIp As New TextBox
    Private lblSend As New Label
    Private txtSend As New TextBox
    Private WithEvents btnConnectSendReceive As New Button
    Private lblReceived As New Label
    Private lvwReceived As New ListView
    Private txtBoxrecieved As New RichTextBox

    'Global Objects.  
    Private gSocket As Socket = Nothing

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Setup form controls.  
        With Me
            .Text = "SplitPackManagerTest"
            .Controls.Add(lblPort)
            .Controls.Add(txtPort)
            .Controls.Add(lblIp)
            .Controls.Add(txtIp)
            .Controls.Add(lblSend)
            .Controls.Add(txtSend)
            .Controls.Add(btnConnectSendReceive)
            .Controls.Add(lblReceived)
            .Controls.Add(lvwReceived)
            .Controls.Add(txtBoxrecieved)
            .Height = 600
        End With
        With lblPort
            .Text = "Port:"
            .Location = New Point(12, 12)
            .AutoSize = True
        End With
        With txtPort
            .Text = "3001" 'Same port that server is listening on.  
            .Location = New Point(100, 12)
        End With
        With lblIp
            .Text = "IP:"
            .Location = New Point(12, 42)
            .AutoSize = True
        End With
        With txtIp
            .Text = "127.0.0.1" 'Loop-back IP address (localhost).  
            .Location = New Point(100, 42)
        End With
        With lblSend
            .Text = "Send:"
            .Location = New Point(12, 72)
            .AutoSize = True
        End With
        With txtSend
            .Text = Chr(2) & "(login (term-id 2))" & Chr(3)
            .Location = New Point(100, 72)
        End With
        With btnConnectSendReceive
            .Text = "Connect"
            .Location = New Point(12, 122)
            .Width = 260
        End With
        With lblReceived
            .Text = "Received Bytes:"
            .Location = New Point(12, 182)
            .AutoSize = True
        End With
        With lvwReceived
            .Height = 100
            .Dock = DockStyle.Bottom
            .View = View.Details
            .GridLines = True
            .FullRowSelect = True
            .MultiSelect = False
            .Scrollable = True
            .Columns.Add("Dec")
            .Columns.Add("Hex")
            .Columns.Add("Chr")
            For Each vCol As ColumnHeader In .Columns
                vCol.Width = CInt(Math.Floor(.Width / .Columns.Count)) - CInt(Math.Floor(30 / .Columns.Count))
            Next
        End With
        With txtBoxrecieved
            .Height = 200
            .Dock = DockStyle.Bottom
            .ScrollBars = RichTextBoxScrollBars.Both
        End With
    End Sub

    Private Function ConnectSendReceive(ByVal pSendData As Byte(), ByVal pIp As String, ByVal pPort As Integer) As Byte()

        'Try creating IP endpoint.  
        If String.IsNullOrEmpty(pIp) Then Return Nothing
        If Not IPAddress.TryParse(pIp, Nothing) Then Return Nothing
        Dim vIp As IPAddress = IPAddress.Parse(txtIp.Text)
        Dim vEndPoint As New IPEndPoint(vIp, CInt(txtPort.Text))

        'Timeout will be 0.5 seconds.  
        Dim vTimeout As Integer = 500000

        'For our little example, we expect all messages to be 1024 bytes or below (arbitrary amount).  
        Dim vMessageLength As Integer = 1002400000

        'Remember, when dimensioning arrays, the integer specified is the upper bounds, not the length.  
        Dim vServerResponse As Byte() = Nothing

        'Initiate socket.  
        Dim gSocket As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

        'Connect.  
        Try
                gSocket.Connect(vEndPoint)

        Catch ex As Exception
            Return Nothing
        End Try
            If Not gSocket.Connected Then Return Nothing

        'Send.  
        'Socket.SendTimeout = vTimeout
        gSocket.Send(pSendData)
        txtBoxrecieved.AppendText("Sending.. " & txtSend.Text)


        'Receive response.  
        'Socket.ReceiveTimeout = vTimeout
        Dim vBuffer(vMessageLength - 1) As Byte
            Dim vNumOfBytesReceived As Integer = 0
            Try
                vNumOfBytesReceived = gSocket.Receive(vBuffer, 0, vMessageLength, SocketFlags.None)
            Catch ex As Exception
                Return Nothing
            End Try

            'Return received bytes.  
            ReDim vServerResponse(vNumOfBytesReceived - 1)
            Array.Copy(vBuffer, vServerResponse, vNumOfBytesReceived)

        'Disconnect (since we're using a "Using" statement, the socket will be disconnected here without us explicitly doing it).  


        Return vServerResponse
    End Function

    Private Sub btnConnectSendReceive_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConnectSendReceive.Click

        'Send message and get response from server.  
        Dim vServerResponse As Byte() = ConnectSendReceive(Encoding.ASCII.GetBytes(txtSend.Text), txtIp.Text, CInt(txtPort.Text))

        'Did we receive a response?  
        If vServerResponse Is Nothing Then MessageBox.Show("Server not reachable / Received no response from server.") : Exit Sub

        'Do something with response.  
        For Each vByte As Byte In vServerResponse
            Dim vLvi As New ListViewItem
            With vLvi

                'Decimal column.  
                .Text = vByte.ToString

                'Hexidecimal column.  
                .SubItems.Add(vByte.ToString("X2"))

                'Character column.  
                .SubItems.Add(ChrW(vByte))
            End With
            With lvwReceived
                .Items.Add(vLvi)
                .EnsureVisible(.Items.Count - 1)
            End With

        Next
        txtBoxrecieved.AppendText(UnicodeBytesToString(vServerResponse))
    End Sub
    Private Function UnicodeBytesToString(
    ByVal bytes() As Byte) As String

        Return System.Text.Encoding.ASCII.GetString(bytes)
    End Function
End Class

THanks

David

Brownd92
  • 75
  • 1
  • 11
  • gSocket.Receive should be in a loop. If the message is 10k, you could receive just 1k at a time. In short, calling the function once does not guarantee to receive the full message. Also, your comment is wrong vMessageLength is not 1025 byte, it's 1g. The vBuffer array will be huge. – the_lotus Aug 28 '15 at 12:17
  • Thanks for that :-) Do you mean a while loop? Sorry but not very experienced... – Brownd92 Aug 28 '15 at 12:26
  • Yes, you need to loop until you have receive all the information. This mean you need to receive a fix number of byte or have a packet header that contain the size of the information. – the_lotus Aug 28 '15 at 12:30

1 Answers1

0

You need to know what's happening on both sides of this conversation. To do that, you need to listen in with a tool like Wireshark. Download Wireshark and learn to use it.

The reason I tend to agree with the_lotus is that the very large buffer you have allocated (ignoring the likely sizing bug) implies that you are expecting a fairly large response. Packet payloads can be up to 64KB but that's uncommon and even if such a thing occurs the protocol allows for fragmentation en route.

So if the data is split over several packets you'll need to keep reading until you have a complete payload. This may entail looking for boundary markers in a stream, and if there's a possibility of receiving more payloads over the same connection you'll need to preserve the start of the next payload in a way that allows you to accumulate the rest of it.

If you do that you will probably see a complete packet with a partial payload, as described by the_lotus in a comment posted while I was still writing.

I think he's probably right, but he could also be wrong, and a tool like Wireshark will allow you to find out, instead of guessing and hacking your code around in a blind attempt to validate your hypotheses.

The style of I/O that you are using is called polling. Sometimes it's necessary to do it that way, eg embedded systems often don't support anything more sophisticated, there isn't space for it. But on a system with a full .NET framework you really ought to use IOCP. It's not strictly necessary for a single user app, but if you pick up the right habits you won't create scalability problems.

OK, as to why the response is truncated, after examining your code (VB makes my eyes hurt) I'm almost sure the_lotus is right. You are reading exactly once from the socket and then you are closing it without any attempt to check whether you have the entire response or any attempt to read more data.

This looks like a first year university assignment, so presumably you have some sort of specification. To determine whether you have the entire response you need to know either one of the following

  • an expected payload length
  • the location and structure of data in the payload telling you how long the remainder is (or sometimes the size of the total packet)
  • an end of payload marker, this will be a particular sequence of bytes guaranteed not to otherwise occur in the payload.

Some pseudocode

open the connection
while not CheckForCompletePayload(buffer)
  read some data and append it to your buffer 
close the connection.
miken32
  • 42,008
  • 16
  • 111
  • 154
Peter Wone
  • 17,965
  • 12
  • 82
  • 134