.NET环境下的TCP服务器组件

在.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服务器的两个线程之一调用。除非在极端情况下(例如内存耗尽),否则不太可能看到这个事件被调用。这个事件的调用是由未处理的错误触发的,导致这个事件的异常作为参数传递给事件。

  • MaxSendAttempts:当调用Send()函数或TcpServerConnection的sendData()函数时,TcpServer的一个内部线程将处理实际的发送,如果在尝试发送时发生IOException,它将重试MaxSendAttempts次,然后丢弃消息。
  • Connections:(只读)这是一个TcpServerConnection列表,代表所有连接的客户端。调用这个属性将给一个内部列表的副本。
  • IdleTime:当两个内部线程中的任何一个检测到没有工作要做时,它们将等待最多这么多毫秒,然后再次检查。这个值应该总是很低,没有理由将其增加到默认值以上,如果有高流量,那么应该将其设置得非常低。
  • MaxCallbackThreads:这代表任何时候允许的最大回调线程数。这应该是什么取决于硬件和Windows版本可以处理多少线程,以及服务器上运行的其他事情有多少。默认值100是保守的,如果有高流量,应该增加它。
  • VerifyConnectionInterval:这是TcpServer应该验证客户端是否仍然连接的频率(以毫秒为单位)。验证过程可能需要一些时间,所以如果有高流量,这个值应该设置得很高,但是当客户端掉线并尝试重新连接之前,TcpServer已经验证了之前的连接(通常导致新连接掉线)时,问题就会出现。也就是说,这个值是指示客户端在成功重新连接之前需要等待多长时间的指标。所以应该将其设置为可以承受的最小值。
  • Encoding:每个TcpServerConnection都有一个同名属性,这是通过sendData()或Send()传递的数据使用的System.Text.Encoding。TcpServer上的属性代表所有新连接的默认值,通过直接在TcpServer上更改这个属性,它将改变所有默认编码为新编码的客户端。要更改默认值而不更改客户端,请使用SetEncoding()函数,并将changeAllClients参数设置为false。默认情况下,编码是Encoding.ASCII。
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485