Please can someone tell me why my server locks up when my rclient connects and try to send data?
If I comment out:
Dim bytes(rclient.ReceiveBufferSiz
You are assuming that reads return a specific amount of data such as ReceiveBufferSize
. That is not true. A read returns at least one byte, that is all.
Just to clarify, TCP does not support message based transfer.
The correct way to read depends on the protocol. If the exact number of bytes expected is known you need to read in a loop until so many bytes are received (or use BinaryReader which does that for you).
For a line based protocol you can use StreamReader.ReadLine
which again automates the looping.
ReceiveBufferSize
is completely unrelated to how much data is available or will come.
DataAvailable
is how much data can be read right now without blocking. But more data might come. It is almost always a bug to use it. Might return 0 at any time even if data comes in 1ms later.
EDIT: For those of you who have downloaded/tested this already, I made a bugfix to the classes so you'll need to redownload the sources if you're going to use them again.
If you want to perform proper data transfer you'll need to use a more reliable method than just simply reading random data. And as usr pointed out: the TcpClient.ReceiveBufferSize
property does not tell you how much data there is to receive, nor how much data there is sent to you. ReceiveBufferSize
is just a variable indicating how many bytes you are expected to get each time you read incoming data. Read the MSDN page about the subject for more info.
As for the data transfer, I've created two classes which will do length-prefixed data transfer for you. Just import them to your project and you'll be able to start using them immediatelly. Link: http://www.mydoomsite.com/sourcecodes/ExtendedTcpClient.zip
Server side
First declare a new variable for ExtendedTcpClient
, and be sure to
include WithEvents
in the declaration.
Dim WithEvents Client As ExtendedTcpClient
Then you just need to use a normal TcpListener
to check for
incoming connections. The TcpListener.Pending()
method can be
checked in for example a timer.
When you are to accept a new TcpClient
, first declare a new
instance of the ExtendedTcpClient
. The class requires to have a
form as it's owner, in this application Me
is the current form.
Then, use the ExtendedTcpClient.SetNewClient()
method with
Listener.AcceptTcpClient()
as it's argument to apply the
TcpClient
from the listener.
If Listener.Pending() = True Then
Client = New ExtendedTcpClient(Me)
Client.SetNewClient(Listener.AcceptTcpClient())
End If
After that you won't be needing the timer anymore, as the
ExtendedTcpClient
has it's own thread to check for data.
Now you need to subscribe to the PacketReceived
event of the
client. Create a sub like so:
Private Sub Client_PacketReceived(sender As Object, e As ExtendedTcpClient.PacketReceivedEventArgs) Handles Client.PacketReceived
End Sub
In there you can for example output the received packet as text into
a TextBox
. Just check if the packet header is PlainText
and then
you can convert the received packets contents (which is an array of
bytes, accessed via e.Packet.Contents
) to a string and put it in
the TextBox
.
If e.Packet.Header = TcpMessagePacket.PacketHeader.PainText Then
TextBox1.AppendText("Message recieved: " & System.Text.Encoding.Default.GetString(e.Packet.Contents) & Environment.NewLine)
End If
Lastly, when closing the form you just need to disconnect the client.
Private Sub ServerWindow_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If Client IsNot Nothing Then Client.Disconnect()
End Sub
And that's it for the server side.
Client side
For the client side you will only be needing a normal TcpClient
(unless you don't want to receive data there too).
Dim Client As New TcpClient
Then connect to the server via the IP and port you've given the listener.
Client.Connect("127.0.0.1", 5555) 'Connects to localhost (your computer) at port 5555.
Now if you want to send plain text to the server you'd do something like this:
Dim MessagePacket As New TcpMessagePacket(System.Text.Encoding.Default.GetBytes(TextBox2.Text), TcpMessagePacket.PacketHeader.PainText)
MessagePacket.Send(Client) 'Client is the regular TcpClient.
And now everything should be working!
Link to a complete example project: http://www.mydoomsite.com/sourcecodes/TCP%20Messaging%20System.zip
If you want to add more headers to the class, just open TcpMessagePacket.vb
and add more values in the PacketHeader
enum (located in the region called Constants
).
Hope this helps!
(Click the image for larger resolution)