在.NET环境中创建一个TCP服务器并不总是像看起来那样简单。许多.NET TCP服务器教程都存在一些限制,例如只支持单个连接客户端,或者为每个连接创建新线程,导致线程限制成为连接限制。本文将介绍一个没有这些限制的TCP服务器组件。
在之前参与的一个项目中,客户要求实现四个TCP服务器接口。在搜索C#中如何创建TCP服务器时,发现.NET框架中缺乏一个易于使用的TCP服务器组件,类似于Delphi/C++ Builder环境中的组件。现有的C# TCP服务器教程并没有考虑到在多个TCP服务器项目之间共享代码的想法,而且它们都有各种限制。因此,决定编写一个没有这些限制的组件。
源代码包含两个项目:TcpServer和TestApp(TestApp作为示例,可以忽略)。将TcpServer项目添加到解决方案中,编译后,应该能在工具箱中找到TcpServer组件,准备放置到一个表单、服务、组件或其他容器上。或者,也可以手动将组件添加到工具箱中使用,或者手动添加项目引用,然后直接调用构造函数。
要启动TCP服务器,需要调用如下代码序列:
C# TcpServer tcpServer1 = new TcpServer();
// 在构造函数中自动添加(如果作为组件添加)
private void openTcpPort(int port)
{
tcpServer1.Port = port;
tcpServer1.Open();
}
private void closeTcpPort()
{
tcpServer1.Close();
}
调用Open()将打开指定的TCP端口进行监听,并启动两个内部线程来处理TCP服务器的主要功能。相反,Close()会停止这两个内部线程并关闭端口,释放所有系统资源。
Port属性是打开或即将打开的TCP端口,注意在端口打开时更改Port可能会导致一些不希望的后果。IsOpen属性会在更改之前告诉端口是否打开。
有三个回调事件:OnConnect、OnDataAvailable和OnError。OnConnect在新客户端连接时调用,OnDataAvailable在之前连接的客户端有数据准备读取时调用。OnConnect和OnDataAvailable都会传递一个TcpServerConnection类(基本上是TcpClient类的简化包装),代表连接/有数据可用的客户端。
这两个事件都是在专门为它们创建的线程中调用的,当这个线程仍然活跃时,不会再次为该客户端调用这些事件(即当有多个回调时,它们将为不同的客户端调用)。如果在这些回调之一返回后缓冲区中仍有数据,OnDataAvailable事件将几乎立即再次被调用。
以下是两个示例回调:
C# public delegate void invokeDelegate();
private void tcpServer1_OnDataAvailable(tcpServer.TcpServerConnection connection)
{
byte[] data = readStream(connection.Socket);
if (data != null)
{
string dataStr = Encoding.ASCII.GetString(data);
invokeDelegate del = () =>
{
handleInput(dataStr);
};
Invoke(del);
data = null;
}
}
protected byte[] readStream(TcpClient client)
{
NetworkStream stream = client.GetStream();
while (stream.DataAvailable)
{
// 调用stream.Read(),读取直到数据包/流/其他终止符结束
// 返回读取的数据作为字节数组
}
return null;
}
从这个示例中可以看出,TcpServerConnection有一个名为Socket的属性,这是一个TcpClient类,所以可以使用它,就像编写TCP客户端程序一样(除了通常混乱的重新连接逻辑)。
如上所示,TcpServerConnection有一个sendData()函数,这个函数会接受一个字符串,使用给定的System.Text.Encoding编码(默认为ASCII),然后排队等待TcpServer的一个内部线程发送到流中。当然,也可以直接使用TcpClient的函数发送数据,但如果这样做,建议不要使用这个库中的任何发送函数,否则可能会导致数据包发送顺序出错。
TcpServer的OnError回调直接从一个操作TCP服务器的两个线程之一调用。除非在极端情况下(例如内存耗尽),否则不太可能看到这个事件被调用。这个事件的调用是由未处理的错误触发的,导致这个事件的异常作为参数传递给事件。