Click here to Skip to main content
Click here to Skip to main content

Reusable multithreaded TCP client and server classes with example project in VB.NET

By , 30 Jul 2012
 

TcpCommExampleProject/ExamplePic.jpg

Introduction

This example project contains four classes - TcpCommServer, TcpCommClient, clsAsyncUnbuffWriter and CpuMonitor. With these classes, you will not only be able to instantly add TCP/IP functionality to your VB.NET applications, but 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.

Background

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.

Channels

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 Start() or Connect(). I.e.:

Dim _Server As New tcpCommServer(AddressOf YourCallbackSub)
_Server.Start(60000) 

Or:

Dim _Client As New tcpCommClient(AddressOf YourCallbackSub)
_Client.Connect("192.168.1.1", 60000) 

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 GetFile and 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 GetPercentOfFileReceived and GetPercentOfFileSent methods.

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)
    Else
        ' We're on the main UI thread now.
        Dim dontReport As Boolean = False

        If dataChannel < 251 Then
            Me.ListBox1.Items.Add(BytesToString(bytes))
        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
            End If

            ' 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
            End If

            ' 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
            End If

            ' 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
            End If

            ' The file transfer to the client has been aborted.
            If msg = "->Aborted." Then
                gbGetFilePregress.Text = "File->Client: Transfer aborted."
                dontReport = True
            End If

            ' The file transfer to the server has been aborted.
            If msg = "<-Aborted." Then
                gbSendFileProgress.Text = "File->Server: Transfer aborted."
                dontReport = True
            End If

            ' _Client as finished sending the bytes you put into sendBytes()
            If msg = "UBS" Then ' User Bytes Sent.
                btSendText.Enabled = True
                dontReport = True
            End If

            ' 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
            End If

            ' Display all other messages in the status strip.
            If Not dontReport Then Me.ToolStripStatusLabel1.Text = BytesToString(bytes)
        End If
    End If

End Sub

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 .GetBlocksize() method.

Throttling

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
        Thread.Sleep(1)
    End While
End If
If bandwidthTimer.AddMilliseconds(250) <= Now Then
    bandwidthTimer = Now
    session.bytesRecievedThisSecond = 0
    session.bytesSentThisSecond = 0
    bandwidthUsedThisSecond = 0
End If

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 ThrottleNetworkBps() method.

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. 

The AsyncUnbuffWriter class 

One of the issues I struggled with while testing was the sudden, unexplained loss of bandwidth. For no reason I could see, bandwidth while copying a file (with throttling off) would go from 300 MB / sec to very little after about 2 gig. A little more testing and I discovered that the slowdown was in my hard drive. A little more and I learned that it was related to the O/S Ram cache.  

When you think about it, allowing Windows to cache file transfers is a very bad idea, specially for a server. It may provide the illusion of very fast file IO, but it is just an illusion. Too many simultaneous uploads, and your server runs out of available ram cache. Performance degrades... if this continues, the OS will eventually crash. 

The solution is of course: unbuffered file IO. We want to leave Windows out of our file IO as much as possible. We also don't want to write to the hard drive every other time bytes arrive in the communications thread - we needed to do this asynchronously. 

Microsoft doesn't provide a tool in managed code to do asynchronous unbuffered file IO. Filestream will do one or the other, but not both.

But what exactly is asynchronous file IO? Well, according to the wiki, it is: "...a form of input/output processing that permits other processing to continue before the transmission has finished."

That didn't sound so hard. Lots of people have been trying to get filestream's BeginRead / BeginWrite  to do unbuffered IO, with almost no success. I abandoned that approach, and went another direction.

The AsyncUnbuffWriter class creates a second low priority thread thread and puts a filestream on it. When we fill it's external buffer, the filestream thread copies the buffer off for itself, signals that the external buffer is free again, and writes the bytes without letting windows buffer the IO. While the writer is writing the bytes, we're free to fill that external buffer again. The speed you get from this class depends entirely on the size of the buffer you choose. I was able to get sustained 100 MB / Sec file transfers using only one Sata 2 hard drive - but I had to use a very large buffer. Using two drives, the performance should be even better. 

Of course, we don't want to use large buffers on the server - so our upload performance will always be limited. I've set the default buffer size in the client to 1 meg x your machine's page size (usually 4096). On my machine, that's 4 meg for the external buffer and 4 meg for the internal buffer - 8 total. This translates to 50 - 60 MB / sec file transfers to the client only. Uploads top out at about 35 MB / sec, as the write buffer is only 256k. 

Again - we're talking about server software. We're never going to allow people 35MB file transfers anyway. 

Points of interest 

One thing worth noting here was an issue I noticed while testing these classes. As you would expect, I tested sending larger and larger files, and more and more connected clients all sending or receiving large files. At one point, I had one client pulling a >3 gig file while two other clients were also doing largish file transfers, and my bandwidth seemed to drop to almost nothing after 2 gig had been sent (almost nothing being between 1 and 30 MBps - I was testing without throttling to see this effect). After much testing, I realized that this is a hardware limitation on my machine. I think it has to do with the hard drive cache. I was able to mitigate this effect by inflating the filestream's buffers in the FileWriter object in the client for large file transfers to the client... but if you notice this effect on your system, this is what it is. Yet another reason to throttle bandwidth. - Resolved. 

History 

  • 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. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

pdoxtader

United States United States
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionClient and Server seperately [modified]memberMember 93952709hrs 31mins ago 
QuestionThanks!memberartusod11-Jun-13 1:30 
AnswerRe: Thanks!memberpdoxtader11-Jun-13 2:29 
QuestionNeed your Helpmemberhasanhabibsurzo31-May-13 3:55 
AnswerRe: Need your Helpmemberpdoxtader11-Jun-13 2:38 
QuestionHow to send file from server to client?memberMember 1002770314-May-13 13:12 
AnswerRe: How to send file from server to client?memberpdoxtader15-May-13 2:00 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 3:19 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 3:25 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 3:29 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 3:36 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 3:38 
GeneralRe: How to send file from server to client? [modified]memberpdoxtader15-May-13 4:17 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 3:39 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 3:52 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 4:32 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 5:21 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 5:27 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 5:44 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 5:30 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 5:48 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 5:51 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 5:59 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 6:01 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 6:10 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 6:12 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 6:15 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 6:18 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 6:19 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 6:26 
GeneralRe: How to send file from server to client?memberMember 1004949615-May-13 6:30 
GeneralRe: How to send file from server to client?memberpdoxtader15-May-13 6:57 
GeneralRe: How to send file from server to client?memberMember 1002770319-May-13 14:06 
GeneralRe: How to send file from server to client? [modified]memberpdoxtader20-May-13 2:05 
GeneralRe: How to send file from server to client?memberMember 1002770323-May-13 14:40 
GeneralRe: How to send file from server to client?memberMember 1004949624-May-13 2:43 
QuestionNeed a Bit of Helpmembertestuser testuser18-Apr-13 20:22 
AnswerRe: Need a Bit of Helpmemberpdoxtader22-Apr-13 2:29 
QuestionGreat Jobmemberlisa_lacour3-Apr-13 8:28 
AnswerRe: Great Jobmemberpdoxtader15-Apr-13 4:14 
Questionrelpy server --to--> clientsmemberakmalkady2-Apr-13 4:58 
AnswerRe: relpy server --to--> clientsmemberpdoxtader3-Apr-13 4:20 
GeneralMy vote of 5memberjgomezprada9-Mar-13 5:46 
GeneralRe: My vote of 5memberpdoxtader9-Mar-13 5:54 
QuestionLooking forward to thismemberjbmckim4-Mar-13 7:21 
AnswerRe: Looking forward to thismemberpdoxtader4-Mar-13 8:29 
GeneralRe: Looking forward to thismemberjbmckim8-Mar-13 12:03 
GeneralRe: Looking forward to thismemberpdoxtader9-Mar-13 5:53 
GeneralRe: Looking forward to thismemberjbmckim9-Mar-13 8:36 
GeneralRe: Looking forward to thismemberpdoxtader23-Mar-13 3:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130619.1 | Last Updated 30 Jul 2012
Article Copyright 2011 by pdoxtader
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid