C# \ VB .NET 多用户通信库 (TCP)
允许多个用户在同一服务器上相互发送和接收消息。
- 下载 NetComm - 10.8 KB
- 下载 NetComm 源代码 (VB.NET) - 47.06 KB
(示例)
- 下载 HostClientExamples (VB.NET) - 112.63 KB
- 下载 HostClientExamples (C#) - 87.42 KB
在我学校的机器人对战项目中使用 NetComm 库的示例,
这是服务器的屏幕截图,有 4 个客户端已连接到它。
引言
NetComm 库允许您将多个客户端连接到同一服务器。这使您可以执行以下操作:
- 相互传输私人消息
- 向同一服务器上的所有用户发送消息(公开消息)
- 仅使用 ID(一个唯一的字符串)与每个客户端通信,而无需知道彼此的 IP,客户端只需要知道主机 IP 地址。
NetComm 库处理所有复杂的工作,它在与应用程序 UI 分开的线程中运行——当新消息到达时,NetComm 库将引发一个事件。
这有什么用?嗯,这使您可以轻松创建多人聊天 - 或任何其他需要处理多用户通信的应用程序。
使用此库,您可以轻松创建此类复杂的通信。
本文用 C# 编写,但我附带了一个 VB.NET 示例供 VB 用户(我最喜欢的语言)使用。该库是用 VB.NET 构建的。
背景
我为我的 学校项目 开发了这个库 - 我创建了一个服务器,用于跟踪两台由两台远程 PC 远程控制的机器人。该项目模拟了两个团队之间的机器人对战。
我将客户端(4 个客户端)连接到同一服务器。服务器处理所有游戏规则、每个机器人的生命值、子弹、起始弹药、武器。服务器的主要作用是让两个团队遵守相同的规则。服务器是进行游戏计算的地方。它基本上是游戏的“中心”,观看者可以查看服务器 UI,查看记分牌、每个团队的机器人生命值、剩余子弹数等…… NetComm 库使两个团队之间的通信变得容易得多。
概念(或,它是如何工作的?面向更高级的用户)
我用 VB.NET 构建了这个库。我使用了 Microsoft 创建的 TCPClient 和 TCPListener (System.Net.Sockets
) 作为这个库的基础。整个库都依赖于这两个类。
为了让 UI 保持响应,我在另一个线程中完成了所有通信,当新消息到达时,我需要引发一个事件到 UI 线程以通知它有关新消息。我使用了 SynchronizationContext 类 (System.Threading
) 来获取 UI 线程的上下文,并将新消息发布到 UI 线程的消息队列。
为了能够使用 ID(这样客户端就可以使用字符串(如名称)传输消息,而无需知道 IP),我使用了 AsciiEncoding 类将字节转换为 string
以便进行唯一 ID。
在使用“代码”之前阅读(重要信息)
在此库中,所有客户端都连接到同一个服务器,这意味着以下几点:
- 所有客户端应使用与服务器相同的端口。
- 所有客户端必须知道服务器的 IP 地址。
- 所有客户端都可以相互通信(使用 ID),ID 描述了消息指向的客户端是谁。它允许客户端相互通信。例如:ID 可以是一个名字,如“Jack”,如果一个 ID 为“Jack”的客户端想向“ElvisPresley”发送消息,他只需使用他的 ID(“ElvisPresley”)来发送消息。这些消息通过主机传输,主机负责分析它们并将它们发送给接收者。

Jack 想向 ElivsPresley 发送一条消息,他使用了他的 ID (ElivsPresley)
要发送该消息,主机接收到消息,
进行分析并将其发送给 ElvisPresley。
- 主机和客户端都可以发送和接收消息。他们可以相互发送私人消息。主机可以广播消息(将公开消息发送给所有参与的客户端)。
- 主机 ID 始终是 ""(null),要向主机发送私人消息,我们使用 SendData 方法并带一个 "" 字符串。(如果你还不明白,最后你会明白的)。
- NetComm 库可以发送和接收字节数组。如果您打算将此库用于聊天目的,可以使用以下函数将字节转换为字符串,并将字符串转换为字节:
string ConvertBytesToString(byte[] bytes)
{
return ASCIIEncoding.ASCII.GetString(bytes);
}
byte[] ConvertStringToBytes(string str)
{
return ASCIIEncoding.ASCII.GetBytes(str);
}
这两个函数都使用 AsciiEncoding 类,该类使用 ASCII 表将 char
转换为其对应的数值。(每个字节代表 string
中的一个字符)。
附带了 VB.NET 示例 - 也附带了 C# 示例
Using the Code
要开始,请创建您的项目并执行以下操作:
- 打开您的项目。
- 右键单击“引用”并选择“添加引用”。
- 选择“浏览”选项卡,然后选择已解压的“NetComm.dll”。
- 接受所有对话框。
创建主机

要开始我们的第一步,我们需要创建一个监听任何传入客户端的主机。主机本身可以发送和接收消息。
在您的 Form
类的全局声明中,添加以下声明:
NetComm.Host Server; //Creates the host variable object
在 Form_Load
事件处理方法中,添加以下代码:
Server = new NetComm.Host(2020); //Initialize the Server object,
//connection will use the 2020 port number
Server.StartConnection(); //Starts listening for incoming clients
2020 是端口号,您可以使用任何您想要的端口号,但请确保客户端使用相同的端口号。
第二行告诉 Server
对象开始监听任何传入的客户端。
Host 类包含以下事件:
onConnection(string id)
每次有用户连接时,此事件都会引发以通知应用程序。id 参数包含已连接客户端的 ID。lostConnection(string id)
每次有用户断开连接时,此事件都会引发以通知应用程序。id 参数包含已断开连接的客户端的 ID。errEncounter(Execption ex)
每次发生错误时,此事件都会引发以通知应用程序。ex 参数包含异常对象。ConnectionLost()
当服务器连接关闭(停止监听)时,将引发此事件。DataReceived(string ID, byte[] Data)
每次收到消息时,此事件都会引发以通知应用程序。ID 参数包含发送消息的客户端的 ID。Data 参数包含该客户端发送的字节数组。DataTransferred(string Sender, string Recipient, byte[] Data)
每次消息在客户端之间传输时(使用Client.SendData
方法),此事件都会引发以通知应用程序。Sender 包含发送消息的客户端的 ID。Recipient 包含接收消息的客户端的 ID。Data 参数包含在客户端之间传输的数据。
我创建了一个 textbox
,并将其 name
属性更改为“Log
”,此 textbox
将记录服务器上发生的所有事件。
让我们在 Form_Load
事件处理方法中(在之前的行下方)添加以下几行:
//Adding event handling methods, to handle the server messages
Server.onConnection += new NetComm.Host.onConnectionEventHandler(Server_onConnection);
Server.lostConnection += new NetComm.Host.lostConnectionEventHandler
(Server_lostConnection);
Server.DataReceived += new NetComm.Host.DataReceivedEventHandler(Server_DataReceived);
我们需要创建一些方法来处理这些事件,对于 onConnection
事件,我添加了以下方法:
void Server_onConnection(string id)
{
Log.AppendText(id + " connected!" +
Environment.NewLine); //Updates the log textbox when new user joined
}
每次有用户加入房间时,您的应用程序都会进入此方法,并在用户进入服务器时更新日志。
日志会持续更新,以通知我们新用户加入房间。如果您注意到 onConnection
方法具有id 参数,则此参数包含加入服务器的用户的 ID(名称/唯一字符串)。
现在让我们添加 Server_lostConnection
方法:
void Server_lostConnection(string id)
{
Log.AppendText(id + " disconnected" +
Environment.NewLine); //Updates the log textbox when user leaves the room
}
每次用户离开房间时,您的应用程序都会进入此方法,并在用户离开服务器时更新日志。
让我们创建 DataReceived
方法来处理新消息:
void Server_DataReceived(string ID, byte[] Data)
{
Log.AppendText(ID + ": " + ConvertBytesToString(Data) +
Environment.NewLine); //Updates the log when a new message arrived,
//converting the Data bytes to a string
}
这有点棘手。每次收到新消息时,您的应用程序都会进入此方法。如果 Jack(Jack 是 ID)发送了一条消息“Hello!”,Log 文本框将被更新为以下文本:“Jack: Hello!”。
但是,为什么 DataReceived 方法的 Data
变量是字节类型?为什么它不是 string
?嗯,如前所述,NetComm
类可以在客户端之间传输字节,因此我们使用ConvertBytesToString 函数。此函数写在本文章的顶部。
如前所述,客户端也可以发送和接收消息。为了发送消息,我们使用以下方法:
Server.SendData("Jack", Data); //Jack is the ID of the client
//we want to send the data to
如前所述,“Jack
”是要发送数据的客户端的 ID。Data 变量是我们想要传输的字节数组。如果您想传输 string
,您应该使用 AsciiEncoding
类将 bytes
转换为 string
。或者继续阅读,我们稍后将构建一个将 string
转换为字节数组的函数。
要向所有参与的客户端发送消息,我们使用 Brodcast
方法:
Server.Brodcast(Data); //Sends the Data bytes to all participating clients
同样,Data
变量是我们想要发送给每个客户端的字节集。
要踢出特定客户端,我们使用 DisconnectUser
方法:
Server.DisconnectUser("Jack"); //Kicks Jack out of the server
要获取所有连接用户的列表,我们使用 User
属性:
List<string> usersList = Server.Users; //Gets a list of all the connected clients
每次关闭应用程序时,您都应该关闭 Server
对象连接。在 Form_Closing
事件处理方法中,添加以下代码:
Server.CloseConnection(); //Closes all of the opened connections and stops listening
Host
类(或我们创建的 Server
对象)还有其他属性和方法我们没有讨论,但您可以随时探索 Host
类并自行查找。
创建客户端
第二步是创建客户端。如果您还没有阅读本文的“创建主机”部分,您可能需要回头看看 - 我们将在客户端方面使用一些我们之前收集到的信息。
将以下变量添加到您的客户端应用程序全局声明中:
NetComm.Client client; //The client object used for the communication
客户端变量保存所有通信处理、事件和方法。
在 Form_Load
事件处理方法中添加以下行:
client = new NetComm.Client(); //Initialize the client object
Client 类包含以下事件:
Connected()
当客户端成功连接到主机时,将引发此事件以通知应用程序。Disconnected()
当客户端与主机断开连接时,将引发此事件以通知应用程序。errEncounter(Execption ex)
每次发生错误时,此事件都会引发以通知应用程序。ex 参数包含异常对象。DataReceived(byte[] Data, string ID)
每次收到消息时,此事件都会引发以通知应用程序。ID 参数包含发送消息的客户端的 ID。Data 参数包含该客户端发送的字节数组。
我们需要处理客户端对象的事件(Connected
、Disconnected
、DataReceived
等)。这使我们能够响应它们并根据它们更新我们的应用程序,将以下代码放在 Form_Load
事件处理方法中:
//Adding event handling methods for the client
client.Connected += new NetComm.Client.ConnectedEventHandler(client_Connected);
client.Disconnected += new NetComm.Client.DisconnectedEventHandler(client_Disconnected);
client.DataReceived += new NetComm.Client.DataReceivedEventHandler(client_DataReceived);
现在我们应该添加将响应这些事件的方法。
让我们创建 Connected
方法:
void client_Connected()
{
Log.AppendText("Connected successfully!" +
Environment.NewLine); //Updates the log with the current connection state
}
每次客户端成功连接时,您的应用程序都会进入此方法,然后日志 textbox
将更新其文本,显示“Connected successfully!
”。
现在我们将添加 Disconnected
方法:
void client_Disconnected()
{
Log.AppendText("Disconnected from host!" +
Environment.NewLine); //Updates the log with the current connection state
}
每次客户端与主机断开连接时,您的应用程序都会进入此方法,然后日志 textbox
将更新其文本,显示“Disconnected from host!
”。
让我们添加 DataReceived
方法:
void client_DataReceived(byte[] Data, string ID)
{
Log.AppendText(ID + ": " + ConvertBytesToString(Data) +
Environment.NewLine); //Updates the log with the current connection state
}
每次客户端接收到新数据时,您的应用程序都会进入此方法,然后日志 textbox
将更新其文本,显示发送数据的客户端 ID 和数据的 string
。ID 参数包含发送数据的客户端的 ID(唯一的 string
)。Data 参数是一个字节数组,包含该客户端发送的数据。
ConvertBytesToString 函数在本文章的顶部有介绍。
我们需要获取主机的 IP 地址和用户想要的 ID,为此我们创建一个简单的表单来收集信息,但仅出于本文的目的,我们将使用 ID“Jack
”和 IP 地址“localhost
”,这意味着我们正在使用的计算机的 IP 地址(我们将在同一台计算机上运行示例)。
因为这是一个示例,我们将把通信设置代码放在 Form_Load
事件处理方法中,但在您的项目中,您应该创建一个连接表单来收集这些信息。
设置连接(连接主机)
//Connecting to the host
client.Connect("localhost", 2020, "Jack"); //Connecting to the host
//(on the same machine) with port 2020 and ID "Jack"
如果连接成功建立,您的应用程序应该会进入我们之前构建的 Connected
方法。
发送消息是通信的“核心”。这就是为什么我们需要首先进行通信。假设我们想将一个 string
发送给另一个客户端,为此,我们应该在我们的项目中添加一个 textbox
,我将其命名为“ChatMessage
”。然后创建一个按钮,我将其命名为“SendButton
”。当用户在“ChatMessage
”文本框中写完消息后,他将单击我们之前创建的“SendButton
”。因此,我们应该在 SendButton_Click
事件处理方法中编写此代码:
private void SendButton_Click(object sender, EventArgs e)
{
client.SendData(ConvertStringToBytes(ChatMessage.Text), "Jack");
}
每次单击 SendButton
时,我们将向“Jack
”发送一条私人消息,消息内容为 textbox
中显示的文本。请记住,我们需要将 string
转换为字节数组,因此我们应该使用以下函数:
byte[] ConvertStringToBytes(string str)
{
return ASCIIEncoding.ASCII.GetBytes(str);
}
阅读本文顶部的解释,了解有关此函数的更多信息。
要向主机发送私人消息,我们只需使用 SendData
方法:
//Sending private message to the host
client.SendData(ConvertStringToBytes(ChatMessage.Text));
最后,要结束通信,我们在 Form_Closing
事件处理方法中使用以下方法:
if (client.isConnected) client.Disconnect(); //Disconnects if the
//client is connected, closing the communication thread
加快通信速度
为了加快通信速度,您可以使用 NoDelay
、SendBufferSize
和 ReceiveBufferSize
属性。这里有一段简短的代码应该可以加快通信速度:
//Speeding up the connection
Server.SendBufferSize = 400;
Server.ReceiveBufferSize = 50;
Server.NoDelay = True;
将此代码放在您的 Form_Load
事件处理方法中。主机和客户端都应使用这些代码行才能使连接更快。
使客户端广播消息
我直到现在才注意到 Client
类中没有任何“Broadcast
”方法。这是一种使客户端能够广播消息的方法,我还没有测试过。
正如您所读到的,主机是唯一能够广播消息的。为了让客户端广播消息,我们可以将消息发送到主机(私人消息),然后告诉主机广播该消息给所有人。一种方法是在主机 DataReceived
事件处理方法中添加以下代码:
void Server_DataReceived(string ID, byte[] Data)
{
foreach (string clientID in Server.Users)
{
if (ID != clientID) Server.SendData(clientID, Data);
}
}
换句话说(不是代码“语言”),我们模拟了主机 Broadcast
方法,但这次我们忽略了发送消息的客户端,因此他不会收到他发送的消息。
客户端使用 SendData
方法将消息发送给主机:
client.SendData(Data); //This message will be pointed to the host,
//then the host will broadcast it
ID 问题 - “D”字符
当使用“David
”或“Dan
”之类的名字时,NetComm
库出于某种原因工作不正常。目前,唯一的解决方案是使用不以“D
”字符开头的其他 ID。
关注点
这个库是为我的学校项目(机器人对战)而开发的,但我决定与大家分享。
编写它很有趣,也很有挑战性(第一次了解 TCP 协议)。我编写这个库是为了在 5 台计算机之间创建通信,其中一台是服务器,负责管理游戏规则、生命值等……另外 2 台计算机在另一个房间控制 2 台机器人,所有消息都通过 NetComm
库传输。随意将此库用于您想要的任何目的!
历史
- 2010.10.15 - 添加了 ID 问题信息,添加了图片
- 2010.10.16 - 修正了错误信息,添加了演示图片,删除了额外信息,为每个类添加了引用链接