Please see below for a list of fixes / changes in version 4.0 I've left version 2 up so it's available for people who may still need a .net 2.0 only solution. This latest version requires .net 4 or higher. Version 4.0 is not compatible with previous versions!
This example project contains two classes -
TcpCommClient. With these classes, you will not only be able to instantly add TCP/IP functionality to your VB.NET applications, but it also has most of the bells and whistles we're all looking for. With these classes, you will be able to connect multiple clients to the server on the same port. You will be able to easily: throttle bandwidth to the clients, and send and receive files and data (text?) along 250 provided channels simultaneously on a single connection.
Looking for a way to transfer folders fast?
I put together an example application on how to use this library to transfer folders. The example application actually provides fast FTP like functionality, and comes with an explorer like window. Transfers are begun via drag and drop:
When I first started looking for information or example code for TCP/IP communication in VB.NET, I have to admit I was looking for it all. I was willing to accept the bits I needed to get started, but I was hoping, like you probably are, that I would find it all... some easily transportable classes that would allow me to do what I think most of us want when we first start looking into this - send and receive files and our own data or text, all over a single connection, simultaneously. It's what we'll all need eventually, for one project or another, when the time comes to build a client/server application. We'll also need to control the bandwidth at the server, or someone will use it all up downloading a large file... And oh - it would be nice if we could connect to the server on the same port from multiple clients, right?
I looked for a long time and never found anything right out of the box that would do what I needed, so I decided to build it myself. Along the way I began to understand why people don't just freely give this kind of work away. It took me quite a bit of time coding, troubleshooting, and testing this - but it is, after all, just a tool. It's what we do with it that will be worth something in the end.
So here it is.
We want to be able to do everything... right? And all over a single connection. You want to be able to send, say, a stream of screenshots, or video, or text, and not have to code your own way of separating one from another. The channel was my answer. Every time you use the
SendBytes method, you send that data with a channel identifier. When it comes out the other end in the callback, it arrives with the channel identifier you sent it with so you can tell your screenshot bytes from your text - if that's what you're sending. As I said above, you have 250 channels at your disposal (1 to 251).
OK, on to some code.
Using the code
First of all, both classes use delegates to do callbacks. All you have to do is pass the
AddressOf YourCallbackSub while instantiating these classes, and then call
Dim _Server As New tcpCommServer(AddressOf YourCallbackSub)
Dim _Client As New tcpCommClient(AddressOf YourCallbackSub)
YourCallbackSub has to have the right signature - you can see the right way to do it in the example project. You can also specify the max bps while instantiating the server class. The default value is 9MBps.
To send a byte array, use
sendBytes(). To get a file from the server, there is a
GetFile() method. Simply supply it with the path of the file on the server (a path local to the server). To send a file to the server, there's a
SendFile() method. Both
SendFile are handled by the classes without any other code needed by you - but if you'd like to track the progress of the incoming or outgoing file, you can poll the
These classes communicate with each other, and with you using a limited protocol language. You will be notified in your callback when your bytes have been sent (using
sendBytes), when your file has completed downloading or uploading, if a file transfer has been aborted, and if you receive an error either locally or from the server. You will receive these messages from your client or server on channel 255. This is a quick look at these messages, and how I handled them in the example project's client form's callback sub:
Public Sub UpdateUI(ByVal bytes() As Byte, ByVal dataChannel As Integer)
If Me.InvokeRequired() Then
' InvokeRequired: We're running on the background thread. Invoke the delegate.
Me.Invoke(_Client.ClientCallbackObject, bytes, dataChannel)
' We're on the main UI thread now.
Dim dontReport As Boolean = False
If dataChannel < 251 Then
ElseIf dataChannel = 255 Then
Dim msg As String = BytesToString(bytes)
Dim tmp As String = ""
' Display info about the incoming file:
If msg.Length > 15 Then tmp = msg.Substring(0, 15)
If tmp = "Receiving file:" Then
gbGetFilePregress.Text = "Receiving: " & _Client.GetIncomingFileName
dontReport = True
' Display info about the outgoing file:
If msg.Length > 13 Then tmp = msg.Substring(0, 13)
If tmp = "Sending file:" Then
gbSendFileProgress.Text = "Sending: " & _Client.GetOutgoingFileName
dontReport = True
' The file being sent to the client is complete.
If msg = "->Done" Then
gbGetFilePregress.Text = "File->Client: Transfer complete."
btGetFile.Text = "Get File"
dontReport = True
' The file being sent to the server is complete.
If msg = "<-Done" Then
gbSendFileProgress.Text = "File->Server: Transfer complete."
btSendFile.Text = "Send File"
dontReport = True
' The file transfer to the client has been aborted.
If msg = "->Aborted." Then
gbGetFilePregress.Text = "File->Client: Transfer aborted."
dontReport = True
' The file transfer to the server has been aborted.
If msg = "<-Aborted." Then
gbSendFileProgress.Text = "File->Server: Transfer aborted."
dontReport = True
' _Client as finished sending the bytes you put into sendBytes()
If msg = "UBS" Then ' User Bytes Sent.
btSendText.Enabled = True
dontReport = True
' We have an error message. Could be local, or from the server.
If msg.Length > 4 Then tmp = msg.Substring(0, 5)
If tmp = "ERR: " Then
Dim msgParts() As String
msgParts = Split(msg, ": ")
MsgBox("" & msgParts(1), MsgBoxStyle.Critical, "Test Tcp Communications App")
dontReport = True
' Display all other messages in the status strip.
If Not dontReport Then Me.ToolStripStatusLabel1.Text = BytesToString(bytes)
Sendbytes will accept any size byte array you hand it. It can only send
blockSize bytes at a time though, so if you hand it a very large byte array,
sendBytes will block until it's done sending it. You will want to wait until you receive the "UBS" notice before handing
sendBytes more data.
These classes will work the most efficiently if you only try to send a maximum of
blockSize bytes at a time.
BlockSize will be different depending on your current throttle speed, and the server will change the client's
blockSize immediately after connection. You can get the current
blockSize by calling the
Throttling works along a 4K boundary if you set the bandwidth to be less then 1 meg, so you can use the traditionally expected bandwidth limits - i.e.: 64K, 96K, 128K, 256K, etc. After one meg, it's along a 5K boundary - so you can set it to more intuitive values: 2.5 meg, 5 meg, 9 meg, etc. The server counts all bytes coming in and going out, and checks to make sure we're not processing more bytes then we should 4 times a second. For those who are interested, this is what the code looks like:
' Throttle network Mbps...
bandwidthUsedThisSecond = session.bytesSentThisSecond + session.bytesRecievedThisSecond
If bandwidthTimer.AddMilliseconds(250) >= Now And bandwidthUsedThisSecond >= (Mbps / 4) Then
While bandwidthTimer.AddMilliseconds(250) > Now
If bandwidthTimer.AddMilliseconds(250) <= Now Then
bandwidthTimer = Now
session.bytesRecievedThisSecond = 0
session.bytesSentThisSecond = 0
bandwidthUsedThisSecond = 0
Throttling will be important for you because these classes will do as much work as you let them - as fast as they can. On my dev machine, I was able to achieve transfer speeds of almost 300 Mbps copying one large file from the server to the client. But the cost of this was the client and the server's background threads using 100% of their respective CPU core's resources - not a good scenario for a server. On my dev machine, 9MBps produced almost no perceptible CPU usage. In a production environment, I'm sure you will want to set this even lower. Ideally, you will want to set the throttling when you instantiate your server.
Dim _Server As New tcpCommServer(AddressOf YourCallbackSub, 9000000)
You can also set the throttled bps using the
The CpuMonitor class
Included in this project, you'll find the
CpuMonitor class. This class is used to monitor the CPU usage of a thread. When running the example project, you will see some reporting of CPU usage in the status strip. This is the usage of the client's background thread - the thread actually doing the work of communicating with the server.
Points of interest
The following is a list of changes available in version 4.08 of this library.
- Configured the example project so that large arrays can be sent to the server on channel 100 as well as received from the server on that channel. To send the example large array to the server on channel 100, click Send in the client when the send text box is empty.
The following is a list of changes available in version 4.07 of this library.
- Redesigned the CPU idle throttling / disconnection detection system so that disconnections are more reliably detected. Single byte keep alive packets are now sent from the client and the server every two seconds during idle time.
The following is a list of changes available in version 4.06.1 of this library.
- Bugfix: The client now polls TcpClient.Client.Connected as part of it's normal connection verification testing. This is to ensure that the client is made aware of loss of connection while it is sending data.
The following is a list of changes available in version 4.05 of this library.
- The client will now disconnect after failing to receive a keepalive packet from the server for longer then 7 seconds (the server sends them every 5 seconds).
The following is a list of changes available in version 4.04 of this library.
- Added the ability to specify which ipaddress you would like the server to listen on in your system, or select 0.0.0.0 for IPAddress.Any via a combo box on the server form.
The following is a list of changes available in version 4.03 of this library.
- Added locking and synchronization code to GetFile() and SendFile(). Both are now thread safe and extremely performant when sending or receiving many small files.
The following is a list of changes available in version 4.02 of this library.
- Bugfix: Corrected an issue where Mbps wasn't being reported correctly when sending a file.
The following is a list of changes available in version 4.01 of this library.
- Bugfix: The client was using excessive CPU resourses (the tcp library wasn't idling while inactive). Found by skdia15.
The following is a list of changes available in version 4.0 of this library.
- This version does NOT use a synchronization byte. This change was to increase performance during file transfers.
- The throttling system can now be disabled, by entering a 0 in the server's constructor instead of a max Mbps value.
- Bugfix: A bug that was causing the client to disconnect occasionally was found and resolved (attempting to close a filestream that had been disposed).
- Because this version does not use the synchronization byte any longer, it is NOT compatible with previous versions.
The following is a list of changes available in version 3.963 of this library.
- Bugfix: The GetSendQueueSize() function was not reporting correctly - found by Bishop44
The following is a list of changes available in version 3.962 of this library.
- Bugfix: Resolved a thread synchronization issue involving the write queue. I have removed the write queue for now.
The following is a list of changes available in version 3.961 of this library.
- Bugfix: Resolved a thread synchronization issue in the new file transfer code.
The following is a list of changes available in version 3.96 of this library.
- Implemented an overhaul of the file transfer system. These changes were made to ensure robust and performant transfers of files to and from the server. An example of how to send multiple files and folders, view the server's remote file system, and even be afforded some limited management functionality using this library will follow shortly (in another article).
- Bugfix: The crash in the GetPercentOfFile... () has been resolved.
- Bugfix: The hang some people experienced while attempting to transfer multiple files has been resolved.
- The 10k blocksize issue has been resolved. Blocksize will now be set to 62500 unless the throttling value is set lower then that.
- A concurrentQueue has been implemented in the client and server classes to replace the AsyncUnbufWriter class, imploving performance.
- Four new delegates have been added for people to subscribe to: incomingFileProgress, outgoingFileProgress, incomingFileComplete and outgoingFileComplete. Have a look at the code in the client form to see how to use them.
- ReceivingAFile() and SendingAFile() functions have been added to the client class, and the server session class. They both return a boolean value, and return false when your file is complete.
The following is a list of changes available in version 3.95 of this library.
- Added an "Active file transfers" listview to the server form. All file transfers from every connected client can be monitored here, and file transfers that are aborted or complete are removed. Code needed to be borrowed from the client and added to the server session class for this to work properly.
The following is a list of changes available in version 3.94 of this library.
- Fixed a bug in client.Connect() that was preventing VS 2015 from compiling the example project.
The following is a list of changes available in version 3.93 of this library.
- Introduced a new UI update pattern that is more efficient.
The following is a list of changes available in version 3.92 of this library.
- Bugfix: The server was not updating the blocksize in clients after the first connected. Reported by Bishop44.
The following is a list of changes available in version 3.91 of this library.
- Bugfix: Corrected threading problem with the Large Array Transfer Helper class: Crashes / LAT failures were sometimes occuring due to list items being added / removed by multiple threads.
- Patch: Auto-reconnecting after connect failure: Clients configured to auto-reconnect were attempting to auto-reconnect after a connection failure. This was unintended behavior.
- Patch: Clients not correctly displaying connection status after clicking the disconnect button while attempting to auto-reconnect: Also resolved in this version.
The following is a list of changes available in version 3.9 of this library.
- Patch: Added options to client.close() and sesison.close(): The default behavior is now to wait until messages in the send queue have been sent before shutting down.
The following is a list of changes available in version 3.8 of this library.
- Bugfix: Patch for an bug accidently introduced in V3.7: Clients deliberately disconnected from the server now correctly report it. V3.8 apperas to function correctly in all areas, and unles someone reports a bug or I discover something else, this should be the last update for a while.
The following is a list of changes available in version 3.7 of this library.
- Bugfix: I throttled the new connection speed down in the server. This server can now accept 100 new connections per second. Of course if more then 100 attempt to connect in a second, they will be able to. The will just have to wait a little longer. This is to provide time for the .net garbage collector to do it's job.
- New Functionality: I also added some new checkboxes to the example project so you can test automatic reconnects and server machineID enforcement (or not).
The following is a list of changes available in version 3.6 of this library.
- Bugfix: Sessions that were disconnected from a server for connecting with a blank MachineID were getting an empty error message before disconnect. I also did much more work ensuring that sessions that connect with duplicate machine ids, or no id are rejected properly. Clients trying to connect to a server with duplicate machineIDs, or a blank machineID receive a rejection notice before the connect() function returns, and as such rejected connection attempts will cause connect() to return false. An error message with an explanation will also be received in your callback. A Connection success notice will also be received before connect() returns, which means before connect() passes control back to you, your session is already visible in the server's session collection. Dissconnected sessions have their sessionIDs replaced with an empty string, and are marked as not validated (checked to ensure uniqueness). As such they are not visible in the session collection at all.
The following is a list of changes available in version 3.5 of this library.
- Bugfix: A memory leak, sessions bumping off other sessions, duplicate sessions, and other issues with the session management system have been resolved. - Reported by Earthed (Thank you).
- New Functionality: Automatic session reconnecting during disconnect events. While initializing a client, you can specify an auto-reconnect duration. The default is 30 seconds.
- New Functionality: The server can now be set to enforce unique IDs. This is the default behavior, but you can specify not to in the server init if you would like to use the machine IDs for something else.
The following is a list of changes available in version 3.1 of this library.
- Bugfix: Utilities.LargeArrayTransferHelper.VerifySignature() was returning an object when it should have been returning a Boolean.
- Graceful disconnect - previously, the client would close without notifying the server. This would sometimes leave the session running in the server, leading to increased CPU and memory usage over time.
- BugFix: Deadlock while attempting to send bytes from the callback. In version 2 and below, using SendBytes() from the callback would sometimes result in a deadlock condition. This has been resolved by the introduction of send and receive packet queuing in both the client and the server classes.
- New Functionality: Method GetSendQueueSize() added. Use this to monitor how fast the messages you drop into the queue are being sent. If this number rises, then you are queuing messages faster then you can send them. This is a client only method.
- New Functionality: The library has been rolled into a .dll project. This was to make it convenient for programmers (ahem... me) using other .net languages to have access to the functionality in these libraries.
- New Functionality: both the client and it's server session now track a 'machineID'. This is a string, and can be anything you like. The idea is that it can be used as a unique machine identifier, above and beyond the sessionId. Use the SetMachineID() and GetMachineID() client methods for this. Also, you can now retrieve a server session using a machineID ( using TcpComm.server.GetSession(machineid) ).
- BugFix: Reuse of sessionIDs. Previously, the server would add sessions to an Arraylist. Disconnected sessions were left in the list, and new sessions would get an ever increasing ID number as they were added. It was feasible (though unlikely), that very busy servers could eventually crash because they'd reached the upper limit of the sessionId integer. Version 3 of this library avoids this issue by reusing sessionIDs.
- New Functionality: The server's sessionCollection object and SessionCommunication class are now exposed. The helper functions: GetSessionCollection(), GetSession(int) and GetSession(string) have been added to assist in server management. Please look at the example project to see how this is done.
- New Functionality: SendText() added. This is exactly what it looks like - a convenience function used to send simple text messages. Use SendBytes() for jobs other then text messages.
- New Functionality: client.Connect() now resolves hostnames and domain names via DNS. You no longer need to enter an IP address in the client.connect() method - it can be anything resolvable by DNS.
- BugFix: Addresses other then valid IPv4 addresses are sometimes incorrectly returned. Reported by ThaviousXVII.
- BugFix: Files transfered are sometimes not released (closed) by the receiver. Reported by Rich Frasier.
- BugFix: The server can now initiate a file transfer to a client. Reported by Kasram and others.
- New Functionality: A convenience class used to transfer large arrays easily. Use the Utilities.LargeArrayTransferHelper class. See the example project for usage and notes.
Lastly, I have to say that I really appreciate the bug reports. I know we all have busy, hectic lives these days (I know I do), so thanks for taking the time to post that stuff here.
- 12/30/11: Corrected a small problem with protocol reporting in
tcpCommServer. Added notification for when
sendBytes() has completed sending data.
- 6/19/12: Resolved file IO issues. Added the AsyncUnbuffWriter class to the example project. Resolved all warnings and rebuilt the project using Option Strict and Option Explicit.
- 4/14/14 - V3 of the library uploaded. Please see above for details.