I am trying to write an asynchronous TCP client with VB.NET to communicate with a thermal printer (Intermec Px4e), but I am facing an issue regarding the way I receive the data from it.
Previously I was using a synchronous client, but I had the problem regarding the response not coming instantly from the printer, so after sending data and then checking for available bytes I was able to read only the first bytes of the response.
This is the synchronous client I am actually using:
Private Function sendBytesEthernet(ByVal data As Byte(), ByVal withResponse As Boolean) As PrinterPacket(Of Byte())
Dim socketStatus As Net.Sockets.SocketError
Dim response As New PrinterPacket(Of Byte()) With {.StatusCode = CommandStatusCode.ERR, _
.Response = String.Empty, _
.Data = data}
Try
If Me.oggettoSocket.Client.Poll(2000, Sockets.SelectMode.SelectWrite) Then
Me.oggettoSocket.Client.Send(data, 0, data.Length, 0, socketStatus)
Else
Throw New Exception(socketStatus.ToString())
End If
If withResponse Then
Dim bytesAvailable As UInteger = Me.oggettoSocket.Available
If bytesAvailable > 0 Then
Dim dataBuffer(bytesAvailable - 1) As Byte
Dim bytesReceived As UInteger = Me.oggettoSocket.Client.Receive(dataBuffer, 0, dataBuffer.Length, Sockets.SocketFlags.None, socketStatus)
If socketStatus = Sockets.SocketError.Success Then
If bytesReceived > 0 Then
response.Response = System.Text.Encoding.UTF8.GetString(dataBuffer)
response.StatusCode = CommandStatusCode.OK
Return response
End If
Else
Throw New Exception(socketStatus.ToString())
End If
End If
End If
If socketStatus = Sockets.SocketError.Success Then
response.StatusCode = CommandStatusCode.OK
Else
Throw New Exception(socketStatus.ToString())
End If
Catch ex As Exception
Select Case ex.Message
Case "ConnectionReset"
Me.oggettoSocket.Client.Disconnect(True)
Me.openSocket()
End Select
response.Response = ex.Message
End Try
Return response
End Function
What happens is that If send a command to the printer that involves a big response from it, I receive the full response splitted in several packets, so with this function I am only able to read the first packet.
For example:
I send to the printer the command to list all the fonts available (FONTS[CRLF])
The printer responds, but it sends the first 54 bytes and then truncates the list.
The printer sends the second packet, this time 1560 bytes long, but it still truncates the list.
Lastly the printer completes the list with last 25 bytes.
Basically with my function I am only able to read the first 54 bytes, unless I don't use a loop to check the availability on the receive buffer in conjunction with a Thread.Sleep() call, since the client is synchronous and I have to wait for the printer to send every packet, but I find this method really inefficient.
At this point I concluded that I had to move to an asynchronous approach, due to the nature of the TCP protocol. I copied an example of async client on the MSDN and it works, but it doesn't fit my use case. Why?
Because I actually have a thread that constantly communicate with the printer through the socket, and I open the connection when the thread starts and close it when the thread ends. If use, for example, the BeginReceive callback used in the MSDN example, that call will never end until I close the connection client side. This cannot take place, since I keep the connection open to the printer and I can't establish a new TCP connection for every thread cycle (that would be a waste in terms of network traffic).
I hope I have explained the problem well.
Related
I'm trying to use Raspberry Pi and a 7" touchscreen to create an Assetto Corsa dashboard, coding with vb on the Universal Windows Platform.
I'm experiencing A LOT of problems with this platform because objects are very different from .net and I find the Microsoft's logic very strange.
However, I try to explain the problem: Assetto Corsa have his own telemetry that when the user starts a session begins to send some data to the connected clients on port 9996, UDP Protocol.
Really, the game is not so simple because to "bind" a client to the server there is a "procedure" that consists in:
The client have to send a structured data containing 3 ints ("110")
The server responds sending to the client some data containing the
track name, the player name and others
The client must send another structured data, this time containing
"111" After this, client and server are "handshaked" and the server
start sending continuous data to the client containing a lot of data
concerning the car's physics.
Here is a more accurate description of the handshaking protocol: Assetto corsa UDP Telemetry documentation
Since those are the first time that I approach UPD communication, I would have been sure to have correctly understood how it works, so I wrote this python script:
from socket import *
from struct import *
serverName='127.0.0.1'
serverPort = 9996
clientSocket = socket(AF_INET,SOCK_DGRAM)
message=pack('iii',1,1,0)
clientSocket.sendto(message, (serverName, serverPort))
messageback, ipAddress=clientSocket.recvfrom(1024)
print 'received'
pktformat='408c'
n = calcsize(pktformat)
print n
#n = len(messageback)
#print n
backString = unpack(pktformat, messageback)
#print backString[1,2,3,4,5,6,7,8,9,10]
#print "fffffff"
message=pack('iii',1,1,1)
clientSocket.sendto(message, (serverName, serverPort))
i=0
while 1:
pktformat='4chfff??????fffhhhhfffffhf4f4f4f4f4f4f4f4f4f4f4f4f4f4fff5f'
messageback, ipAddress =clientSocket.recvfrom(16384)
n = calcsize(pktformat)
print n
n = len(messageback)
print n
backString = unpack(pktformat, messageback)
print backString
i+=1
clientSocket.close()
Everything works but Python GUIs are not so good to see so I would move to VS+VB+UWP+WIN10IoT.
Dim s As String = "110"
Dim byteCommand As Byte() = Encoding.ASCII.GetBytes(s)
Dim writer As New DataWriter(listener.OutputStream)
writer.WriteBytes(byteCommand)
Try
Await listener.ConnectAsync(hostname, "9996")
Await writer.StoreAsync()
Catch ex As Exception
testo = ex.Message
refreshTb()
End Try
With this code I'm able to receive THE FIRST communication from server, so I repeat the code sending "111":
Private Async Sub HandShake(inStream As IBuffer)
If handShaked Then
Exit Sub
End If
handShaked = True
Dim writer As New DataWriter(listener.OutputStream)
Dim s As String = "111"
Dim byteCommand As Byte() = Encoding.ASCII.GetBytes(s)
writer.WriteBytes(byteCommand)
Await writer.StoreAsync()
testo = "hand-shaked!"
End Sub
After this, nothing happens.
I tried:
1. Send data as string, instead of byte(). Nothing changed
2. Send "XXX" instead of "110": I expected to not receive nothing but I received the same as when I send "110"!!!
So I'm starting to think that the I'm not correctly converting "110" so I'm sending a kind of data to server that it's not able to understand.
Any ideas?
I’m after some help with a problem i have been working on for the past few days but i can't seem to get it to work correctly.
I have multiple clients connecting to a server, the server needs to keep a list of connected clients and remove them from the list if the client disconnects, I have this bit working correctly.
Once a client is connected the server may call upon the client for some information about its state by sending a few bytes of data to which the client responds with 2 bytes of data. Based on this response the server will complete any one of number of different tasks.
I have had this working synchronously, though now I am trying to make the function asynchronous and I’m running in to an issue. The best I can tell my function that request the information from the client is completing before the client responds and is returning incorrect data, if I print the data received from the client I can see it is the correct information.
This is my 1st time working with asynchronous functions and connections so it’s highly likely I’ve got it completely wrong, I have looked at lots of example code but I can seem to find any that sheads any light on my issue.
This is what I have at the moment:
'create the collection instance to store connected clients
Private clients As New List(Of TcpClient)
'declare a variable to hold the listener instance
Private listener As TcpListener
'declare a variable to hold the cancellation token source instance
Private tokenSource As CancellationTokenSource
Private recvTsk As Task
Private Rreply As New List(Of Byte)
Private Async Sub startTCPListenerServer()
'get port from ini file
Dim netPort = SettingsIniFile.GetString("Setup", "NetworkPort", "")
While True
'create a new cancellation token source instance
tokenSource = New CancellationTokenSource
'create a new listener instance bound to the desired address and port
listener = New TcpListener(IPAddress.Any, netPort)
'start the listener
listener.Start()
While True
Try
Dim client As TcpClient = Await listener.AcceptTcpClientAsync
clients.Add(client)
Dim clientIP As String = client.Client.RemoteEndPoint.ToString
ListBox1.Items.Add(clientIP)
Try
'begin reading from the client's data stream
Using stream As NetworkStream = client.GetStream
Dim buffer(client.ReceiveBufferSize - 1) As Byte
Dim read As Integer = 1
'if read is 0 client has disconnected
While read > 0
recvTsk = New Task(Sub()
For i = 0 To read - 1
'data recived at this point is correct
Rreply.Add(buffer(i))
Next
End Sub, tokenSource.Token)
read = Await stream.ReadAsync(buffer, 0, buffer.Length, tokenSource.Token)
recvTsk.Start()
End While
'client gracefully closed the connection on the remote end
End Using
Catch ocex As OperationCanceledException
'the expected exception if this routines's async method calls honor signaling of the cancelation token
'*NOTE: NetworkStream.ReadAsync() will not honor the cancelation signal
Catch odex As ObjectDisposedException
'server disconnected client while reading
Catch ioex As IOException
'client terminated (remote application terminated without socket close) while reading
Finally
'ensure the client is closed - this is typically a redundant call, but in the
'case of an unhandled exception it may be necessary
'remove the client from the list of connected clients
clients.Remove(client)
client.Close()
ListBox1.Items.Remove(clientIP)
'remove the client's task from the list of running tasks
'clientTasks.Remove(client.Task)
End Try
Catch odex As ObjectDisposedException
'listener stopped, so server is shutting down
Exit While
End Try
End While
For i As Integer = clients.Count - 1 To 0 Step -1
clients(i).Close()
Next
tokenSource.Dispose()
End While
'signal any processing of current clients to cancel (if listening)
tokenSource.Cancel()
'abort the current listening operation/prevent any new connections
listener.Stop()
'End If
End Sub
Async Function sendToPod(message() As Byte, podNum As Integer) As Task(Of Byte)
If clients.Count = 0 Then
Else
Dim podIP As String
'get ip address as string from ini file
podIP = SettingsIniFile.GetString("NetworkSettings", "Pod" & podNum & "IP", "")
Dim currentClient As TcpClient = Nothing
For Each client As TcpClient In clients
Dim clientIP As String = (CType(client.Client.RemoteEndPoint, IPEndPoint).Address.ToString())
If clientIP = podIP Then
currentClient = client
End If
Next
If currentClient IsNot Nothing Then
'get the current client, stream, and data to write
Dim stream As NetworkStream = currentClient.GetStream
Dim buffer() As Byte = message
'wait for the data to be sent to the remote
Await stream.WriteAsync(buffer, 0, buffer.Length)
recvTsk.Wait()
Return Rreply(1)
End If
End If
End Function
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Try
Me.TextBox1.Text = Await sendToPod({"&HAB", "&HAD", "&HFF", "&HFF"}, 1)
Catch
End Try
End Sub
End Class
Is there a way to keep a TcpClient connection open constantly? I have an application which lets our users scan a carton, some database updates are performed, and a shipping label is sent to and printed from a wireless hip printer (model of printer is Zebra QLn420) which the user is using.
The application attempts to keep a connection to the wireless printer via the TcpClient connection, and multiple checks are made throughout processing to make sure the connection is good, before sending a generated ZPL to the printer for printing.
We have been having an issue with an occasional label missing, and it seems to be whenever the user stops scanning for a few minutes, then resumes. However, it is a semi-rare occurrence when a label is skipped, and as such is rather hard to reproduce (I haven't been able to replicate it myself, but I have seen it happen out in the warehouse).
I would like to know either if there is a way to make sure that the connection is always open (by "pinging" the device every so often), or if there is a way to get feedback that the data has been received and printed.
This is the code I'm calling to ensure a connection:
Public Function Connect(strIP As String, intPort As Integer) As Boolean
Try
'connect to printer via TcpClient, need ip address and port number
'connects without thread, hangs program for 10-20 seconds if printer is not turned on, replaced with code below to thread the connection and set timeout
For i As Integer = 1 To 2
If Not (client IsNot Nothing AndAlso client.Connected) Then
'uses ClientSocketParameters structure to pass to recursive function ConnectionReturned()
clntSockParams = New ClientSocketParameters
clntSockParams.addrs = strIP
clntSockParams.prt = intPort
'create client and call BeginConnect (attempts to connect on separate thread until TimeoutTime has elapsed)
client = New System.Net.Sockets.TcpClient
client.SendTimeout = 5000
client.ReceiveTimeout = 5000
'setup timer with timeout length and start, if timer goes past intTimeoutLength, the Timeout() function is called which closes everything and leaves client = Nothing
AddHandler TimeoutTime.Elapsed, AddressOf Timeout
TimeoutTime.Interval = intTimeoutLength
TimeoutTime.Start()
client.BeginConnect(strIP, intPort, New AsyncCallback(AddressOf ConnectionReturned), clntSockParams)
'keeps the program from doing anything else until BeginConnect either succeeds or fails (due to connect on separate thread)
Do While TimeoutTime.Enabled
System.Threading.Thread.Sleep(500)
Loop
End If
'if TimeoutTime is elapsed and client is Nothing, connection didn't happen, throw an error
If client Is Nothing Then
blnConnected = False
Else
blnConnected = True
Exit For
End If
Next
Catch ex As Exception
blnConnected = False
End Try
Return blnConnected
End Function
Private Sub ConnectionReturned(ByVal ar As System.IAsyncResult)
'this method is called from the client.BeginConnect line in Connect(), make sure timer is running
If TimeoutTime.Enabled Then
'ensure client is initialized
If client Is Nothing Then client = New System.Net.Sockets.TcpClient
'keep calling ConnectionReturned until client.Connected is true
If client.Connected Then
TimeoutTime.Stop()
Else
Dim actualParameters As ClientSocketParameters = DirectCast(ar.AsyncState, ClientSocketParameters)
client.BeginConnect(actualParameters.addrs, actualParameters.prt, New AsyncCallback(AddressOf ConnectionReturned), clntSockParams)
End If
End If
End Sub
Private Sub Timeout(ByVal sender As Object, ByVal e As EventArgs)
'this method is only called if TimeoutTime elapsed, which means no connection was made. close the client object if needed, set to Nothing, and stop TimeoutTime
If TimeoutTime.Enabled Then
Try
client.Close()
Catch ex As Exception
End Try
client = Nothing
TimeoutTime.Stop()
End If
End Sub
According to this question:
tcp client in vb.net not receiving the entire data response data from server
TcpClient is not always guaranteed to deliver all data to the other end of a connection, so if a more reliable connection method is available, that would be worth a try as well.
Please let me know if more information is needed. Thank you!
Originally I got the code for connecting via this link. I've modified it since because it would hang the application for 10-20 seconds if it took longer to connect. Code here is in C# and I translated to VB:
Send ZPL Commands via TCP/IP in C#
This is the link to the docs for the class:
TcpClient Class
Zebra printers have a timeout setting on TCP that I think has a 3 or 5 minute default. The first thing to do is to turn that timeout off. There will still be other reasons that the printer would disconnect so you will need to handle that as well.
embed this into your program:
! U1 setvar "wlan.ip.timeout.enable" "off"
Make sure you send a CR/LF before and after that line.
if you send a query after your format you can know that the whole format made it to the printer. Something like the following would work:
! U1 getvar "device.uptime"
That's not a sane solution. A sane solution is this: If a label isn't completely sent to the printer because the connection failed, make a new connection and send the label.
I'm developing some software that listens for events from another program via TCP.
The events come in a string formatted like this: "value1;value2;value3"
The problem is that sometimes a single character from value1 gets read onto the previous event so i get something like this:
value1;value2;value3v
alue1;value2;value3
How can i make it know where each message begins and ends?
Here is the code:
Dim client As New TcpClient()
Sub listen()
Dim networkStream As Stream = client.GetStream()
While True
Dim bytes(client.ReceiveBufferSize) As Byte
Dim size As Integer = 0
size = networkStream.Read(bytes, 0, CInt(client.ReceiveBufferSize))
'process event here
End While
End Sub
This is all done on a thread of it's own.
Sender has to included the length of each data its sending so at receiver you will receive upto the provided length. Easier option would be to include NULL termination so that you can read it as individual string at receiver end.
I am not a software programmer but I have a task to create a TCP Server (a program that is listening on its network card interfaces for incoming data streams).
I have searched on the internet and I found that I can use two methods: Socket or TCPListener class.
I have created an example for the Socket class, but I was wondering how I can test it?
If another computer in the network sends some string data to the listener computer, then the message should be displayed.
Here is the example from Microsoft that I am using for the TCP server using a Socket:
Public Shared Sub Main()
' Data buffer for incoming data.
Dim data = nothing
Dim bytes() As Byte = New [Byte](1024) {}
Dim ipAddress As IPAddress = ipAddress.Any
Dim localEndPoint As New IPEndPoint(ipAddress, 0)
Dim intI As Integer = 0
'Display the NIC interfaces from the listener
For Each ipAddress In ipHostInfo.AddressList
Console.WriteLine("The NIC are {0}", ipHostInfo.AddressList(intI))
intI += 1
Next
Console.WriteLine("You are listening on {0}",localEndPoint)
' Create a TCP/IP socket.
Dim listener As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
' Bind the socket to the local endpoint and
' listen for incoming connections.
Try
listener.Bind(localEndPoint)
listener.Listen(200)
Catch e As SocketException
Console.WriteLine("An application is alreading using that combination of ip adress/port", e.ErrorCode.ToString)
End Try
' Start listening for connections.
While True
Console.WriteLine("Waiting for a connection...")
' Program is suspended while waiting for an incoming connection.
Dim handler As Socket = listener.Accept()
data = Nothing
' An incoming connection needs to be processed.
While True
bytes = New Byte(1024) {}
Dim bytesRec As Integer = handler.Receive(bytes)
data += Encoding.ASCII.GetString(bytes, 0, bytesRec)
Console.WriteLine("The string captured is {0}", data)
If data.IndexOf("something") > -1 Then
Exit While
End If
End While
' Show the data on the console.
Console.WriteLine("Text received : {0}", data)
' Echo the data back to the client.
Dim msg As Byte() = Encoding.ASCII.GetBytes(data)
handler.Shutdown(SocketShutdown.Both)
handler.Close()
End While
End Sub
End Class
Am I on the right lead?
Thanks
Later Edit:
I have used that code in a Console Application created with Visual Studio and I want to check the scenario when a device is sending some string message through the network.
E.g:
I have two devices :Computer A, computer B connected through LAN
I have tried this command : telnet computerA port ( from computer B) but nothing is displayed in the TCP server running from computer A.
telnet 192.168.0.150 3232
I also made a TCP client for testing (derived from the Microsoft example):
Public Class SynchronousSocketClient
Public Shared Sub Main()
' Data buffer for incoming data.
Dim bytes(1024) As Byte
Dim ipHostInfo As IPHostEntry = Dns.GetHostEntry(Dns.GetHostName())
Dim ipAddress As IPAddress = ipHostInfo.AddressList(0)
Dim remoteEP As New IPEndPoint(ipAddress, 11000)
' Create a TCP/IP socket.
Dim sender As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
' Connect the socket to the remote endpoint.
sender.Connect(remoteEP)
Console.WriteLine("Socket connected to {0}", _
sender.RemoteEndPoint.ToString())
' Encode the data string into a byte array.
Dim msg As Byte() = _
Encoding.ASCII.GetBytes("This is a test<EOF>")
' Send the data through the socket.
Dim bytesSent As Integer = sender.Send(msg)
' Receive the response from the remote device.
Dim bytesRec As Integer = sender.Receive(bytes)
Console.WriteLine("Echoed test = {0}", _
Encoding.ASCII.GetString(bytes, 0, bytesRec))
' Release the socket.
sender.Shutdown(SocketShutdown.Both)
sender.Close()
Console.ReadLine()
End Sub
End Class 'SynchronousSocketClient
But it does not work because of the PORT setting.
If in the TCP Server I have "Dim localEndPoint As New IPEndPoint(ipAddress, 0)" then the client crashes, but if I change the port from any (0) to 11000 for example, the client works fine.
Do you know why?
Later edit 2:
Maybe I should have started with this question: Which method is recommended for my scope - asynchronous or synchronous method?
Yes, you are on the right path.
The next thing to do is to introduce message detection since TCP is stream based and not message based like UDP. This means that TCP might decide to send two of your messages in the same packet (so that one socket.Recieve will get two messages) or that it will split up your message into two packets (thus requiring you to use two socket.Recieve to get it).
The two most common ways to create message detection is:
Create a fixed size header which includes message size
Create a delimiter which is appended to all messages.
Your "server" isn't listening on a set port, so you'll need to pay attention to the "You are listening on" message that appears. Then, from another machine on the network, telnet the.ip.add.ress port. (This may require installing "telnet client", or enabling it in the Programs and Features stuff, or whatever.)
Side note...if you actually intend for this to be a server of some sort, you'll want to decide what port you want to use, so that other computers can find your service. Most people won't be able to read your screen to figure out where to connect. :)
As for your "client"...when you connect to another computer, you don't just "pick a port" (which is what a port number of 0 means in an endpoint). You need to know what port the server uses. (Reread what i said in the previous paragraph. A program running on another computer has no idea what port to use to connect to the server -- any server could be running on any port.) You need to pick a port number for the server (say, 11000...good as any, really) rather than letting it use port 0.