System designHow-to

Read smart camera data from a VB.NET TCP client safely

Split complete smart camera messages on a delimiter and never lose data: a VB.NET TCP client receive loop that survives partial reads and merged lines.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01Why does a VB.NET TCP read split or merge smart camera messages?
  2. 02What you need from the camera before writing code
  3. 03How to connect and send a command from VB.NET
  4. 04How the receive loop keeps every byte
  5. 05How to turn the buffer into complete camera messages
  6. 06How to show results on the UI without a cross-thread exception
  7. 07How to test against partial and merged reads
  8. 08When is a delimiter loop the wrong pattern?
  9. 09Questions

Your smart camera is set to push inspection results as short ASCII lines over a TCP socket, and the VB.NET program that should display them occasionally shows half a line, two results glued together, or the tail of an old reading. Nothing is wrong with the camera: TCP is a byte stream, and the receive code treats one Read call as one message.

This walkthrough builds the receive side properly: a background thread, a byte buffer that carries partial frames between reads, and a delimiter split that emits only complete lines. It ends with a test that proves an 18-byte line split into reads of 5 and 13 bytes still arrives intact. It is written for controls engineers wiring smart cameras and vision sensors to a PC-side VB.NET application.

Why does a VB.NET TCP read split or merge smart camera messages?

TCP carries bytes with no message boundaries. One NetworkStream.Read returns whatever has arrived so far — between 1 byte and the count you passed in. That splits an 18-byte camera line into reads of 5 and 13 bytes, or delivers two finished lines in one 36-byte read.

TcpClient and NetworkStream are documented as stream abstractions over a socket, and the grouping of bytes into messages is deliberately left to you. Three symptoms follow from that contract:

  • Truncated lines. The camera sends OK,12.44,68.02,1 (18 bytes including the CR LF); the first Read after a trigger returns 5 bytes, and your code displays OK,12 as if it were the whole result.
  • Merged lines. Two triggers land close together, the socket is holding 36 bytes, one Read returns all of them, and two results display as one string.
  • Stale tails. The loop decodes the entire 10,025-element array instead of just the bytes Read returned, so leftovers from the previous read repeat after every short message.

All three are fixed by the same two moves: count what Read returned, and frame messages on a delimiter before decoding.

What you need from the camera before writing code

Four values: the camera's IP address (for example 192.168.0.100), its TCP port (for example 49211), the command that requests a result such as jobinfo followed by CR LF, and the delimiter that ends each line — CR, LF or CR LF on most result outputs.

Where each one lives:

  • Address and port — the communication or I/O page of the camera's setup software. Set the camera to TCP server mode, so it listens and your program connects; the same role logic decides whether a barcode reader is the server or the client, and the TCP client or listener guide walks that choice.
  • Command string — what wakes a result: a trigger command such as jobinfo with a CR LF, or nothing at all if the camera streams each result as it happens. The camera side of that string format is its own subject; the OCR string output guide shows what such a message typically carries.
  • Delimiter — the bytes that end one line, usually the two bytes \r\n (decimal 13 then 10). Every framing decision below assumes one fixed delimiter.
  • Encoding — ASCII on nearly every result output, so one byte is one character and a character can never be split mid-decode.

If the host is a PLC rather than a VB program, the framing problem is identical and only the client code differs — the PLC side is covered in how to configure a PLC as the TCP/IP client to a smart camera server.

How to connect and send a command from VB.NET

Three calls: a New TcpClient, Connect to the camera's address and port, then a Write of the command as ASCII bytes. Add a 3,000 ms receive timeout, so a silent camera throws an exception you can log instead of hanging the loop forever.

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

' Address, port and command are example values from a camera
' commissioning sheet; take yours from the communication settings page.
Private Const CameraAddress As String = "192.168.0.100"
Private Const CameraPort As Integer = 49211
Private Const CommandText As String = "jobinfo" & vbCr & vbLf
Private Const ChunkSize As Integer = 4096

Private client As TcpClient
Private stream As NetworkStream

Private Sub ConnectToCamera()
    client = New TcpClient()
    client.ReceiveTimeout = 3000      ' ms: a silent camera throws, not hangs
    client.Connect(CameraAddress, CameraPort)
    stream = client.GetStream()

    Dim outBytes As Byte() = Encoding.ASCII.GetBytes(CommandText)
    stream.Write(outBytes, 0, outBytes.Length)
End Sub

Any timeout from 2,000 to 5,000 ms works; what matters is that a dead camera surfaces as an exception with a timestamp. Skip the Flush that older socket listings carry — NetworkStream.Flush does nothing on a network stream, because the class does not buffer writes.

How the receive loop keeps every byte

Read into a 4,096-byte array in a loop on a background thread. Take the returned count, and append exactly that many bytes to an accumulator. A Read that returns 0 means the camera closed the socket, so exit and reconnect rather than spinning on an empty stream.

Private Sub ReceiveLoop()
    Dim chunk(ChunkSize - 1) As Byte       ' 4,096 bytes per Read call
    Dim pending As New List(Of Byte)       ' bytes of unfinished lines

    While client IsNot Nothing AndAlso client.Connected
        Dim n As Integer = stream.Read(chunk, 0, chunk.Length)

        If n = 0 Then
            Exit While                     ' remote end closed: reconnect
        End If

        For i As Integer = 0 To n - 1
            pending.Add(chunk(i))          ' append exactly n bytes
        Next

        ExtractLines(pending)
    End While
End Sub

A blocking Read waits until at least one byte has arrived and returns 0 only after the remote end closes — that zero is your disconnect signal, not an empty result. Accumulate bytes rather than decoded strings so no character is interpreted before its frame is complete. On disconnect, clear pending, retry the connect every 2,000 ms and log each attempt; the loop then re-enters cleanly.

How to turn the buffer into complete camera messages

Search the accumulator for the CR LF delimiter, decode and emit everything before it, then delete those bytes including the delimiter. Whatever follows the last delimiter — say a 5-byte OK,12 — stays in the buffer and finishes on the next read.

' Emits every complete CR LF terminated line and keeps the remainder.
Private Sub ExtractLines(ByVal pending As List(Of Byte))
    While True
        Dim endIdx As Integer = IndexOfCrLf(pending)
        If endIdx < 0 Then Exit While      ' no complete line yet

        Dim lineBytes(endIdx - 1) As Byte
        pending.CopyTo(0, lineBytes, 0, endIdx)
        OnLineReceived(Encoding.ASCII.GetString(lineBytes))

        pending.RemoveRange(0, endIdx + 2) ' the line plus CR LF
    End While
End Sub

Private Function IndexOfCrLf(ByVal data As List(Of Byte)) As Integer
    For i As Integer = 0 To data.Count - 2
        If data(i) = 13 AndAlso data(i + 1) = 10 Then Return i
    Next
    Return -1
End Function

The two scans per read cost nothing at result-line sizes: 100-byte lines scanned twice at 50 lines per second is 100 x 50 x 2 = 10,000 byte comparisons per second, far below any modern processor's budget. Only the receive thread touches pending, so no locking is needed.

How to show results on the UI without a cross-thread exception

Never write to a TextBox from the receive thread: WinForms throws an InvalidOperationException on cross-thread control access. Hand each complete line to the UI with BeginInvoke and let the message loop append it — one line of marshalling, no lost updates.

Private Sub OnLineReceived(ByVal line As String)
    ' txtShow is the results TextBox on the form.
    Me.BeginInvoke(Sub() txtShow.AppendText(line & Environment.NewLine))
End Sub

Private Sub StartReceiver()
    Dim receiver As New Thread(AddressOf ReceiveLoop)
    receiver.IsBackground = True           ' lets the app close cleanly
    receiver.Start()
End Sub

BeginInvoke queues the append onto the UI message loop, and the queue keeps first-in-first-out order, so results display in exactly the order the camera sent them. The string handed across is never modified after creation, so no synchronization is needed on that path.

How to test against partial and merged reads

Feed ExtractLines the exact byte patterns a real socket delivers: 5 bytes with no delimiter, then 13 bytes that finish the line. Then one 35-byte read carrying two lines, and confirm two complete strings come out with an empty buffer — 18 + 17 = 35 bytes.

Private Sub AppendBytes(ByVal target As List(Of Byte), ByVal text As String)
    target.AddRange(Encoding.ASCII.GetBytes(text))
End Sub

' Test 1: an 18-byte line split across two reads, 5 + 13 bytes.
pending.Clear()
AppendBytes(pending, "OK,12")                       ' 5 bytes, no delimiter
ExtractLines(pending)                               ' emits nothing: correct
AppendBytes(pending, ".44,68.02,1" & vbCr & vbLf)   ' 13 bytes finish it
ExtractLines(pending)                               ' emits OK,12.44,68.02,1

' Test 2: two lines in a single 35-byte read, 18 + 17 bytes.
pending.Clear()
AppendBytes(pending, "OK,12.44,68.02,1" & vbCr & vbLf & _
    "NG,0.01,68.10,0" & vbCr & vbLf)
ExtractLines(pending)                               ' emits both, in order

The byte counts check out by hand: OK,12 is 5 characters; .44,68.02,1 is 11 characters plus CR LF, so 13; the two full lines are 16 + 2 and 15 + 2 characters, so 18 + 17 = 35 bytes in one read.

Then run it against the live camera:

  1. Set the camera's result output to TCP server mode and note its address and port, for example 192.168.0.100 on port 49211.
  2. Put the same values into CameraAddress and CameraPort, and set CommandText to your camera's trigger command — or skip the write if the camera streams each result on its own.
  3. Run the program, connect, trigger the camera, and confirm exactly one line appears in txtShow per trigger.
  4. Unplug the camera's Ethernet cable mid-run: the loop must exit on the zero-byte Read and surface a disconnect.
  5. Reconnect and confirm the next trigger resumes cleanly, with no half lines carried over from the break.
  6. Leave the raw-byte log running for a full shift before trusting the link in production.

When is a delimiter loop the wrong pattern?

Whenever the payload is binary or arrives without a delimiter: image frames and packed numeric records need length-prefix framing — read a 4-byte header that states the payload length, then read exactly that many bytes — and the same accumulator carries them.

Two cases decide it:

  • Binary payloads. A CR LF pair can appear inside binary data — two bytes of a floating-point value can be 13 and 10 — so delimiters are unsafe there. Keep the accumulator, change the boundary rule: header first, payload second, decode once.
  • Images on the result socket. A 2 MB frame at 30 fps is 2,000,000 x 30 = 60 MB per second, past Fast Ethernet's 100 Mbit/s / 8 = 12.5 MB per second and uncomfortably heavy for a shared GigE link. Keep result sockets for text and compact records; pull images over the camera's image interface or a dedicated link.

For this pattern the camera only needs an Ethernet interface and a result output you can point at a socket; the vision sensors and smart cameras below all publish inspection results as text over an Ethernet link.

Smart cameras & vision sensorsHikrobot MV-SC3050XC-16M-ATN-H 5 MP color vision sensorMV-SC3050XC-16M-ATN-H
Interface
Fast Ethernet
Resolution
5 MP
$1,809.00View part
Smart cameras & vision sensorsHikrobot MV-SC5060XM-08M-WBN SC5000X series Smart Camera, 6 MP, Mono, 8 mm focal length, M12-mountMV-SC5060XM-08M-WBN
Interface
GigE
Resolution
6-10 MP
$1,629.00View part
Smart cameras & vision sensors1.6 MP smart camera, 60 fps, 16 mm lens, white light (polarized, transparent light) (LS-SMC-0063)LS-SMC-0063
Interface
GigE
Resolution
Up to 3 MP
$1,110.00View part

If the same program also switches lights and talks to the PLC, host it on a vision controller instead of an office desktop; the VC2100 model below brings 4 built-in light channels and 2 USB 3 ports to run this parsing code beside the cameras.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

Where the parsed lines go next matters as much as receiving them: the smart camera data logging guide compares three logging paths with the bandwidth arithmetic, and host-to-device raw socket wiring follows the same discipline in barcode reader to PLC over raw TCP sockets. To size the camera side first, browse the camera finder; to scope a controller for the whole station, send the station details through the quote form and we will help you pick one.

Questions

What buffer size should you use for camera result lines?

A 4,096-byte read chunk is ample for ASCII result lines, which rarely exceed 100 bytes. The chunk size only bounds each Read call, not the message length, because the accumulator carries unfinished bytes across reads. Framing decides whether bytes are lost, not buffer size: even a 10,025-element array loses data if you decode it without the returned byte count.

Why do old bytes or strange characters appear at the end of received strings?

You are decoding the whole array instead of the bytes Read returned: Encoding.ASCII.GetString(buffer) converts all 10,025 elements, including stale bytes left from the previous read. Pass the count instead, GetString(buffer, 0, n). Splitting on the delimiter already hides stale bytes in most cases, but decoding only n bytes keeps your raw log honest.

Can you read the camera socket on the UI thread with a timer instead?

Yes, by polling TcpClient.Available and calling Read only when bytes are waiting, but a timer under 50 ms is still less responsive than a dedicated thread, and heavy processing on the UI thread delays reads. A background thread with a blocking Read and a BeginInvoke hand-off is simpler, keeps up with every trigger and never freezes the form.

Does the same receive pattern work for barcode readers and other Ethernet devices?

Yes: any device that pushes delimiter-terminated ASCII, including code readers, scale interfaces and label printers, works with the identical loop. Change only the address, port, command and delimiter. If the device instead expects your host to be the server, only the connect direction changes; the framing loop stays the same.

How many result lines per second can one TCP socket handle?

Far more than any line produces. An 18-byte result line at 2 parts per second is 18 x 2 = 36 bytes per second, while Fast Ethernet alone moves 100 Mbit/s / 8 = 12.5 MB per second and GigE about 125 MB/s. The practical ceiling is your parsing and display code, so keep the receive loop free of heavy work.