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

可扩展的 TCP 客户端/服务器应用程序框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (12投票s)

2016年3月17日

CPOL

14分钟阅读

viewsIcon

40436

downloadIcon

825

提供开源客户端/服务器 DotNetOpenServer SDK 项目的介绍,该项目适用于 Android、iOS、Windows Phone、Windows、Mac 和 Java 平台

引言

如果您正在阅读本文,您可能已经看过 CodeProject 上无数关于 .NET TCP 套接字服务器的文章。我没有找到一个开源解决方案,它既快速又高效,易于扩展,包含安全性,特别是 TLS 1.2 支持,并且为 Android、iOS、Windows Phone、Windows、Mac 和 Java 客户端提供了 API。

本文展示了我创建的一个名为 DotNetOpenServer SDK 的开源项目。该 SDK 提供了一个客户端/服务器应用程序框架,实现了可扩展的二进制协议栈,支持 SSL/TLS 1.2 连接,包含可扩展的安全框架,包含保持连接/心跳协议,并为 Windows 和 Windows Mobile 提供 C# API,为 Android 和 Unix/Linux 平台提供 Java API,为 iOS 和 Mac 提供 Objective-C API。

本文是和不是什么

CodeProject 上已经有很多文章向开发人员展示了如何创建同步和异步 TCP 客户端/服务器应用程序。本文不试图重现已经做得很好的内容。相反,本文提供了框架的高层概述,详细介绍了框架的二进制协议栈,总结了如何通过协议实现来扩展框架,总结了如何创建自己的身份验证协议,并提供了创建自己的客户端/服务器应用程序的详细教程。

链接和第三方参考

架构

DotNetOpenServer 是在 Windows 上使用 .NET Framework 4.5.2 实现的。服务器实现为一个异步 TCP 套接字服务器。.NET 组件用于协商 SSL/TLS 1.2。协议在独立的程序集中实现,当客户端请求时,这些程序集会通过反射加载。服务器加载程序集所需的信息包含在 app.config 文件中,也可以选择通过编程方式设置。

项目

当我构建这个 API 时,我的目标是尽可能地减少重复代码。我最终得到了几个解决方案,甚至更多的项目。最低级别是一个名为 OpenServerSharedPortable 项目,它被服务器、Windows 客户端和 Windows Mobile 客户端共享。接下来是一个名为 OpenServerWindowsShared 的 Windows 项目,它在服务器和 Windows 客户端之间共享。最后,有一个用于服务器、Windows 客户端和 Windows Mobile 客户端的项目。

Project Hierarchy

由于 Android 应用程序是用 Java 编写的,我想支持 Unix 和 Linux 系统,我能够进一步减少代码重复。使用 Eclipse,我能够创建一个单一的 Java 项目,负责生成可供 Android 和 Unix/Linux 应用程序使用的 JAR 文件。

对于那些不熟悉 Apple 操作系统编程的人来说,iOS(iPhone 和 iPad)和 OS X(Mac)应用程序是用 Objective-C 或 Swift 编写的。当我开始这个项目时,我不知道 Objective-C 或 Swift 语言,所以我研究了自动生成选项。我惊喜地了解到 J2ObjC 项目,并且更惊喜地发现所有代码不仅可以顺利移植,而且似乎都能完美执行。同样,我能够最小化代码重复。将来,一旦有好的 Java 到 Swift 转换器可用,我也计划包含一个 Swift API。

J2ObjC

快速高效

由于创建快速高效的协议栈是该项目的核心要求,我将从详细介绍协议栈开始。如果您对字节流不感兴趣,请跳过本节。

会话层协议 (SLP)

会话层协议 (SLP) 是栈中的第一个协议。SLP 是一个非常简单的协议,传输 6 字节数据。第一个字段包含一个 2 字节的标识号。虽然标识号技术上不是必需的,但我认为它对于嗅探包含多个 DotNetOpenServer 数据包的 TCP 数据包是一个很好的补充。下一个字段包含一个 4 字节的 Uint32,指定了负载的字节长度。最后一个字段包含一个 2 字节的 Uint16,指定了下一层的唯一协议标识符。最后一个字段包含在负载长度中。同样值得注意的是,所有整数数据类型都以小端序(最低有效数字在前)传输。

为便于说明,下面是一个传输 3 字节数据,协议标识为 0x0B0A,负载为一个字节值为 0xFF 的数据包的样子。

53 55 03 00 00 00 0A 0B FF

能力协议 (CAP)

DotNetOpenServer 包含一个内部能力协议 (CAP),它允许客户端应用程序查询服务器支持的协议列表。另外值得注意的是,如果客户端尝试初始化一个不受支持的协议,服务器会通过 CAP 返回错误。CAP 的唯一协议标识符是 0x0000。协议标识符后面的第一个字节是命令值。

命令 类型 描述
GET_PROTOCOL_IDS 0X01 请求 由客户端发送,以获取服务器支持的协议 ID 列表
PROTOCOL_IDS 0X02 响应 由服务器响应 GET_PROTOCOL_ID 命令发送
ERROR 0xFF 响应 当客户端请求服务器不支持的协议时,由服务器发送

这是一个 GET_PROTOCOL_IDS 命令的样子

53 55 03 00 00 00 00 00 01

这是一个示例响应的样子

53 55 0D 00 00 00 00 00 02 03 00 00 00 01 00 02 00 0A 00

响应负载包含一个 Uint32,其中包含支持的协议数量,后跟每个协议的标识符。在这种情况下,支持 3 个协议:1、2 和 10。十六进制表示为 0x0001、0x0002 和 0x000A。

保持连接协议 (KAP)

DotNetOpenServer 包含一个保持连接协议 (KAP),可以根据需要启用或禁用。该协议有三个目的。首先,服务器实现为丢弃不活动的连接。KAP 通过发送非常小的数据包来保持连接。其次,由于 KAP 发送和接收数据包,它可以识别空闲或损坏的连接,从而使两端都可以关闭损坏的连接,释放资源,并在发生网络故障时通知客户端应用程序。最后,KAP 包含一个 QUIT 命令,允许两端在终止会话之前通知对方,从而及时释放资源。如果启用,KAP 是自动的,不需要也不提供任何外部方法。KAP 的唯一协议标识符是 0x0001。协议标识符后的第一个字节是命令值。

命令 描述
KEEP_ALIVE 0X01 由客户端和服务器发送,以保持空闲会话打开。
QUIT 0xFF 由客户端和服务器发送,以通知端点会话正在关闭。

这是一个 KEEP-ALIVE 命令的样子

53 55 03 00 00 00 01 00 01

可扩展性

我设计 DotNetOpenServer 时,可以由开发人员随时轻松地添加协议,而无需重新编译或重新部署服务器。这是通过反射实现的。换句话说,开发人员可以将他们的协议创建并安装到现有服务器部署中。协议实现只需创建一个 .NET 程序集或 JAR 文件,其中包含一个派生自 ProtocolBase 类的类。通常,您会为服务器端创建一个类,为客户端端创建一个类。实现后,您可以配置服务器以使用 app.config 文件加载协议,也可以通过编程方式配置服务器。最后,更新您的客户端应用程序以使用您的协议。

SDK 包含教程,逐步指导您完成创建服务器端协议以及支持平台的客户端协议的过程。

安全

没有安全性的客户端/服务器框架是不完整的。SDK 中包含一个 Windows 身份验证协议,但是;您可以创建并添加任意数量的身份验证协议。例如,服务器可以托管一个通过 Facebook 进行身份验证用户的协议,以及一个通过专有公司数据库进行身份验证用户的协议。CAP,如上所述,允许客户端用户选择身份验证协议。

身份验证协议的实现只需创建一个 .NET 程序集或 JAR 文件,其中包含一个派生自 AuthenticationProtocolBase 类的类。为服务器端创建一个类,然后实现您的身份验证方法。接下来,创建一个 .NET 和/或 Java 类来远程调用您的身份验证方法。如果您的身份验证方法支持角色和/或组,并且您想从其他协议检查角色成员资格,请在服务器端实现中覆盖 IsInRole 方法。如果您计划创建自己的身份验证方法,作为起点,我建议查看附加源中以及 GitHub 上的 Windows 身份验证协议或数据库身份验证协议的源代码。

您创建的任何协议都可以通过包含自己的授权功能来构建在安全模型之上。协议实现可以通过 ProtocolBase.Session.AuthenticationProtocol 属性访问调用客户端的用户名。返回的对象(AuthenticationProtocolBase 的实例)提供了一个 UserName 属性,并且如上所述,还有一个 IsInRole(string role) 方法。

整合

创建服务器应用程序

创建一个示例服务器应用程序非常简单。

首先,创建一个 .NET 4.5.2 控制台应用程序。

接下来,添加 DotNetOpenServer 程序集。要添加程序集,请打开 NuGet 包管理器控制台,然后键入以下命令:

PM> Install-Package UpperSetting.OpenServer
PM> Install-Package UpperSetting.OpenServer.Protocols.KeepAlive
PM> Install-Package UpperSetting.OpenServer.Protocols.WinAuth.Server
PM> Install-Package UpperSetting.OpenServer.Protocols.Hello.Server
PM> Install-Package log4net
描述
UpperSetting.OpenServer 包含服务器
UpperSetting.OpenServer.Protocols.KeepAlive 包含服务器和客户端的保持连接协议实现
UpperSetting.OpenServer.Protocols.WinAuth.Server 包含服务器端的 Windows 身份验证协议实现
UpperSetting.OpenServer.Protocols.Hello.Server 包含一个示例协议,该协议只是将 hello 消息回显给客户端
log4net 包含 Apache log4net 程序集,使您可以使用 log4net 记录消息

如前所述,服务器可以使用 app.conifg 文件或通过编程方式进行配置。两种方法都相当简单。

使用 app.config 创建服务器实例

首先,添加创建服务器的代码。在使用 app.config 配置服务器时,启动服务器所需的应用程序代码非常简单。只需创建一个 US.OpenServer.Server 实例。当您的应用程序准备好关闭时,调用 Server.Close 方法。例如:

using System;
using US.OpenServer;

namespace HelloServer
{
    class Program
    {
        static void Main(string[] args)
        {
            Server server = new Server();
            server.Logger.Log(Level.Info, "Press any key to quit.");
            Console.ReadKey();
            server.Close();
        }
    }
}

接下来,将配置添加到 app.config 文件中。需要 3 个部分:log4net、server 和 protocols。添加到 'configSections' 元素后,必须实现每个部分。

log4net 部分

log4net 部分内容非常广泛,超出了本文的范围。有关更多信息,请参阅 Apache log4net 网站

server 部分

server’ 部分包含以下元素:

元素/属性 描述
host 绑定 TCP 套接字服务器的 IP 地址。默认为 0.0.0.0(所有 IP 地址)。
port 运行服务器的 TCP 端口。默认为 21843。
tls True 表示启用 SSL/TLS 1.2,否则为 False。默认为 False。
tls/certificate 用于身份验证的 X509Certificate。
tls/requireRemoteCertificate True 表示要求端点提供证书进行身份验证,否则为 False。
tls/allowSelfSignedCertificate True 表示启用自签名证书,否则为 False。
tls/checkCertificateRevocation True 表示在身份验证期间检查证书吊销列表,否则为 False。
tls/allowCertificateChainErrors True 表示在身份验证期间检查证书链,否则为 False。
idleTimeout 连接在被自动关闭之前可以保持空闲的秒数。默认为 300 秒。
receiveTimeout 接收操作等待数据时阻塞的秒数。默认为 120 秒。
sendTimeout 发送操作等待数据时阻塞的秒数。默认为 120 秒。

例如

<server>    
    <host value="0.0.0.0" />
    <port value="21843" />
    <tls value="true"
         certificate="UpperSetting.com"
         requireRemoteCertificate="false"
         allowSelfSignedCertificate="false"
         checkCertificateRevocation="true"
         allowCertificateChainErrors="false"/>
    <idleTimeout value="300" />
    <receiveTimeout value="120" />
    <sendTimeout value="120" />
</server>

protocols 部分

protocols’ 部分包含一个‘item’元素的数组。每个‘item’元素包含 4 个属性,我在下表中定义了它们。

Attribute 描述
id 一个 Uint16,指定唯一的协议标识符。
assembly 一个 String,指定实现协议的程序集。
classPath 一个 String,指定实现协议的类的类路径。
configClassPath 一个 String,指定实现协议配置读取器的类的类路径。

例如

<protocols>
    <item id="1"
          assembly="US.OpenServer.Protocols.KeepAlive.dll"
          classPath="US.OpenServer.Protocols.KeepAlive.KeepAliveProtocol" />
    <item id="2"
          assembly="US.OpenServer.Protocols.WinAuth.Server.dll"
          classPath="US.OpenServer.Protocols.WinAuth.WinAuthProtocolServer"
          configClassPath="US.OpenServer.Protocols.WinAuth.WinAuthProtocolConfigurationServer">
        <permissions>
            <roles>
                <role value="Administrators" />
            </roles>
            <users>
                <user value="TestUser" />                                    
            </users>
        </permissions>
    </item>
    <item id="10"
          assembly="US.OpenServer.Protocols.Hello.Server.dll"
          classPath="US.OpenServer.Protocols.Hello.HelloProtocolServer" />
</protocols>

SDK 文档中有一个完整的 app.config 示例。

最后,编译并运行。

通过编程方式创建服务器实例

首先,添加创建服务器的代码。从 Main 函数,创建一个 US.OpenServer.Configuration.ServerConfiguration 对象,然后设置您要覆盖的任何属性,包括 SSL/TLS 属性。

ServerConfiguration cfg = new ServerConfiguration();
cfg.TlsConfiguration.Enabled = true;
cfg.TlsConfiguration.Certificate = "UpperSetting.com";

接下来,创建一个 US.OpenServer.Protocols.WinAuth.WinAuthProtocolConfigurationServer 对象,然后添加您要授权访问的用户组和用户。

WinAuthProtocolConfigurationServer winAuthCfg = new WinAuthProtocolConfigurationServer();
winAuthCfg.AddRole("Administrators");
winAuthCfg.AddUser("TestUser");

接下来,创建一个 Dictionary,其中包含 US.OpenServer.Protocols.ProtocolConfiguration 对象,并以唯一协议标识符作为键,该字典包含以下三个协议:

  • US.OpenServer.Protocols.WinAuth.WinAuthProtocolServer
  • US.OpenServer.Protocols.KeepAlive.KeepAliveProtocol
  • US.OpenServer.Protocols.Hello.HelloProtocol
Dictionary<ushort, ProtocolConfiguration> protocolConfigurations =
    new Dictionary<ushort, ProtocolConfiguration>();

protocolConfigurations.Add(WinAuthProtocol.PROTOCOL_IDENTIFIER, winAuthCfg);

protocolConfigurations.Add(KeepAliveProtocol.PROTOCOL_IDENTIFIER,
    new ProtocolConfiguration
      (KeepAliveProtocol.PROTOCOL_IDENTIFIER, typeof(KeepAliveProtocol)));

protocolConfigurations.Add(HelloProtocol.PROTOCOL_IDENTIFIER,
    new ProtocolConfiguration
      (HelloProtocol.PROTOCOL_IDENTIFIER, typeof(HelloProtocolServer)));

创建 US.OpenServer.Server,传入 ServerConfigurationProtocolConfiguration 对象的 Dictionary

Server server = new Server(cfg, protocolConfigurations);

最后,当您的应用程序准备关闭时,调用 Server.Close()

server.Close();

此服务器示例应用程序的完整源代码可在附加源以及 GitHub 上找到。

创建客户端应用程序

如引言中所述,我想为 Android、iOS、Windows Phone、Windows、Mac 和 Java 客户端提供 API。为了尽量缩短本文的篇幅,我将只向您展示如何创建 Windows 客户端,但是;DotNetOpenServer SDK 包含了支持平台的教程。

创建 Windows 客户端

创建一个示例客户端应用程序非常简单。

步骤 1

创建一个 .NET 4.5.2 控制台应用程序。

第二步

添加 DotNetOpenServer 程序集。要添加程序集,请打开 NuGet 包管理器控制台,然后键入以下命令:

PM> Install-Package UpperSetting.OpenServer.Windows.Client
PM> Install-Package UpperSetting.OpenServer.Protocols.KeepAlive
PM> Install-Package UpperSetting.OpenServer.Protocols.WinAuth.Client
PM> Install-Package UpperSetting.OpenServer.Protocols.Hello.Client
描述
UpperSetting.OpenServer.Windows.Client 包含客户端
UpperSetting.OpenServer.Protocols.KeepAlive 包含服务器和客户端的保持连接协议实现
UpperSetting.OpenServer.Protocols.WinAuth.Client 包含客户端的 Windows 身份验证协议实现
UpperSetting.OpenServer.Protocols.Hello.Client 包含一个示例协议,该协议只是将消息发送到服务器并接收回显响应

步骤 3

Main 函数,创建一个 US.OpenServer.Configuration.ServerConfiguration 对象,然后设置您要覆盖的任何属性,包括 SSL/TLS 属性。如果您复制下面的代码,请将 ServerConfiguration.Host 值替换为服务器正在运行的主机名。

ServerConfiguration cfg = new ServerConfiguration();
cfg.Host = "UpperSetting.com";
cfg.TlsConfiguration.Enabled = true;

步骤 4

创建一个 Dictionary,其中包含 US.OpenServer.Protocols.ProtocolConfiguration 对象,并以唯一协议标识符作为键,该字典包含以下三个协议:

  • US.OpenServer.Protocols.WinAuth.WinAuthProtocolClient
  • US.OpenServer.Protocols.KeepAlive.KeepAliveProtocol
  • US.OpenServer.Protocols.Hello.HelloProtocolClient
Dictionary<ushort, ProtocolConfiguration> protocolConfigurations =
    new Dictionary<ushort, ProtocolConfiguration>();

protocolConfigurations.Add(WinAuthProtocol.PROTOCOL_IDENTIFIER,
    new ProtocolConfiguration
     (WinAuthProtocol.PROTOCOL_IDENTIFIER, typeof(WinAuthProtocolClient)));
    
protocolConfigurations.Add(KeepAliveProtocol.PROTOCOL_IDENTIFIER,
    new ProtocolConfiguration
     (KeepAliveProtocol.PROTOCOL_IDENTIFIER, typeof(KeepAliveProtocol)));

protocolConfigurations.Add(HelloProtocol.PROTOCOL_IDENTIFIER,
    new ProtocolConfiguration
     (HelloProtocol.PROTOCOL_IDENTIFIER, typeof(HelloProtocolClient)));

步骤 5

创建 US.OpenServer.Client,传入 ServerConfigurationProtocolConfiguration 对象的 Dictionary

client = new Client(cfg, protocolConfigurations);

步骤 6

调用 Client.Connect 连接到服务器。

client.Connect();

步骤 7

要获取服务器上正在运行的协议列表,请调用 Client.GetServerSupportedProtocolIds。例如:

ushort[] protocolIds = client.GetServerSupportedProtocolIds();
foreach (int protocolId in protocolIds)
    client.Logger.Log(Level.Info, "Server Supports Protocol ID: " + protocolId);

步骤 8

初始化 WinAuthProtocolClient 协议,然后调用 WinAuthProtocolClient.Authenticate 来进行身份验证。如果您复制下面的代码,请将下面的用户名/密码替换为您自己的用户名/密码。

string userName = "TestUser";
string password = "T3stus3r";
string domain = null;
WinAuthProtocolClient wap = 
   client.Initialize(WinAuthProtocol.PROTOCOL_IDENTIFIER) as WinAuthProtocolClient;
if (!wap.Authenticate(userName, password, domain))
    throw new Exception("Access denied.");

步骤 9

初始化 KeepAliveProtocol 以启用客户端/服务器保持连接(又名 Heartbeat)协议。

client.Initialize(KeepAliveProtocol.PROTOCOL_IDENTIFIER);

第 10 步

初始化 HelloProtocolClient,然后调用 HelloProtocolClient.Hello。例如:

HelloProtocolClient hpc = (HelloProtocolClient)client.Initialize
                               (HelloProtocol.PROTOCOL_IDENTIFIER);
string serverReponse = hpc.Hello(userName);
client.Logger.Log(Level.Info, serverReponse);

客户端构造函数的每个参数都可以设置为 null。如果将参数设置为 null,则构造函数将使用默认属性值创建配置对象的实例,然后尝试从 app.config 文件加载属性。

第 11 步

编译并运行。客户端/服务器应用程序应显示以下输出:

服务器输出
Info Execution Mode: Debug
Info Press any key to quit.
Info Listening on 0.0.0.0:21843...
Info Session [1 127.0.0.1] - Connected.
Debug Session [1 127.0.0.1] - [Capabilities] Sent Protocol IDs: 1, 2, 10
Debug Session [1 127.0.0.1] - Initializing protocol 2...
Info Session [1 127.0.0.1] - [WinAuth] Authenticated \TestUser.
Debug Session [1 127.0.0.1] - Initializing protocol 10...
Info Session [1 127.0.0.1] - [Hello] Client says: TestUser
Info Session [1 127.0.0.1] - [Hello] Server responded: Hello TestUser
Debug Session [1 127.0.0.1] - Initializing protocol 1...
Debug Session [1 127.0.0.1] - [Keep-Alive] Received.
Debug Session [1 127.0.0.1] - [Keep-Alive] Received.
Debug Session [1 127.0.0.1] - [Keep-Alive] Sent.
Info Session [1 127.0.0.1] - [Keep-Alive] Quit received.
Info Session [1 127.0.0.1] - Disposed.
客户端输出
Info Execution Mode: Debug
Info Connecting to localhost:21843...
Info Connected to localhost:21843.
Debug Session [1 127.0.0.1] - [Capabilities] Received Protocol IDs: 1, 2, 10
Info Server Supports Protocol ID: 1
Info Server Supports Protocol ID: 2
Info Server Supports Protocol ID: 10
Debug Session [1 127.0.0.1] - Initializing protocol 2...
Info Session [1 127.0.0.1] - [WinAuth] Authenticated.
Debug Session [1 127.0.0.1] - Initializing protocol 1...
Debug Session [1 127.0.0.1] - Initializing protocol 10...
Info Session [1 127.0.0.1] - [Hello] Client says: TestUser
Info Session [1 127.0.0.1] - [Hello] Server responded: Hello TestUser
Info Hello TestUser
Info Press any key to quit.
Debug Session [1 127.0.0.1] - [Keep-Alive] Sent.
Debug Session [1 127.0.0.1] - [Keep-Alive] Sent.
Debug Session [1 127.0.0.1] - [Keep-Alive] Received.
Debug Session [1 127.0.0.1] - [Keep-Alive] Quit sent.
Info Session [1 127.0.0.1] - Closed.

此示例应用程序的完整源代码可在附加源以及 GitHub 上找到。

结论

我已经向您展示了如何使用开源 DotNetOpenServer SDK 来创建能够安全访问云中运行的数据和/或业务逻辑的智能移动设备应用程序。我提供了几个链接,展示了如何创建自己的协议,包括如何实现自己的身份验证模型。我详细介绍了如何使用 SDK 创建 Windows 客户端/服务器应用程序,并提供了创建 Android、iOS、Windows Phone、Mac 和 Java 客户端的链接。

我希望这个开源项目对社区有所帮助,我欢迎您所有意见。非常感谢您阅读本文。

© . All rights reserved.