VB.NET 可重用多线程 TCP/IP 客户端和服务器类及示例项目
一个多线程服务器类,它接受来自提供的客户端类的多个连接。每个客户端可以同时通过 250 个可用通道发送和接收文件和文本(字节数据)。
引言
请参阅下方 4.0 版的修复/更改列表。我保留了第 2 版,以便仍需要仅限 .NET 2.0 的解决方案的用户可以使用。此最新版本需要 .NET 4 或更高版本。4.0 版与之前的版本不兼容!
此示例项目包含两个类 - TcpCommServer
和 TcpCommClient
。通过这些类,您不仅可以立即将 TCP/IP 功能添加到您的 VB.NET 应用程序中,而且它还具有我们都在寻找的大部分功能。有了这些类,您将能够将多个客户端连接到同一端口上的服务器。您将能够轻松地:限制客户端的带宽,并在单个连接上同时通过 250 个提供的通道发送和接收文件和数据(文本?)。
正在寻找快速传输文件夹的方法?
我为此库构建了一个示例应用程序,用于传输文件夹。该示例应用程序实际上提供了类似 FTP 的快速功能,并带有一个类似浏览器的窗口。传输通过拖放开始
https://codeproject.org.cn/Articles/1185064/Remote-File-System-viewing-and-file-transfer-using
背景
当我第一次开始寻找 VB.NET 中 TCP/IP 通信的信息或示例代码时,我必须承认我想要一切。我愿意接受入门所需的零散信息,但我希望,和您一样,我能找到所有需要的东西……一些易于移植的类,可以让我完成大多数人一开始想做的事情——通过单个连接同时发送和接收文件以及自己的数据或文本。当需要构建客户端/服务器应用程序时,最终我们都会因为某个项目而需要它。我们还需要在服务器上控制带宽,否则有人会下载大文件时耗尽所有带宽……而且,最好能从多个客户端连接到同一端口上的服务器,对吧?
我找了很久,但从未找到现成的能满足我需求的东西,所以我决定自己动手。在此过程中,我开始理解为什么人们不免费分享这类成果。我花费了大量时间进行编码、故障排除和测试——但最终,它只是一种工具。最终有价值的是我们如何使用它。
所以,现在是时候了。
通道
我们想做一切……对吧?而且所有的一切都通过一个连接进行。您希望能够发送例如一系列屏幕截图、视频或文本,而无需自己编写区分它们的方法。通道是我的答案。每次使用 SendBytes
方法时,您都会将该数据连同通道标识符一起发送。当它从另一端通过回调返回时,它会附带您发送的通道标识符,这样您就可以区分您的屏幕截图字节和文本——如果您发送的是这些的话。如上所述,您有 250 个通道可供使用(1 到 251)。
好了,进入代码部分。
Using the Code
首先,这两个类都使用委托进行回调。您所要做的就是在实例化这些类时传递 AddressOf
YourCallbackSub,然后调用 Start()
或 Connect()
,即:
Dim _Server As New tcpCommServer(AddressOf YourCallbackSub)
_Server.Start(60000)
或者
Dim _Client As New tcpCommClient(AddressOf YourCallbackSub)
_Client.Connect("192.168.1.1", 60000)
YourCallbackSub
必须具有正确的签名——您可以在示例项目中看到正确的方法。您也可以在实例化服务器类时指定最大 bps。默认值为 9MBps。
要发送字节数组,请使用 sendBytes()
。要从服务器获取文件,有一个 GetFile()
方法。只需提供服务器上文件的路径(服务器本地路径)即可。要将文件发送到服务器,有一个 SendFile()
方法。GetFile
和 SendFile
都由类处理,您无需其他任何代码——但是,如果您想跟踪传入或传出文件的进度,可以轮询 GetPercentOfFileReceived
和 GetPercentOfFileSent
方法。
这些类通过一种有限的协议语言进行通信,与彼此以及与您进行通信。在回调中,您将收到通知,告知您的字节已发送(使用 sendBytes
)、文件已完成下载或上传、文件传输已中止,以及您是本地收到错误还是从服务器收到错误。您将通过通道 255 从您的客户端或服务器接收这些消息。这里快速看一下这些消息,以及我如何在示例项目的客户端窗体的回调子程序中处理它们。
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
可以接受您传递的任何大小的字节数组。但它一次只能发送 blockSize
字节,因此如果您传递一个非常大的字节数组,sendBytes
将阻塞直到发送完成。您需要在将更多数据传递给 sendBytes
之前收到“UBS”通知。
如果一次只尝试发送最多 blockSize
字节,这些类将工作得最高效。BlockSize
将根据您当前的节流速度而有所不同,服务器将在连接后立即更改客户端的 blockSize
。您可以通过调用 .GetBlocksize()
方法来获取当前的 blockSize
。
节流
如果将带宽设置为小于 1 兆,节流将沿着 4K 边界工作,因此您可以使用传统上预期的带宽限制——即 64K、96K、128K、256K 等。超过 1 兆后,它将沿着 5K 边界工作——因此您可以设置更直观的值:2.5 兆、5 兆、9 兆等。服务器计算所有传入和传出的字节,并每秒检查四次,以确保我们处理的字节不超过应有的数量。对于有兴趣的人来说,代码看起来是这样的。
' 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
节流对您很重要,因为这些类会尽可能快地处理您允许它们处理的所有工作。在我的开发机器上,我能够实现近 300 Mbps 的传输速度,复制一个大文件从服务器到客户端。但代价是客户端和服务器的后台线程使用了各自 CPU 核心资源的 100%——这对服务器来说不是一个好的场景。在我的开发机器上,9MBps 几乎没有产生可感知的 CPU 使用率。在生产环境中,我确定您会希望将其设置得更低。理想情况下,您会在实例化服务器时进行节流设置。
Dim _Server As New tcpCommServer(AddressOf YourCallbackSub, 9000000)
您还可以使用 ThrottleNetworkBps()
方法设置节流 bps。
CpuMonitor 类
此项目包含 CpuMonitor
类。此类用于监视 *线程* 的 CPU 使用率。运行示例项目时,您会在状态栏中看到一些 CPU 使用率报告。这是客户端后台线程的使用率——实际执行与服务器通信工作的线程。
关注点
以下是此库 4.08 版的更改列表。
- 配置了示例项目,以便可以将大数组发送到通道 100 上的服务器,也可以从该通道上的服务器接收。要将示例大数组发送到通道 100 上的服务器,请在文本框为空时在客户端单击“发送”。
以下是此库 4.07 版的更改列表。
- 重新设计了 CPU 空闲节流/断开连接检测系统,以便更可靠地检测断开连接。现在,在空闲时间,客户端和服务器每两秒发送一次单字节的保持活动数据包。
以下是此库 4.06.1 版的更改列表。
- Bugfix: 客户端现在将
TcpClient.Client.Connected
作为其正常连接验证测试的一部分进行轮询。这是为了确保客户端能够感知到在发送数据时连接丢失。
以下是此库 4.05 版的更改列表。
- 如果服务器的
keepalive
数据包在超过 7 秒(服务器每 5 秒发送一次)的时间内未收到,客户端现在将断开连接。
以下是此库 4.04 版的更改列表。
- 增加了通过服务器窗体上的组合框指定服务器要侦听的
ipaddress
的能力,或者选择0.0.0.0
作为IPAddress.Any
。
以下是此库 4.03 版的更改列表。
- 向
GetFile()
和SendFile()
添加了锁定和同步代码。两者现在都是线程安全的,并且在发送或接收许多小文件时性能极高。
以下是此库 4.02 版的更改列表。
- Bugfix: 修正了发送文件时 Mbps 未正确报告的问题。
以下是此库 4.01 版的更改列表。
- Bugfix: 客户端使用了过多的 CPU 资源(tcp 库在不活动时未空闲)。由 skdia15 发现。
以下是此库 4.0 版的更改列表。
- 此版本不使用同步字节。此更改是为了提高文件传输期间的性能。
- 节流系统现在可以禁用,只需在服务器的构造函数中输入
0
而不是最大 Mbps 值即可。 - Bugfix: 发现并解决了导致客户端偶尔断开连接的 bug(尝试关闭已处置的
filestream
)。 - 由于此版本不再使用同步字节,因此与先前版本不兼容!
以下是此库 3.963 版的更改列表。
- Bugfix:
GetSendQueueSize()
函数未正确报告——由 Bishop44 发现
以下是此库 3.962 版的更改列表。
- Bugfix: 解决了涉及写队列的线程同步问题。我现在暂时删除了写队列。
以下是此库 3.961 版的更改列表。
- Bugfix: 解决了新文件传输代码中的线程同步问题。
以下是此库 3.96 版的更改列表。
- 实施了文件传输系统的全面检修。进行这些更改是为了确保向服务器和从服务器进行健壮且高效的文件传输。稍后(在另一篇文章中)将提供一个示例,说明如何使用此库发送多个文件和文件夹、查看服务器的远程文件系统,甚至获得一些有限的管理功能。
- Bugfix: 已解决
GetPercentOfFile... ()
中的崩溃问题。 - Bugfix: 已解决某些人在尝试传输多个文件时遇到的挂起问题。
- 已解决 10k 块大小问题。
Blocksize
现在将设置为62500
,除非节流值低于该值。 - 在客户端和服务器类中实现了一个
concurrentQueue
以替换AsyncUnbufWriter
类,提高了性能。 - 已添加四个新的委托供人们订阅:
incomingFileProgress
、outgoingFileProgress
、incomingFileComplete
和outgoingFileComplete
。请查看客户端窗体中的代码以了解如何使用它们。 - 在客户端类和服务器会话类中添加了
ReceivingAFile()
和SendingAFile()
函数。它们都返回一个布尔值,并在文件完成时返回false
。
以下是此库 3.95 版的更改列表。
- 在服务器窗体中添加了一个“活动文件传输”
listview
。在此处可以监视来自每个已连接客户端的所有文件传输,并且会删除已中止或完成的文件传输。为了使其正常工作,需要将代码从客户端借用到服务器会话类中。
以下是此库 3.94 版的更改列表。
- 修复了
client.Connect()
中的一个 bug,该 bug 阻止 VS 2015 编译示例项目。
以下是此库 3.93 版的更改列表。
- 引入了一种新的、更高效的 UI 更新模式。
以下是此库 3.92 版的更改列表。
- Bugfix: 服务器在第一次连接后未更新客户端中的
blocksize
。由 Bishop44 报告。
以下是此库 3.91 版的更改列表。
- Bugfix: 修正了大型数组传输助手类的线程问题:由于多个线程添加/删除列表项,有时会发生崩溃/LAT 故障。
- Patch: 连接失败后自动重连:配置为自动重连的客户端在连接失败后会尝试自动重连。这是意外行为。
- Patch: 点击断开连接按钮尝试自动重连后,客户端未正确显示连接状态:*在此版本中也已解决。
以下是此库 3.9 版的更改列表。
- Patch: 向
client.close()
和sesison.close()
添加了选项:默认行为现在是在关闭前等待send
队列中的消息发送完毕。
以下是此库 3.8 版的更改列表。
- Bugfix: 修正了 V3.7 中意外引入的一个 bug:已故意从服务器断开连接的客户端现在会正确报告。V3.8 在所有方面似乎都能正常工作,除非有人报告 bug 或我发现其他问题,否则这应该是近期的最后一次更新。
以下是此库 3.7 版的更改列表。
- Bugfix: 我降低了服务器的新连接速度。现在此服务器每秒可以接受 100 个新连接。当然,如果一秒内尝试连接的数量超过 100 个,它们也会被接受。只是需要等待更长的时间。这是为了给 .NET 垃圾回收器留出工作时间。
- New Functionality: 我还在示例项目中添加了一些新的复选框,以便您可以测试自动重连和服务器
machineID
强制执行(或不执行)。
以下是此库 3.6 版的更改列表。
- Bugfix: 连接时
MachineID
为空的会话被断开后,会在断开连接前收到一个空错误消息。我还做了更多工作,确保具有重复 machine ids 或没有 id 的会话被正确拒绝。尝试连接到具有重复machineID
或空machineID
的服务器的客户端将在connect()
函数返回之前收到拒绝通知,因此,被拒绝的连接尝试将导致connect()
返回false
。回调中还会收到一个带有解释的错误消息。连接成功通知也会在connect()
返回之前收到,这意味着在connect()
将控制权交还给您之前,您的会话已显示在服务器的会话集合中。已断开连接的会话的sessionID
会被替换为空string
,并标记为未验证(检查以确保唯一性)。因此,它们在会话集合中根本不可见。
以下是此库 3.5 版的更改列表。
- Bugfix: 已解决内存泄漏、会话挤占其他会话、重复会话以及会话管理系统中的其他问题。——Earthed 报告(谢谢)。
- New Functionality: 断开事件期间自动会话重连。在初始化客户端时,可以指定自动重连时长。默认为 30 秒。
- New Functionality: 服务器现在可以设置为强制执行唯一 ID。这是默认行为,但如果您想将 machine id 用于其他用途,可以在服务器初始化时指定不强制。
以下是此库 3.1 版的更改列表。
- Bugfix:
Utilities.LargeArrayTransferHelper.VerifySignature()
返回了一个对象,而它应该返回一个布尔值。 - Graceful disconnect - 以前,客户端会在不通知服务器的情况下关闭。这有时会使服务器中的会话保持运行,导致 CPU 和内存使用量随时间增加。
- BugFix: 尝试从回调中发送字节时发生死锁。在版本 2 及更早版本中,从回调中使用
SendBytes()
有时会导致死锁。通过在客户端和服务器类中引入发送和接收数据包排队,已解决了此问题。 - New Functionality: 添加了
GetSendQueueSize()
方法。使用此方法可以监视放入队列的消息发送速度。如果此数字增加,则表示您排队的消息比您发送的速度快。这是仅客户端方法。 - New Functionality: 该库已集成到一个* .dll* 项目中。这是为了方便其他 .NET 语言的程序员(嗯……我)访问这些库中的功能。
- New Functionality: 客户端及其服务器会话现在都会跟踪一个‘
machineID
’。这是一个string
,可以是您喜欢的任何内容。其想法是,它可以作为唯一的机器标识符,超越sessionId
。使用SetMachineID()
和GetMachineID()
客户端方法进行此操作。此外,您现在可以使用machineID
检索服务器会话(使用TcpComm.server.GetSession(machineid)
)。 - BugFix: 会话 ID 重用。以前,服务器会将会话添加到
Arraylist
。已断开连接的会话保留在列表中,新会话在添加时会获得不断增加的 ID 号。非常繁忙的服务器最终可能会崩溃,因为它们达到了sessionId
整数的上限(尽管不太可能)。此库的 3.0 版本通过重用sessionID
s 来避免此问题。 - New Functionality: 服务器的
sessionCollection
对象和SessionCommunication
类现在是公开的。添加了帮助函数:GetSessionCollection()
、GetSession(int)
和GetSession(string)
以协助服务器管理。请查看示例项目以了解如何操作。 - New Functionality: 添加了
SendText()
。这正如其名——一个方便函数,用于发送简单的文本消息。对于文本消息以外的任务,请使用SendBytes()
。 - New Functionality:
client.Connect()
现在通过 DNS 解析主机名和域名。您不再需要在client.connect()
方法中输入 IP 地址——它可以是 DNS 可解析的任何内容。 - BugFix: 有时会错误地返回无效 IPv4 地址以外的其他地址。由 ThaviousXVII 报告。
- BugFix: 传输的文件有时不会被接收方释放(关闭)。由 Rich Frasier 报告。
- BugFix: 服务器现在可以发起向客户端的文件传输。由 Kasram 等人报告。
- New Functionality: 一个方便的类,用于轻松传输大型数组。使用
Utilities.LargeArrayTransferHelper
类。请参阅示例项目了解用法和注意事项。
最后,我必须说我真的很感谢那些 bug 报告。我知道我们现在的生活都很忙碌、紧张(我当然也是),所以感谢您花时间在这里发布这些内容。
历史
- 12/30/11:修正了
tcpCommServer
中协议报告的一个小问题。增加了sendBytes()
完成发送数据时的通知。 - 12/19/12:解决了文件 IO 问题。将
AsyncUnbuffWriter
类添加到示例项目中。解决了所有警告,并使用 Option Strict 和 Option Explicit 重新构建了项目。 - 14/04/14 - 上传了库的 V3。详情请参阅上方。