服务器概要设计说明.docx
《服务器概要设计说明.docx》由会员分享,可在线阅读,更多相关《服务器概要设计说明.docx(5页珍藏版)》请在冰豆网上搜索。
服务器概要设计说明
功能概述
服务器主要业务功能是连接物管和终端,为社区物管和管理中心提供管理功能,使其能够统一管理终端。
服务器的功能模块包括:
1.数据管理,数据包括房屋数据、住户数据、配租数据、门禁卡数据、终端配置数据等;
2.状态管理,服务器需要维持物管和终端的连接,保持连接状态的可增删改查;
3.命令管理,物管和终端之间的交互命令有确认机制,命令通过服务器传递,服务器需要保证命令传递的可靠性;
4.数据有效性检测,服务器需要定期检测一些数据的有效性,包括配租数据是否(临近)到期、门禁卡白名单数据与终端定期交换等;
5.文件传输通道,包括软件版本升级、数据文件传输等;
6.日志。
网络通信层
通信层负责业务命令和数据的发送接收。
由于物管、终端和服务器之间命令和数据需要精确送达,所有业务都采用TCP来实现。
IOCP模型是Windows服务器开发中性能最好的非阻塞异步IO模型,所以通信层采用IOCP模型构建。
Windows下有五种非阻塞I/O模型:
选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(OverlappedI/O)和完成端口(CompletionPort)。
Select是同步IO模型,同时处理的任务有限(上限1024),不符合处理成千上万连接的要求;WSAAsyncSelect也是同步IO模型,以接收Windows消息为基础,不符合服务器控制台程序要求;WSAEventSelect也是同步IO模型,需要创建与连接数等同的事件内核对象,资源未能高效利用,也排除在外;上面三种IO模型其实是一回事,都是类select模型,适合开发小型服务器或者客户端程序,而不适合需要接受成千上万连接的服务器程序。
OverlappedI/O是异步IO模型,但是它需要程序员关心线程池的实现和调度(类似Linux下面的epoll模型,但是epoll是同步IO模型);而IOCP克服了上面四种模型的缺点,对实现大连接数的服务器有可靠的性能和较少的资源占用,而且伸缩性比较强,占用资源数跟连接数量相关,甚至可以用在客户端程序上面。
连接生命周期的管理
C++语言没有对象回收(GC)机制,生命周期的管理和防止内存泄露需要程序自己实现,而一条连接从产生后到销毁的过程中会有多个线程同时对其进行操作,同时读写甚至同时关闭,对象的多线程同步也需要程序实现。
这里采用智能指针(shared_ptr,stl_c++11)来管理连接的生命周期,通信层维护各个连接在内存中唯一一份数据,同时提供引用计数,统计当前该数据被外界使用情况,当外界没有角色再需要该数据时(引用计数减到0),通信层会删除这份数据,同时表明该连接生命周期终止。
接口
数据接口采用handle/body手法,连接的handle采用整形数据,body采用C++对象封装连接数据,数据包含SOCKET句柄、连接状态和当前接收缓存(业务层)等。
连接生命周期反映到handle上表现为该handle是否为有效。
发送内存采用智能指针(unique_ptr,stl_c++11)进行传递,这里用到了智能指针对数据和数据析构的封装,发送完成之后直接调用其删除器(deleter)进行内存的删除,这样上下层之间就避免了一次内存拷贝。
回调接口为C++接口(纯虚函数)。
异步IO缓冲内存池
由于系统层和stl层容器都实现了小内存内存池,所以程序将不再实现自己的内存池,发送缓冲内存完全动态分配,接收缓冲内存每个连接有一份,也通过动态分配而来。
本地数据与字节流数据的互相转换
本地数据转换为字节流数据时,根据本地数据大小构造字节流对象,然后将本地数据逐字节填入流中,可变数组先填入数组大小再逐个填充数组内容。
字节流数据转换为本地数据时,根据字节流中标识的大小动态构造本地数据,构造时使用智能指针(unique_ptr,stl_c++11)管理数据,加上C++多态特性,可以大大简化内存的管理。
信令和通信数据结构
信令设计的原则是方便数据分析,即通过抓包工具得到数据后能够方便地定位到信令所属业务类型。
通信数据结构的设计原则是数据之间相互独立,减少耦合,但是又方便扩展,而且方便与本地数据之间的转换。
由于交互三方(服务器、终端、物管)采用三种不同的语言来实现,数据结构的定义必然各有不同,所幸三方都是面向对象语言,因此可以使用C++定义作为其他两方的伪代码来参考。
数据结构统一处于同一包头定义下,即都继承自同一个包头基类。
伪代码定义
classDemoPacket:
publicPacketHead{
public:
DemoPacket():
PacketHead(0,kPacketTypeDemo,0){}
DemoPacket(constPacketHead&head):
PacketHead(head){}
virtual~DemoPacket()override{}
virtualboolRead(PacketReadStream&stream)override{
if(stream.Read(a_)&&
stream.Read(b_)&&
c_.Read(stream)&&
d_.Read(stream)){
returntrue;
}
returnfalse;
}
virtualboolWrite(PacketWriteStream&stream)override{
if(PacketHead:
:
Write(stream)&&
stream.Write(a_)&&
stream.Write(b_)&&
c_.Write(stream)&&
d_.Write(stream)){
returntrue;
}
returnfalse;
}
virtualintCalculateSize()override{
autototal_size=0;
total_size+=PacketHead:
:
CalculateSize();
total_size+=sizeof(a_);
total_size+=sizeof(b_);
total_size+=c_.CalculateSize();
total_size+=d_.CalculateSize();
returntotal_size;
}
public:
BYTE4a_;//四字节整型字段
BYTE4b_;//四字节整型字段
PacketArrayBYTE1c_;//字符串字段
PacketArrayd_;//可变数组字段,Obj是一个(任意)类
};
其中BYTE4、PacketArrayBYTE1、PacketArray都是类型名,具体定义不表。
命令管理
命令需要精确送达,因此必须将命令放入队列中,待到已经精确送达才能移出。
队列需要实现等待机制,因此在每个命令包中加入该命令的序号,对方在确认回包时将此序号一同返回,收到此确认包后根据序号找到队列中未完成的命令,将其移出。
由于此队列只存在于内存中,服务器只能保证该队列中的命令能够精确送达,一旦程序关闭,该队列也会随之消失,因此其他两方(物管和终端)也需要加入同步或者异步确认机制,如果接收确认命令超时,因当重发,但是命令序号保持和之前的一致,但是服务器不保证转发后的命令序号的一致性,即如果物管发起一个序号为0x00000001的命令,服务器转发此命令给终端时序号可能为0x00000101,但是终端给服务器确认包的序号必须是0x00000101,而服务器给物管的确认回包的序号必须是0x00000001。
暂时不考虑同时命令数量超过整型(4字节)(4294967295个)范围的情况。
命令移出后内存中不可寻,但是会记录日志。
该队列使用stl标准容器map来实现。
数据有效性检测
有效性检测即定时轮询数据,查看是否有数据达到了需要告知物管或者终端的地步,然后发送命令告知。
效率是这个模块最需要考虑的问题,因此可以将需要检测的数据全部放入内存中进行操作。
文件传输通道
网络传输层搭建完成之后,文件传输通道可以轻松的完成。
由于IP层有包头校验,TCP层有包头加数据的校验,以上两层检验算法都是16bit字的二进制反码和(把需校验的数据看成以16位为单位的数字组成,依次进行二进制反码求和),网络通信层也有包内容校验,理论上文件数据是能保证其完整性的,只需要加一层文件MD5校验即可。
文件传输由于数据量大,占用带宽也大,所以应当做适度的限速。
日志
日志分为命令日志和运行日志。
命令日志指的是物管的操作命令产生的日志,例如开卡销卡等,以及终端的操作命令产生的日志,例如呼叫开门和警告处理等;运行日志指的是服务器程序在运行过程中遇到需要记录的情况而产生的日志,比如业务逻辑出错和服务器硬件资源不足等。
命令日志需要记录到数据库当中以便运维分析挖掘,运行日志直接记录到本地文件系统用来监测和调试服务器程序。
运行日志的记录采用最简单的同步写文件方式。