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
Member
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

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionHow to send file from server to client?memberMember 1002770314 May '13 - 13:12 
Hello pdoxtader.
I found the project very interesting.
And I'm using your class in my project generation backup MSSQL Server.
I have a tremendous doubt, I am sending the command "GoBackup" from client to server. the server received the command and executed perfectly generating the backup of the database.
 
I do not know how to send this backup server to the client, this is my difficulty.
 
how can I send the backup server to the client without client interaction
 
PS. I'm using google translator.
AnswerRe: How to send file from server to client?memberpdoxtader15 May '13 - 2:00 
"without client interaction"
 
That's the problem right there. The short answer is that you can't. The server class does not initiate connections, and the client class doesn't listen for them. You CAN do some other things though... Like have the client poll the server every few seconds or minutes for a backup command. If it's time to do the backup, have the server reply with your GoBackup command. If not, have it reply with something else.
 
You could also run a server on your client machine, and a client on your server machine, and have the server machine connect to the client machine with it's client. This may not be possible though, if your server and client are on different networks, and you don't have control of the router / firewall the client lives behind.
 
I think polling the server every few minutes for a GoBackup command is probably your best option.
 
Best of luck,
- Pete
 
P.S. Google Translator did a fairly good job! What country are you from?
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 3:19 
Hello Pete.
I'm from Brazil
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 3:25 
Awesome... It's interesting to know how far my code has traveled.
 
Best of luck with your project,
 
- Pete
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 3:29 
Pete.
 
Backup Command I'll do it once a month.
 
The server will be on another network. the link is the internet.
 
I have access to the router / firewall. I create routes doors.
 
I can do this I will say below.
 
The server receives the command "GoBackup". after making the backup sends it to me (Client) by command "SendFile"?
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 3:36 
Yes, as long as the client is still connected to the server when the backup is completed.
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 3:38 
Pete, the customer will stay online until the backup finishes.
 
Could show how can I make the server send the backup by sendfile. and how the customer will receive this information?

GeneralRe: How to send file from server to client? [modified]memberpdoxtader15 May '13 - 4:17 
Ok, a very simple example:
 
1.) When a goBackup command arrives at the server, pass the session ID.
 
   Public server as TcpCommServer
 
   Public Sub ServerCallback(ByVal bytes() As Byte, ByVal sessionID As Int32, ByVal dataChannel As Integer)
      Dim text As String = BytesToString(bytes)
 
      If text = "GoBackup" then
 
         ' do your backup here
         DoBackup(sessionID)
 
      end If
   End Sub
 
   Private Sub DoBackup(ByVal ID As Int32)
      ' Do your backup. 

      ' When the backup completes, send the backup file to the client:
      server.SendFile(" This is the path of your backup file ", ID)
   End Sub
 
   'Then, in the client's call back 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
           Dim msg As String = BytesToString(bytes)
           ' The file being sent to the client is complete.
           If msg = "->Done" Then
              MsgBox("The backup file " & _Client.GetIncomingFileName & " has been transferred to you from the server.")
           End If
        End If
   End Sub


modified 15 May '13 - 10:36.

GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 3:39 
And also, If you have control of the router the client lives behind, then you can do the second thing I mentioned. You can run a client on the server machine, and create a port forwarding in the client side router, forwarding a port to the machine the client is running on. Give your client machine a static IP, and run a server on it. Then you can connect from server to client on the port your forwarded, and have the server send anything you like to the client.

- Pete
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 3:52 
This possibility I've thought about doing. More will not only am I going to use the application. will be 4 people.
 
why we needed the server to send the file to me.
 
I'll show you what I'm doing.
 
Client:
Private Sub bntBackup_Click(sender As System.Object, e As System.EventArgs) Handles bntBackup.Click
 
    _Client.SendBytes(StrToByteArray("GoBackup"))
 
End Sub
'-----------------------------------------------------------------
 
Server:
Public Sub UpdateUI(ByVal bytes() As Byte, ByVal sessionID As Int32, ByVal dataChannel As Integer)
 
    If Me.InvokeRequired() Then
        ' InvokeRequired: We're running on the background thread. Invoke the delegate.
        Me.Invoke(_Server.ServerCallbackObject, bytes, sessionID, dataChannel)
    Else
        ' We're on the main UI thread now.
        If dataChannel = 1 Then
            Me.lbTextInput.Items.Add("Session " & sessionID.ToString & ": " & BytesToString(bytes))
 

            If BytesToString(bytes) = "GoBackup" Then
 
                AbreBanco()
                sSql = "BACKUP DATABASE " & sBancoDados & " TO DISK = '" & sCaminho & sBancoDados & ".BAK" & "' WITH INIT"
                drGerarBackUp = CType(criaDataReader(conexao, sSql), SqlDataReader)
                drGerarBackUp.Close()
                arquivoOriginal = File.ReadAllBytes(sCaminho & sBancoDados & ".BAK")
                arquivoDestino = File.Create(sCaminho & sBancoDados & ".ZIP")
 
                Dim zip As New GZipStream(arquivoDestino, CompressionMode.Compress, False)
 
                zip.Write(arquivoOriginal, 0, arquivoOriginal.Length)
                zip.Close()
 
                _Server.SendBytes(StrToByteArray("BackupOK"), , sessionID)
 
                _Server.SendFile(sCaminho & sBancoDados & ".ZIP", sessionID)
 
            End If
        ElseIf dataChannel = 255 Then
            Dim tmp = ""
            Dim msg As String = BytesToString(bytes)
            Dim dontReport As Boolean = False
 
            ' _Server as finished sending the bytes you put into sendBytes()
            If msg.Length > 3 Then tmp = msg.Substring(0, 3)
            If tmp = "UBS" Then ' User Bytes Sent.
                Dim parts() As String = Split(msg, "UBS:")
                msg = "Data sent to session: " & parts(1)
            End If
 
            If Not dontReport Then Me.ToolStripStatusLabel1.Text = msg
        End If
    End If
 
End Sub

GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 4:32 
Yes, this looks about right. I see you're sending the file back to the client here. The only thing you're missing is:
 
 
   Dim msg As String = BytesToString(bytes)
   ' The file being sent to the client is complete.
      If msg = "->Done" Then
         MsgBox("The backup file " & _Client.GetIncomingFileName & " has been transferred to you from the server.")
   End If
 
 
In your client's callback to notify the user that the backup file has been transferred to him.
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 5:21 
I did so and the customer has not received the file.
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 5:27 
Are you setting the folder you will be receiving the file into in your client application? in the example program, I'm setting to be a folder on your desktop, like this:
 
_Client.SetReceivedFilesFolder(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\ClientReceivedFiles")

 
If this folder is not set correctly, your client will not be able to put the file anywhere, so you will not receive anything.
 
I think you should test the client application out from your own computer before you give it to your customer to make sure it's working the way you expect it to.
 
Let me know how it goes.
 
- Pete
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 5:44 
Pete, I'm by setting this information in the form load
 
    Private Sub frmClient_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        cboEnderecoIP.Text = _Client.GetLocalIpAddress.ToString
        _Client.SetReceivedFilesFolder("C:\Pneuaço\BackupMensal")
        tmrPoll.Start()
    End Sub

GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 5:30 
Also, you must make sure your remote user has the rights to create a folder and write a file to the location you've selected in windows. This issue may also be a windows NTFS or user rights issue.
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 5:48 
I'm testing locally. Do not put the program into production.
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 5:51 
Good. I would run the client app in the IDE and set some stops so you can watch what's happening when the file arrives.
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 5:59 
Want to connect to my computer through TeamViewer?
 
and look at my source code
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 6:01 
Sure, but I'll have to do it later - I'm at work right now. I'll message you here when I'm home. Ok?
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 6:10 
Do you use any kind of program to chat?
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 6:12 
Not really. I can set something up though. What do you use?
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 6:15 
I use Skype and facebook
 
My Skype contact is: SelkSantos
My contact in facebook is: https://www.facebook.com/selk.santos
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 6:18 
Ok - I'll message you on facebook in a minute
GeneralRe: How to send file from server to client?memberMember 1004949615 May '13 - 6:19 
Okay.
 
thank you
GeneralRe: How to send file from server to client?memberpdoxtader15 May '13 - 6:26 
Just messaged you. Not sure if I have the right person on facebook though...

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

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