65.9K
CodeProject 正在变化。 阅读更多。
Home

WCF Killer

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (20投票s)

2011 年 2 月 12 日

CPOL

4分钟阅读

viewsIcon

77624

downloadIcon

1096

一个简单的、高性能的、即插即用的 TCP 协议处理程序,用于客户端-服务器通信,支持 POCO 对象,无需像 WCF 那样臃肿的代理类。

引言

这是我长期使用的一个代码片段,它使用简单,不施加任何额外工作,并且能够通过 TCP 协议完成客户端-服务器通信任务。您只需将单个文件包含在您的项目中,即可获得所有好处。

感谢 **Yuen Chiu So** 撰写了最初的文章,可在 dotnettcp.aspx 找到。最初的文章只处理字符串;我的文章将其扩展到一个级别,您可以在自己的客户端-服务器应用程序中使用它来传输详细的数据对象。我还对代码进行了调整和测试,以获得最佳性能和稳定性(请参阅性能测试部分)。

背景

这段代码的产生是我在长期使用命名管道通信并遇到以下问题之后,尽管可以说命名管道在数据传输方面更快

  • 命名管道连接到计算机需要身份验证,这在非域计算机上非常受限,并且需要用户输入目标计算机上有效的系统用户名和密码才能工作。
  • 我使用的命名管道实现(代码量上)非常庞大。

因此,出于上述原因,我着手创建一个替代品,以满足以下要求

  • 易于在代码中使用,配置最少。
  • 多线程,可以扩展到支持数百个并发用户。
  • 不施加额外的“代理”和“契约”代码,并且应与普通的可序列化对象配合使用。
  • 不需要计算机级别的身份验证,这在使用 TCP 协议时是隐式的,因为它在比操作系统更低的级别上处理(与命名管道不同)。
  • 灵活性,可以在其之上实现自己的身份验证。
  • 处理大型数据传输对象,例如 3MB 的数据包。

测试应用程序

testserver.png

运行解决方案,您将得到一个类似上面图片的命令行应用程序。按 '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 日。
© . All rights reserved.