从文电传输说起
一、故事背景
最近公司对我等新进员工进行考核,形式就是给定一个任务书,完成该软件,语言C++。此次完成的软件大致是通过以太网进行文电传输的东西,其实通俗讲就是类似于飞秋或QQ这样的聊天软件,可以成员管理,广播信息,P2P聊,群聊,文件发送这些(当然,这与相关领域上的文电传输是有区别的)。
考核规定可以在公司做(基础库封装得足够成熟),也可以在家做(没有这么好的库,一切从打地基开始)。想想大致依赖的主要基础操作:网络部分收发、二进制数据流的处理。于是,我决定,秉承我一贯的风格——“抓住一切可以学习的机会”,在家做,其实我就是不想太依赖公司的库,不然以后就对技术只知其然啦(手动捂脸)。
于是,轰轰烈烈的学习旅程开始了,当然,注意时间是在十天内完成啊!
二、故事开始
Question one:我用什么“媲美”公司的基础库呢?
Answer:当然是C++社区里强大的开源库:Boost。毕竟Boost C++ Library是一组基于 C++标准的现代库,平台独立,其支持大多数编译器。要增强C++项目的生产力,除了C++标准以外,Boost C++库是不错的选择。毕竟好几个库都已经进入C++标准,今后还会有更多的库被选中。
学习一门语言,不仅是语法,技巧,还应融入它的生态。
所以,Boost Library是我的不二之选。
Question two:我要封装哪些基础接口呢?
Answer:考虑到是以太网的数据传输,自然少不了网络socket的封装,那socket编程的话,需要依赖一些系统的基本操作,比如线程,定时器,事件机制。
至于对于这些接口,我并不想直接使用Boost的,还是想封装一层,方便应用层面的开发,怎样定接口呢,对于我这还是新手级别的程序员,还是参考参考公司的吧,毕竟是多年的积累。
好的接口让开发事半功倍哦,当然开发好接口也需要相当的经验。
线程、定时器部分:Boost.Thread
事件机制:Boost.Thread + Boost.Signals2
网络编程:Boost.Asio
其他:Boost.Uuid
(当然还有一些必须使用的,如:Boost.Bind)
嗯…一周前三四天下班后就回家搞,然后就是这几个.cpp和.h了。然后下面是个UdpSocket的封装,搞了一个晚上。由于时间限制,我只能封装UdpSocket了。
By the way,下班后回来一门心思地搞,整个人都在亢奋之中,根本停不下来(还好…不加班)
Question three:我这个软件到底要做多“大”呢?
任务书其实说得挺大的,因为内容是用公司某大项目的文档抄下来的(真是直接了,幸好负责人说不懂就照着飞秋或QQ做。)
Answer:
1 从功能上:成员管理,广播信息,P2P聊,群聊,文件发送,分片传输,文件重传要实现,至于安全,保密传输,掂量着短期搞不定啊。
2.从时间上:十天(还得排开上班时间),我要完成系统基础操作做,数据传输、逻辑操作、界面,必须有侧重点了,不能都抓,而我更倾向于前两者。
3.从架构上:这类项目可大可小,看看QQ、微信这类大项目的架构就知道可以有多大了,而想想负责人说其实只需要实现消息收发就可以了,就知道这个项目可真是小了。我现在的能力来讲(毕业几个月的小小个),我做不大,但我要尽我所能做,做好,做规范。
尽吾志者也而不能至,可以无悔也,其孰能讥之乎?
于是,我想到了中间件,作为平台与通信的连接,对上提供特定消息收发的处理,对下封装网络部分的处理,这儿也是考虑到了扩展性,以后TCP封装了后,对于平台的业务逻辑讲,是没关系的。
最终我确认的架构就是这样,采用了订阅者-发布者模式。
接下来我的重点是要讲讲文件分片传输部分(最下面那个PacketManager就是我今天讲故事的导火索了)。
三、进入正题(文件分片)
其实就是对Binary Buffer的分片处理,而为什么要分片呢,因为网络上传输是有限制的(后面具体叨叨),不能一大块数据直接就扔过去,其本质是网络协议就定了的,当然也逃不了物理的限制,所以我们必须分片(一包多大,后面叨叨)。
从应用层面理解
这就是一个拆包、组包的活儿,那么一块Buffer分包后,需要知道每个包的序列号,Buffer的总包数,包各自的Length及内容。所以我的PaketHandler就做了这么个拆组包的工作。
#pragma once
#include <map>
#include "DataTransferHandlerExport.h"
#include "Uuid.h"
#include "DataTransferMacro.h"
class DATATRANSFERHANDLER_EXPORT CPieceData
{
friend class CPacketHandler;
public:
unsigned char* DataBuffer(void) const;
int DataLength(void) const;
unsigned short CurrentSequence(void) const;
private:
CPieceData(void);
~CPieceData(void);
CPieceData(const CPieceData& rPieceData);
CPieceData operator =(const CPieceData& rPieceData);
private:
void AssignBuffer(unsigned char* pBuffer, int nLength);
void ClearData(void);
private:
unsigned short m_usCurrentSequence; // 2Byte << A piece of Packet's Current Sequence
unsigned char* m_pDataBuffer; // N << The Follow Data's Binary Buffer
int m_nDataLength; //<< The Follow Data's Length
};
class DATATRANSFERHANDLER_EXPORT CPacketHandler
{
public:
enum
{
Unit_Net_Data_Defualt_Length = 1024, // Equal 1KB (A NetPieceData buffer's Max Length)
Msg_Head_Fixed_Size = 16 + 2 + 2, // Head's Length(Fixed)
};
// Map Parameter: CurrentSequence, NetPieceData
typedef std::map<unsigned short, CPieceData*> MapPieceData;
CPacketHandler(int nUnitLength = Unit_Net_Data_Defualt_Length);
~CPacketHandler(void);
CPacketHandler(const CPacketHandler& rPacketHandler);
CPacketHandler& operator =(const CPacketHandler& rPacketHandler);
bool IsDividedPacket(void) const;
int CountTotalBufferLength(void) const;
CUuid PacketUuid(void) const;
int CountCurrentSequences() const;
unsigned short TotalSequeceNum(void) const;
int UnitLength(void) const;
// Record Network Information
DataTransfer::StrucNetworkAddress RemoteAddress(void) const;
void SetRemoteAddress(const DataTransfer::StrucNetworkAddress& rStructRemoteAddr);
// Divide
void ResetOffset(); // Re cout Takeout Number
CPieceData* TakeoutPieceData(); // if NULL Take Out Over , Divided Data Finish
bool DivideRawDataToPacket(unsigned char* pRawDataBuffer, int nRawTotalLength);
// Combine
bool IsCombinCompleted(void) const;
static CUuid ParseNetDataBufferUuid(const unsigned char* pNetDataBuffer, int nNetDataLength);
bool CombineNetDataBuffer(unsigned char* pNetDataBuffer, int nNetDataLength);
bool ExportNetToRawDataBuffer(unsigned char** pBufferAddr, int& nTotalLength);
private:
void ClearAllPieceData(void);
// Divide
CPieceData* CreateRawPieceData(unsigned char* pRawDataBuffer, int nRawDataLength);
bool EncaseRawPieceData(CPieceData& rRawPieceData);
private:
CUuid m_PacketUuid; // 16byte << The Packet's Uuid
unsigned short m_usTotalSequenceNum; // 2 byte << All pieces Sequence Number Count
MapPieceData m_mapPieceData; //<< The Container of All PieceData
bool m_bDividedPacket; //<< Judge The Packet was Divided or Combined
const int m_nUnitLength; //<< A piece of Data's Fixed Length (Divide Use)
DataTransfer::StrucNetworkAddress m_StructRemoteAddr; //<< For Oriented Remote Endpoint
int m_nDivideRecord; // Record Handled Divide Pieces
unsigned short m_usCurCombinRecord; // Record Current Combin Handle Pieces sequence Num
};
/*
Divided Packet: PieceData's Buferr = Head + RawDataBuffer;
Comes from User Level
Combined Packet: PieceData's Buffer = RawDataBuffer;
Comes from Net transfer
*/
而对于应用层面来说,我得支持同时有多块Buffer在发也可能在收(比如同时给两个成员发文件,或同时收多个文件),这就需要对每个包有ID确认了。我使用了Uuid来区别哪个包属于哪块Buffer的。然后用队列,开启收发线程,分包应用层面给的Buffer发送,收到包组合后给应用层面。这就是我的PacketManager干的事儿。(用了观察者模式)
#pragma once
#include "DataTransferMacro.h"
#include "TaskAttach.h"
class CMsgProcObject;
class DATATRANSFERHANDLER_EXPORT CPacketManager
{
public:
CPacketManager(void);
~CPacketManager(void);
void AttachObserver(CMsgProcObject* pObserver);
void DetachObserver();
bool HandleRawDataBufferForSendReady(
const DataTransfer::StrucNetworkAddress& rStructRemoteAddr, // Remote Address For Find Send Endpoint
unsigned char* pRawDataBuffer, int nRawDataLength); // Raw Buffer parameters
bool HandleNetDataBufferForRecvReady(
const DataTransfer::StrucNetworkAddress& rStructRemoteAddr, // Remote Address For Record Sender Endpoint
unsigned char* pNetDatBuffer, int nNetDataLength); // Net Buffer parameters
void ShutdownSendingNetBuffer(const std::string& rStrShutDownSenderIp);
void ShutdownRecievingRawBuffer(const std::string& rStrShutdonwRecverIp);
private:
CPacketManager(const CPacketHandler* rPacketManager) {}
CPacketManager& operator =(const CPacketManager& rPacketManager) { return *this; }
void OnRecvPacketManage();
void OnSendPacketManage();
private:
CMsgProcObject* m_pObserver;
boost::mutex m_MutexRecv;
DataTransfer::ListPacketHandler m_listRecvPacketHandler;
CTaskAttach m_TaskRecv;
boost::mutex m_MutexSend;
DataTransfer::ListPacketHandler m_listSendPacketHandler;
CTaskAttach m_TaskSend;
};
以上就是我对数据分片的代码处理,到当时写完的那个时候,我对分片的认识还很模糊,只知道得这样分,一片是1024*10,然而都没有具体测试过上限。所以下面我对这个进行了一些思考。
还听下篇故事继续走。