从文电传输说起(一)

从文电传输说起

一、故事背景

最近公司对我等新进员工进行考核,形式就是给定一个任务书,完成该软件,语言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)

基于Boost的基础库封装
嗯…一周前三四天下班后就回家搞,然后就是这几个.cpp和.h了。然后下面是个UdpSocket的封装,搞了一个晚上。由于时间限制,我只能封装UdpSocket了。
基于Boost的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,然而都没有具体测试过上限。所以下面我对这个进行了一些思考。
还听下篇故事继续走。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值