一个用于协议开发的 C++ 框架
前言
欢迎!这里有一个网络基础类的抽象封装,开发者可以构建网络协议的具体实现。它以C++库的形式提供了一套类集,任何人都可以使用它构建程序。
这些抽象的主要好处是便于将这些协议实现集成到程序中我们不想浪费时间实现期望的协议或者使用一个第三方实现的地方。
此外,这些抽象便于员工协同工作,复用他们的工作并且提供其它实现的接口。例如,一些开发者可以致力协议的实现,另一些则聚集于协议测试工具。
目标
解决当前所有问题和满足所有需求的抽象层级是最好的,可以在更改之前保持长时间的有效性。此外,它应该符合人类思维并且足够精确。这意味着所有的差异都要集中到一个模型中。这些抽象应该在开发者看到他们的时候感到自然顺畅,并且能够在整个实现过程中指导他们如何实现协议。
下面这些目标是要告知协议开发者的。从程度开发的观点来看,抽象层级应该便于协议实现在应用中集成以便于调用,更重要的是将线路通信协议置于两个通信进程的首要位置。例如,增加传输安全验证应该通过少量的线路通信协议调用实现。如果应用程序需要传输海量数据并且我们发现其中一个通信端使用了压缩数据协议,它应该在高层协议实现数据的解压缩。下面的图表演示了一个例子,当网络层数据进入传输层TLS时,需要将IP数据包解码成传输层的有效负荷Websocket帧,然后再解压成离散的字节缓冲单元,最后序列化成应用程序可以使用的对象消息结构。当应用程序传递消息时,先序列化成字节缓冲单元,然后压缩并转换成Websocket帧,编码并发送给TCP。
实现
在规划协议的抽象层次之前,需要一个人浏览很多协议实现并且推论出公共的要求。从我的经验来看,我会将了解到的重要要求写在一张清单上。对于别人赞扬或者批评的评论我都会表示赞赏。
编码和序列化:协议尽量使数据的表示方式透明。在一个协议栈的层级上,上层协议将会按照字节处理收到的数据。如果不是商用协议就不要将数据序列化成特殊的结构,或者期望使用一种特殊类型来进行序列化。协议将会将字节编码成另一种字节,也会将字节解码成另一种字节。我认为任何协议实现都应该超越这个层次,作一些额外的数据填充使得协议实现复用和同一层次协议聚集变得困难等等。
许多协议需要一种通信路径存储每一连接数据。编码和解码可能依赖于数据的演化。(在协议框架中,这种类称为 ProtocolContext类)。
当收到数据时,每一个协议层都试图解码内容并传递到上一层协议中。这个过程会不断重复直到没有协议层可以将数据推送到上层时。这也意味着每一层需要数据缓冲区和更多的读操作。
当收到数据时,它不应该被直接送到缓冲区,一些协议将会对内容进行转换,所以我们应该在预先读到的数据中没有增加新的内容并且尚未提交给上一层时提供转换操作。
当协议解码数据时,它可能返回解码后的数据,也可能将数据交由对等的应用程序处理而不介入数据解码。这种数据被称为控制信息,它将会满足许多协议的需要。最简单的例子,当一层协议需要送 heart beat信息时其它的协议必须予以回复。
抽象应该提供一种方式便于协议层进入初始化阶段,在这期间,上层协议和应用层不应当发送任何数据直到初始化完成。例如,如果在Websocket和 TLS层,当一个连接建立时,我们需要TLS层完成握手操作,然后再给Websocke发送OK信息并开始交换数据。应用程序代码不能开始任何操作直到Websocke完成握手操作。
用例:
如果你想浏览一个完整的框架工作用例,你可以浏览CodePlex上的 Push Framework repository 中的Websocket solution。
http://pushframework.codeplex.com/SourceControl/latest(查找 version 3.0,打开WebsocketServer 目录)。
WebsocketProtocol 是一个使用ProtocolFramework抽象实现Websocket Protocol (RFC 6455) 的库文件。DemoServer是一个仅打印出它接收到数据的示例服务器。多亏那些在Autobahn Websocket测试站点的不知名的客户端,它将WebsocketProtocol视为协议而我用它使协议实现变得健壮。
感谢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)