用于协议开发的C++框架






4.63/5 (11投票s)
一个使应用程序协议开发民主化的 C++ 库。
引言
欢迎。这是为了实现一个抽象而进行的努力,开发人员可以使用它来构建具体的网络协议实现。它以一套 C++ 库类的形式提供,任何人都可以以此为基础进行构建。
这种抽象的主要好处是易于将协议实现集成到应用程序中,因为我们不想浪费时间去关心如何实现所需的协议或改编第三方协议的实现。
此外,这种抽象还可以帮助人们轻松地协同合作,重用他们的工作成果,甚至为其他想法打开大门。例如,一些开发人员可以专注于协议实现,而另一些开发人员则可以专注于协议测试工具。
目标
抽象只有在解决所有当前问题和需求,并且在被更改之前保持有效时才是有价值的。此外,它应该是自然和简洁的。这意味着所有差异都被归结为一个模型。当开发人员查看这个抽象时,他应该觉得它是自然的,并且他应该觉得设计能够轻松地引导他完成实现。
这些目标是针对协议开发人员的。从应用程序开发人员的角度来看,抽象应该使他们能够轻松地将实现集成到他们的应用程序中,最重要的是,能够轻松地将协议堆叠起来。例如,决定增加安全性应该像通过几次调用连接两个协议实现一样简单。或者,如果应用程序正在传输大量数据,并且我们发现有人提出了一个压缩协议,那么应该很容易将该实现堆叠到顶部。下面的图表显示了一个示例,其中网络数据进入底层 TLS,TLS 将其解密为 WebSocket 帧,然后 WebSocket 帧的有效负载被解压缩成离散的缓冲区单元,这些单元被反序列化为应用程序响应的结构化消息对象。当应用程序发送一个消息对象时,它会被序列化成一个字节缓冲区,然后被压缩,然后被打包成 WebSocket 帧,然后被加密并通过 TCP 发送。
实现
试图设想抽象是什么样子,需要一个人看过许多协议实现,并推断出共同的要求。根据我自己的经验,我列出了我看到的最重要的要求。我很高兴看到其他人有什么想法、赞成或批评。
- 编码与序列化:协议绝不应该强制规定传输内容的表示方式。在协议的分层栈中,上层仍然会将收到的数据视为纯字节内容。协议的职责不是将字节反序列化为特定的结构化类型,也不是期望输入是特定类型,然后才进行序列化。协议会将字节缓冲区编码为字节缓冲区,并将字节缓冲区解码为字节缓冲区。我认为任何超出此范围的协议实现都在做一些额外的事情,这只会使其难以重用实现,难以将协议分层在一起,等等。
- 许多协议需要一种方式来存储每个连接的数据。编码和解码可能取决于这些数据的演变。(在 ProtocolFramework 中,这就是 ProtocolContext 类)
- 当数据到达时,每个协议层都会尝试解码内容并将其传递给上层。这个过程应该重复进行,直到没有层能够将新内容传递给其上层。这也意味着每个层都需要缓冲需要更多读取的数据。
- 当数据到达时,不应直接缓冲。有些协议会对内容进行转换。因此,我们应该给它们一个机会来执行此转换,然后再将新结果与先前未传递给上层的数据追加在一起。
- 当协议解码数据时,它可以选择返回内容,或者返回需要发送回对端而无需应用程序代码干预的协议字节。这将满足许多需要它们称为控制消息的协议的需求。最简单的例子是,当一层发送常规的心跳消息,而另一层必须回复时。
- 抽象应该提供一种方式,让协议层进入初始化阶段,在此期间,上层和应用程序层在阶段完成之前不应发送任何数据。例如,如果我们堆叠 WebSocket 和 TLS,那么当连接建立时,我们需要 TLS 层完成其握手阶段,然后才能允许 WebSocket 层开始交换数据。在 WebSocket 完成握手阶段之前,应用程序代码本身仍然不会启动任何操作。
用例
如果您想看到该框架的完整工作示例,可以查看 CodePlex 上 Push Framework 存储库中的 WebSocket 解决方案。
http://pushframework.codeplex.com/SourceControl/latest (转到版本 3.0,然后到 WebsocketServer 目录)。
WebsocketProtocol 是一个使用 ProtocolFramework 抽象实现的 WebSocket 协议(RFC 6455)的库。DemoServer 是一个简单的示例服务器,它只回显它接收到的任何内容。它使用 WebsocketProtocol 作为协议,我利用 Autobahn WebSocket 测试套件中的模糊测试客户端来稳定协议实现。
得益于 ProtocolFramework,我现在可以轻松地在多个协议之间切换,将协议堆叠在一起,甚至使用多个监听端口,每个端口与理解特定协议栈的不同类别的客户端通信。
MyServer theServer;
WebsocketProtocol webProtocol;
//Create a listening port using unsecured port.
ListenerOptions lOptions;
lOptions.pProtocol = &webProtocol;
theServer.createListener(10010, &lOptions);
//Create a second listening port, for secured clients:
SSLProtocol sslProtocol;
if (!sslProtocol.initializeAsServer("E:\\certificates\\server.crt",
"E:\\certificates\\server.key",
"pass"))
{
cout << "ssl initialization failed"<< endl;
return -1;
}
WebsocketProtocol webProtocol2;
webProtocol2.addLowerProtocolLayer(&sslProtocol);
ListenerOptions lOptions2;
lOptions2.pProtocol = &webProtocol2;
theServer.createListener(10010, &lOptions2);
theServer.start(true)