文章目录
共享项目收获总结
这是一个局域网分布式资源存储项目,没有涉及外部P2P的穿墙技术.
得失
目的
技术道路
起源
一直希望在P2P上有所进展.
特别某次思考到分布式计算的设计.
留下了一个思考框架在笔记中.
此次正值上份更新完成空档时间,看了下最近的项目需求是减少流量,而局域网P2P是个有效措施,
遂向领导申请做此事.
开始进行预研.
结果
本项目:
总行数: 4300
代码规模:2600
实际编程历时在:三周.
28,29,30,31,18,
19,20,21,22,25,
26,27
作为对比:Proxy项目:
总行数:5200.
代码规模:3400.
另一个对比是RPC项目
总行数: 4200
代码规模:2300
历时:两周+
效率评估
时间不是太长.提升很大.基本都在设计和实现中.
设计得与失
从时间上来说没什么太大问题.
但是前期设计还是费了不少时间.
跨进程请求
为了稳定性,独立服务端目的,因为有同机有多个程序在不同阶段需要调用P2P下载服务.
如果再组织一个跨进程通信…太麻烦了:其实可以通过共享内存来处理.原因是我没有做过这个…
这里采用的措施是:
将下载需求放入配置文件中…
下载文件管理
ini中hash作为key.也算是一个好设计.
通信层选择
经历:
- MailSlot
切换原因:
受限制的发送长度.
不能适应多端通信.没有单独的向不同客户端通信的设计.
导致一次只能向一端通信,即使这样也不太方便切换端.
也就是KD-RPC设计的时候只考虑了向单一服务器通信.
改进方向是:在通信层上增加不同的目标地址.
此外其设计上偏重于发送数据给服务器.而不是从服务器拉回数据. - Pipe
切换原因:
受限制的网络服务.
以为这个可以不限制通信长度.从资料中了解到它跨机限制长度后:改成服务器回复使用:分片发送.限制客户端的发送长度. - Socket
沿用了客户端发送请求限制在一个包里,收到即返回.
如果能更早的使用Socket来做处理可少走很多弯路.
这是一条经验.
三种选择.
通信层至少花费4天时间.后期会更快.
预研效果
前期预研主要集中在通信结构设计.
初步研判:这也是KD-RPC即不能很好的表达从服务器上拉回数据这一需求.
测试用例仅存在于最外部,导致内部代码轻易改动不了.
任务分为如下:
- 新的通信结构的设计
- 新的通信协议封装
总体上预研还是不错的.特别是在定下结构为项目设计.
使用空函数的方式使得逻辑能呈现在代码中,并在后续过程中陆续对空函数进行实现.
空函数使用了异常抛出来防止误用.
长连接->短连接
早期设计是长连接.
与特定客户端的通信,不是每次都关闭.
即组成P2P网.但是由于一些考虑,导致无法做合理复用,只能使用短连接暂时解决.
单元测试
真正做到了类级单元测试.
这极大地改善了代码的可靠性,
为代码的稳步建构提供了保障.
设计艺术之骨架先行
性能改善
主要遇到过以下性能问题:
- 内存管理
- 扫描局域网范围
- 缩小范围
- 无法缩短时间的阻塞类请求
- 将对象指针作为参数传给多线程.
- 无论成功失败都将在设置对象数据后退出线程
- 统一等待所有线程退出.
- 循环检查各个线程的结果.
//这里可以优化,现在的检查方案是:没有成败独立标识,直接操作句柄,导致异常来判断.
//应该改为更有效率的标识法.
通信结构设计-效率至上
由于我们采取了std::string作为存储的方式,
所以实际上并不太方便使用位处理和直接指针读取,eg:DWORD* 直接读的方式很不友好.
本次采取的是:完全是可视字符串处理.
虽然增大了那么一点的大小.但是相对于整体的安全和实现便利 来说是值得的.
MAGIC 6
MainVersion 1 ;主版本不匹配直接就返回错误了.
SecondaryVersion 1
PackageLength 10 ;10个十进制数字可以表达0xFFFFffff的范围
status 3 ;类型网络状态码:200,300之类所以占3个字符
MainType 8
SecondType 8
OtherData ...
前期设计是按位处理,但是在添加\0的时候感觉不爽.来回转换也不好.
直接用"."来补位空缺.
最终本格式设计可以快乐的集成在任一通信层上了.比如JS之类也方便实现.
难道是因为我被JS荼毒?
类设计的新探究
- 首次使用其他类指针作为构造函数参数
用于处理A依赖B的关系.顺带学了UML.
CFormat和CClient类遇到一个另人困惑的设计.
CClient依赖具体CFormat.原本打算用CClient继承CFormat,但是想了一下这样很不合理.
因为CFormat需要被继承多态处理,而CClient也需要被继承多态处理.
总不能人家对CFormat继承时被迫对CClient也继承?相对不合理.
所以当想到以上设计时,惊为天人.
知识所得
这就是不按照经典书籍走的后果,会遗漏很多细节.
以下两处所得也是本次遗漏的细节
std::string作为内存管理器
C++ 创建对象的三种方法之栈中分配
Test test1; //隐式调用,栈中分配 ,由操作系统进行内存的分配和管理
Test test2 = Test; //显式调用,栈中分配 ,由操作系统进行内存的分配和管理
Test *test3=new Test(); //堆中分配 ,由管理者进行内存的分配和管理,用完必须delete(),否则可能造成内存泄漏
之前以为栈中分配是不调用构造函数的,其实并不是.
这是在调试Socket通信的例子时发现的.
这一解决了我的疑问:std::string 为什么不new也能快乐的玩耍.
Socket代码也并不复杂
在网络层中尽早使用 Socket是有益的。
如何将一个类作为参数传入方法中
好吧,终究还是回来了.
当年难以理解和掌握的知识点,随着实践的增加一定更容易习得了.
使用的是类的引用:
事后诸葛
如果再次设计,我会:
- 保持类级单元测试
- 使用基本的Socket协议做通信
简单淳朴不浪费时间 - 时间预估上:
P2P协议类通信类:4000行:10天/2周+预研1周+测试1周=4周.
总结
本次设计颇有收获,不仅仅是具体语言更细节的使用上.
也在设计上增长了经验.在测试上增加了.
掌握一门语言从其经典书籍中确实能掌握很多细节.
之前认为这些细节并不必要.
即使已经3年工作经验的我依然遭遇了很多难题却浑然不知.
如果能伏下身潜心做一些细致的基础学习,会有很大提升.
基础学习 和 开源代码的研读相互推动,自己迈向更高的台阶.