WCF Killer






4.92/5 (20投票s)
一个简单的、高性能的、即插即用的 TCP 协议处理程序,用于客户端-服务器通信,支持 POCO 对象,无需像 WCF 那样臃肿的代理类。
引言
这是我长期使用的一个代码片段,它使用简单,不施加任何额外工作,并且能够通过 TCP 协议完成客户端-服务器通信任务。您只需将单个文件包含在您的项目中,即可获得所有好处。
感谢 **Yuen Chiu So** 撰写了最初的文章,可在 dotnettcp.aspx 找到。最初的文章只处理字符串;我的文章将其扩展到一个级别,您可以在自己的客户端-服务器应用程序中使用它来传输详细的数据对象。我还对代码进行了调整和测试,以获得最佳性能和稳定性(请参阅性能测试部分)。
背景
这段代码的产生是我在长期使用命名管道通信并遇到以下问题之后,尽管可以说命名管道在数据传输方面更快
- 命名管道连接到计算机需要身份验证,这在非域计算机上非常受限,并且需要用户输入目标计算机上有效的系统用户名和密码才能工作。
- 我使用的命名管道实现(代码量上)非常庞大。
因此,出于上述原因,我着手创建一个替代品,以满足以下要求
- 易于在代码中使用,配置最少。
- 多线程,可以扩展到支持数百个并发用户。
- 不施加额外的“代理”和“契约”代码,并且应与普通的可序列化对象配合使用。
- 不需要计算机级别的身份验证,这在使用 TCP 协议时是隐式的,因为它在比操作系统更低的级别上处理(与命名管道不同)。
- 灵活性,可以在其之上实现自己的身份验证。
- 处理大型数据传输对象,例如 3MB 的数据包。
测试应用程序
运行解决方案,您将得到一个类似上面图片的命令行应用程序。按 'S' 键进入服务器模式,按 'C' 键进入客户端模式,按其他任何键进入自动模式,它将开始向服务器发送请求。该应用程序已硬编码为在 127.0.0.1 IP 地址(即本地系统)上发送和接收;您可以更改它来在具有不同计算机的真实网络上进行测试。
data.cs 文件包含示例 POCO 数据对象,该对象被硬编码为 3MB 大小;您可以更改它来测试更小和更大的数据包(请参阅性能测试部分)。
使用代码
要使用代码,首先您必须创建像下面这样的数据包
[Serializable()]
public class Packet
{
public Packet()
{
data = new byte[3*1024*1024]; // 3 mega byte
}
public byte[] data { get; set; }
public string Message { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public Guid SessionGuid { get; set; }
public new string ToString()
{
return Message;
}
}
[Serializable()]
public class ReturnPacket
{
public bool OK { get; set; }
public string Message { get; set; }
}
正如您所见,数据结构是普通的类而不是代理;您还必须将 Serializble
属性添加到类上,以便 .NET 序列化程序可以与其配合使用。
对于服务器代码,您需要这样做
NetworkServer ns = new NetworkServer(99, new ProcessPayload(serverprocess));
// will listen on port 99
ns.Start();
private static object serverprocess(object data)
{
Packet dp = data as Packet;
if (dp != null)
return HandlePacket(dp);
Console.WriteLine("message not recognized");
return new ReturnPacket();
}
private static object HandlePacket(Packet dp)
{
ReturnPacket ret = new ReturnPacket();
if (dp.SessionGuid == Guid.Empty)
{
// Authenticate username and password possibly with LDAP server
}
else
{
// check sessionguid valid -> if not return failed
}
ret.OK = true;
ret.Message = "your msg : " + dp.Message + "\r\nreturn from server " + DateTime.Now;
return ret;
}
正如您所见,这非常直接和简单。
对于客户端,您需要这样做
NetworkClient nc = new NetworkClient("127.0.0.1", 99);
// send to local host on port 99
nc.Connect();
Packet p = new Packet();
ReturnPacket ret = nc.Send(p) as ReturnPacket;
由您决定
以下内容由您自行决定,以适合您的应用程序
- 数据包定义:您想要通过网络发送的内容
- 身份验证:如果您需要,身份验证处理由您负责
- 会话管理:与身份验证相关的是会话管理
性能测试
以下是在 AMD K625 1.5ghz、4GB RAM、Windows 7 Home、win 评级 3.9 的笔记本电脑(CPU 使用率超过 88%)上进行的性能测试结果
并发客户端数量 | 数据包大小 | 块大小 | 10 秒内的请求数 |
5 | ~12kb | 32kb | 12681 |
5 | ~12kb | 16kb | 12291 |
5 | ~12kb | 4kb | 11089 |
5 | ~3mb | 4kb | 141 |
5 | ~3mb | 16kb | 207 |
5 | ~3mb | 32kb | 220 |
正如您所见,对于小型和大型数据包,CHUNK_SIZE
为 32 KB 时性能最佳。您可以设置更大的值,例如 64 KB,但收益会递减,并且可能(我不确定)在某些交换机和路由器上会遇到问题,因为它们可能会阻止较大的数据包大小。
关注点
我将网络服务器代码放在了 NETSERVER
条件编译块中,因此您需要将其添加到您的项目定义中。在 NetWorkClient.cs 文件中有一个 Config
类,其中包含一些我已调整到最佳性能的预设选项;一个值得注意的选项是 NUM_OF_THREADS
,它决定了服务器创建多少线程来处理请求。默认值为 10,这对于大多数应用程序来说应该足够了;在实际情况中,我已将其用于处理 50 多个客户端的应用程序。
您可以在家尝试或进一步开发的方向
以下是如果您想冒险,可以在家尝试的可能性列表
- 进度事件:用于发布数据进度的事件,客户端应用程序可以挂接并显示 UI。
- 加密通信:使用
SslStream
而不是NetworkStream
进行加密数据传输。 - LDAP 用户身份验证:使用 LDAP 服务器检查数据包中嵌入的用户名和密码(请参阅测试应用程序中的注释)。
- 会话管理:处理客户端会话和已认证的连接(请参阅测试应用程序中的注释)。
- 也许替换
BinarySerializer
:为了获得更高的性能,您可以研究一下 Google 的 Protocol Buffers,它非常快速且紧凑。
历史
- 首次发布:2011 年 2 月 12 日。