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






4.91/5 (12投票s)
提供开源客户端/服务器 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 客户端/服务器应用程序。本文不试图重现已经做得很好的内容。相反,本文提供了框架的高层概述,详细介绍了框架的二进制协议栈,总结了如何通过协议实现来扩展框架,总结了如何创建自己的身份验证协议,并提供了创建自己的客户端/服务器应用程序的详细教程。
链接和第三方参考
-
本文的完整源代码可以在上面找到,也可以在 GitHub 上找到:
https://github.com/UpperSetting/DotNetOpenServerSDK -
Windows 二进制文件可以选择从 NuGet 下载。有关更多信息,请参阅 GitHub 上的 README.md:
https://github.com/UpperSetting/DotNetOpenServerSDK/blob/master/README.md -
文档和最新的客户端 Java JAR 文件可从 GitHub 下载:
https://github.com/UpperSetting/DotNetOpenServerSDK/releases -
Windows 服务器和客户端可选支持通过 log4Net 进行日志记录,log4Net 可从 NuGet 下载:
https://nuget.net.cn/packages/log4net/ -
为了最小化代码库,我使用位于 http://j2objc.org/ 的 J2ObjC 开源项目自动生成了 Objective-C API,这是从我的 Java 源代码生成的。
http://j2objc.org/
架构
DotNetOpenServer
是在 Windows 上使用 .NET Framework 4.5.2 实现的。服务器实现为一个异步 TCP 套接字服务器。.NET 组件用于协商 SSL/TLS 1.2。协议在独立的程序集中实现,当客户端请求时,这些程序集会通过反射加载。服务器加载程序集所需的信息包含在 app.config 文件中,也可以选择通过编程方式设置。
项目
当我构建这个 API 时,我的目标是尽可能地减少重复代码。我最终得到了几个解决方案,甚至更多的项目。最低级别是一个名为 OpenServerShared
的 Portable
项目,它被服务器、Windows 客户端和 Windows Mobile 客户端共享。接下来是一个名为 OpenServerWindowsShared
的 Windows 项目,它在服务器和 Windows 客户端之间共享。最后,有一个用于服务器、Windows 客户端和 Windows Mobile 客户端的项目。
由于 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。
快速高效
由于创建快速高效的协议栈是该项目的核心要求,我将从详细介绍协议栈开始。如果您对字节流不感兴趣,请跳过本节。
会话层协议 (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
,传入 ServerConfiguration
和 ProtocolConfiguration
对象的 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
,传入 ServerConfiguration
和 ProtocolConfiguration
对象的 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 客户端的链接。
我希望这个开源项目对社区有所帮助,我欢迎您所有意见。非常感谢您阅读本文。