1. 一文搞懂網(wǎng)絡(luò)庫(kù)的分層設(shè)計(jì)!

        共 9673字,需瀏覽 20分鐘

         ·

        2021-06-27 10:52

        ??關(guān)注“博文視點(diǎn)Broadview”,獲取更多書(shū)訊

        “對(duì)于計(jì)算機(jī)科學(xué)領(lǐng)域中的任何問(wèn)題,都可以通過(guò)增加一個(gè)間接的中間層來(lái)解決”這句話幾乎概括了計(jì)算機(jī)軟件體系結(jié)構(gòu)的設(shè)計(jì)要點(diǎn)。

        計(jì)算機(jī)軟件體系結(jié)構(gòu)從上到下都是按照嚴(yán)格的層次結(jié)構(gòu)設(shè)計(jì)的,不僅整個(gè)體系如此,體系里面的每個(gè)組件如OS本身、很多應(yīng)用程序、軟件系統(tǒng)甚至很多硬件結(jié)構(gòu)也如此。

        1. 網(wǎng)絡(luò)庫(kù)設(shè)計(jì)中的各個(gè)層

        常見(jiàn)的網(wǎng)絡(luò)通信庫(kù)根據(jù)功能也可以分成很多層。

        根據(jù)離業(yè)務(wù)的遠(yuǎn)近從上到下依次是Session層、Connection層、Channel層、Socket層

        其中Session層屬于業(yè)務(wù)層,Connection層、Channel層、Socket層屬于技術(shù)層,示意圖如下。

                                                    

        下面依次介紹各層的作用。

        ▊ Session層

        Session 層處于頂層,在設(shè)計(jì)上不屬于網(wǎng)絡(luò)框架本身,用于記錄各種業(yè)務(wù)狀態(tài)數(shù)據(jù)和處理各種業(yè)務(wù)邏輯。在業(yè)務(wù)邏輯處理完畢后,如果需要進(jìn)行網(wǎng)絡(luò)通信,則依賴Connection層進(jìn)行數(shù)據(jù)收發(fā)。

        例如,一個(gè)IM服務(wù)的Session類(lèi)可能有如下接口和成員數(shù)據(jù):

        typedef std::shared_ptr<TcpConnection> TcpConnectionPtr; typedef  const TcpConnectionPtr& CTcpConnectionPtrR;class ChatSession{public:    ChatSession(CTcpConnectionPtrR conn, int sessionid);    virtual ~ChatSession();
        int32_t GetSessionId() { return m_id; }
        int32_t GetUserId() { return m_userinfo.userid; }
        std::string GetUsername(){ return m_userinfo.username; }
        int32_t GetClientType() { return m_userinfo.clienttype; }
        int32_t GetUserStatus() { return m_userinfo.status; }
        int32_t GetUserClientType() { return m_userinfo.clienttype; }
        void SendUserStatusChangeMsg(int32_t userid, int type, int status = 0);
        private: //各個(gè)業(yè)務(wù)邏輯的處理方法 bool Process(CTcpConnectionPtrR conn, const char* inbuf, size_t buflength);
        void OnHeartbeatResponse(CTcpConnectionPtrR conn); void OnRegisterResponse(const std::string& data, CTcpConnectionPtrR conn); void OnLoginResponse(const std::string& data, CTcpConnectionPtrR conn); void OnGetFriendListResponse(CTcpConnectionPtrR conn); void OnFindUserResponse(const std::string& data, CTcpConnectionPtrR conn); void OnChangeUserStatusResponse(const std::string& data, CTcpConnectionPtrR conn);
        TcpConnectionPtr GetConnectionPtr(){ if (m_tmpConn.expired()) return NULL;
        return m_tmpConn.lock(); } //調(diào)用下層Connection層發(fā)送數(shù)據(jù)的方法 void Send(int32_t cmd, int32_t seq, const std::string& data); void Send(int32_t cmd, int32_t seq, const char* data, int32_t dataLength); void Send(const std::string& p); void Send(const char* p, int32_t length);
        private: int32_t m_id; //session id OnlineUserInfo m_userinfo; //該Session對(duì)應(yīng)的用戶信息 int32_t m_seq; //當(dāng)前Session數(shù)據(jù)包的序列號(hào) bool m_isLogin; //當(dāng)前Session對(duì)應(yīng)的用戶是否已登錄 //引用下層Connection層的成員變量 //但不管理TcpConnection對(duì)象的生命周期 std::weak_ptr<TcpConnection> m_tmpConn;};
        在以上代碼中除了業(yè)務(wù)狀態(tài)數(shù)據(jù)和業(yè)務(wù)接口,還有一個(gè)send系列的函數(shù),這個(gè)函數(shù)依賴Connection對(duì)象進(jìn)行數(shù)據(jù)收發(fā)。

        但是,Session對(duì)象并不擁有Connection對(duì)象,也就是說(shuō)Session對(duì)象不控制Connection對(duì)象的生命周期。這是因?yàn)殡m然Session對(duì)象的主動(dòng)銷(xiāo)毀(如收到非法的客戶端數(shù)據(jù)并關(guān)閉Session對(duì)象)會(huì)引起Connection對(duì)象的銷(xiāo)毀,但Connection對(duì)象本身也可能因?yàn)榫W(wǎng)絡(luò)出錯(cuò)等原因被銷(xiāo)毀,進(jìn)而引起Session對(duì)象被銷(xiāo)毀。

        因此,在上述類(lèi)接口描述中,ChatSession類(lèi)使用了一個(gè)std::weak_ptr來(lái)引用TCPConnection對(duì)象。這是需要注意的地方。

        Connection層

        Connection 層是技術(shù)層的頂層,每一路客戶端連接都對(duì)應(yīng)一個(gè) Connection 對(duì)象,該層一般用于記錄連接的各種狀態(tài)信息。

        常見(jiàn)的狀態(tài)信息有連接狀態(tài)、數(shù)據(jù)收發(fā)緩沖區(qū)信息、數(shù)據(jù)流量信息、本端和對(duì)端的地址和端口號(hào)信息等,同時(shí)提供對(duì)各種網(wǎng)絡(luò)事件的處理接口,這些接口或被本層自己使用,或被Session層使用。

        Connection持有一個(gè)Channel對(duì)象,而且掌管Channel對(duì)象的生命周期。

        一個(gè)Connection對(duì)象可以提供的接口和記錄的數(shù)據(jù)狀態(tài)如下:

        class TcpConnection{public:         TcpConnection(EventLoop* loop,                  const string& name,                  int sockfd,                  const InetAddress& localAddr,                  const InetAddress& peerAddr);    ~TcpConnection();
        const InetAddress& localAddress() const { return m_localAddr;} const InetAddress& peerAddress() const { return m_peerAddr; } bool connected() const { return m_state == kConnected; }
        void send(const void* message, int len); void send(const string& message); void send(Buffer* message); void shutdown();
        void forceClose();
        void setConnectionCallback(const ConnectionCallback& cb); void setMessageCallback(const MessageCallback& cb); void setCloseCallback(const CloseCallback& cb); void setErrorCallback(const ErrorCallback& cb);
        Buffer* getInputBuffer(); Buffer* getOutputBuffer();
        private: enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting }; void handleRead(Timestamp receiveTime); void handleWrite(); void handleClose(); void handleError(); void sendInLoop(const string& message); void sendInLoop(const void* message, size_t len); void shutdownInLoop(); void forceCloseInLoop(); void setState(StateE s) { m_state = s; }
        private: //連接狀態(tài)信息 StateE m_state; //引用Channel對(duì)象 std::shared_ptr<Channel> m_spChannel; //本端的地址信息 const InetAddress m_localAddr; //對(duì)端的地址信息 const InetAddress m_peerAddr;
        ConnectionCallback m_connectionCallback; MessageCallback m_messageCallback; CloseCallback m_closeCallback; ErrorCallback m_errorCallback;
        //接收緩沖區(qū) Buffer m_inputBuffer; //發(fā)送緩沖區(qū) Buffer m_outputBuffer; //流量統(tǒng)計(jì)類(lèi) CFlowStatistics m_flowStatistics;};

        Channel層

        Channel層一般持有一個(gè)socket句柄,是實(shí)際進(jìn)行數(shù)據(jù)收發(fā)的地方,因而一個(gè)Channel對(duì)象會(huì)記錄當(dāng)前需要監(jiān)聽(tīng)的各種網(wǎng)絡(luò)事件(讀寫(xiě)和出錯(cuò)事件)的狀態(tài),同時(shí)提供對(duì)這些事件狀態(tài)的查詢和增刪改接口。

        在部分網(wǎng)絡(luò)庫(kù)的實(shí)現(xiàn)中,Channel對(duì)象管理著socket對(duì)象的生命周期,因此Channel對(duì)象需要提供創(chuàng)建和關(guān)閉socket對(duì)象的接口;而在另外一些網(wǎng)絡(luò)庫(kù)的實(shí)現(xiàn)中由Connection對(duì)象直接管理socket對(duì)象的生命周期,也就是說(shuō)沒(méi)有Channel層。

        所以,Channel層不是必需的。

        由于TCP收發(fā)數(shù)據(jù)是全雙工的(收發(fā)走獨(dú)立的通道,互不影響),所以收發(fā)邏輯一般不會(huì)有依賴關(guān)系,但收發(fā)操作一般會(huì)被放在同一個(gè)線程中進(jìn)行,這樣做的目的是防止在收發(fā)過(guò)程中改變socket狀態(tài)時(shí),對(duì)另一個(gè)操作產(chǎn)生影響。假設(shè)收發(fā)操作分別使用一個(gè)線程,在一個(gè)線程中收數(shù)據(jù)時(shí)因出錯(cuò)而關(guān)閉了連接,但另一個(gè)線程可能正在發(fā)送數(shù)據(jù),這樣就會(huì)出問(wèn)題。

        一個(gè)Channel對(duì)象提供的函數(shù)接口和狀態(tài)數(shù)據(jù)如下:

        class Channel{public:    Channel(EventLoop* loop, int fd);    ~Channel();
        void handleEvent(Timestamp receiveTime);
        int fd() const; int events() const; void setRevents(int revt); void addRevents(int revt); void removeEvents(); bool isNoneEvent() const;
        bool enableReading(); bool disableReading(); bool enableWriting(); bool disableWriting(); bool disableAll();
        bool isWriting() const;
        private: const int m_fd; //當(dāng)前需要檢測(cè)的事件 int m_events; //處理后的事件    int                m_revents;


        Socket層

        嚴(yán)格來(lái)說(shuō),并不存在Socket層,這一層通常只是對(duì)常用的socket函數(shù)進(jìn)行封裝,例如屏蔽不同操作系統(tǒng)操作socket函數(shù)的差異性來(lái)實(shí)現(xiàn)跨平臺(tái),方便上層使用。

        如果存在 Channel 層,則 Socket 層的上層就是 Channel 層;如果不存在Channel層,則Socket層的上層就是Connection層。

        Socket層也不是必需的,因此很多網(wǎng)絡(luò)庫(kù)都沒(méi)有Socket層。

        下面是某Socket層對(duì)常用socket函數(shù)的功能進(jìn)行一層簡(jiǎn)單封裝的接口示例:

        namespace sockets{#ifdef WIN32#else    typedef int SOCKET; #endif
        SOCKET createOrDie(); SOCKET createNonblockingOrDie();
        void setNonBlockAndCloseOnExec(SOCKET sockfd);
        void setReuseAddr(SOCKET sockfd, bool on); void setReusePort(SOCKET sockfd, bool on);
        int connect(SOCKET sockfd, const struct sockaddr_in& addr); void bindOrDie(SOCKET sockfd, const struct sockaddr_in& addr); void listenOrDie(SOCKET sockfd); int accept(SOCKET sockfd, struct sockaddr_in* addr); int32_t read(SOCKET sockfd, void *buf, int32_t count);#ifndef WIN32 ssize_t readv(SOCKET sockfd, const struct iovec *iov, int iovcnt);#endif int32_t write(SOCKET sockfd, const void *buf, int32_t count); void close(SOCKET sockfd); void shutdownWrite(SOCKET sockfd);
        void toIpPort(char* buf, size_t size, const struct sockaddr_in& addr); void toIp(char* buf, size_t size, const struct sockaddr_in& addr); void fromIpPort(const char* ip, uint16_t port, struct sockaddr_in* addr);
        int getSocketError(SOCKET sockfd);
        struct sockaddr_in getLocalAddr(SOCKET sockfd); struct sockaddr_in getPeerAddr(SOCKET sockfd);}

        在實(shí)際開(kāi)發(fā)中,有的服務(wù)在設(shè)計(jì)網(wǎng)絡(luò)通信模塊時(shí)會(huì)將Connection層與Channel層合并成一層,當(dāng)然,這取決于業(yè)務(wù)的復(fù)雜程度。所以在某些服務(wù)代碼中只看到 Connection 對(duì)象或者Channel對(duì)象時(shí),請(qǐng)不要覺(jué)得奇怪。

        另外,對(duì)于服務(wù)端程序,拋開(kāi)業(yè)務(wù)本身,從技術(shù)層面上來(lái)說(shuō),我們需要一個(gè) Server對(duì)象(如TcpServer)來(lái)集中管理多個(gè)Connection對(duì)象,這也是網(wǎng)絡(luò)庫(kù)自身需要處理好的部分。一個(gè)TcpServer對(duì)象可能需要提供如下函數(shù)接口和狀態(tài)數(shù)據(jù):

        class TcpServer{public:    typedef std::function<void(EventLoop*)> ThreadInitCallback;    enum Option    {        kNoReusePort,        kReusePort,    };
        TcpServer(EventLoop* loop, const InetAddress& listenAddr, const std::string& nameArg, Option option = kReusePort); ~TcpServer();
        void addConnection(int sockfd, const InetAddress& peerAddr); void removeConnection(const TcpConnection& conn);
        typedef std::map<string, TcpConnectionPtr> ConnectionMap;
        private: int m_nextConnId; ConnectionMap m_connections;};

        對(duì)于客戶端程序來(lái)說(shuō),同樣可以設(shè)計(jì)出一個(gè)TCPClient對(duì)象來(lái)管理各個(gè)Connector(連接器對(duì)象)。
        Session對(duì)象雖然與Connection對(duì)象一一對(duì)應(yīng),但在業(yè)務(wù)層(網(wǎng)絡(luò)通信框架之外)需要有專(zhuān)門(mén)的類(lèi)來(lái)管理這些 Session 對(duì)象的生命周期,我們一般把這個(gè)專(zhuān)門(mén)的類(lèi)稱為SessionManager或者SessionFactory。
        2. 將Session進(jìn)一步分層

        不同的服務(wù),其業(yè)務(wù)可能千差萬(wàn)別,在實(shí)際開(kāi)發(fā)中,我們可以根據(jù)業(yè)務(wù)場(chǎng)景將Session層進(jìn)一步拆分成多個(gè)層,使每一層都專(zhuān)注于自己的業(yè)務(wù)邏輯。

        例如,假設(shè)現(xiàn)在有一個(gè)需要支持聊天消息壓縮的即時(shí)通信服務(wù),我們可以將Session劃分為三個(gè)層,從上到下依次是ChatSession、CompressionSession和TcpSession。ChatSession負(fù)責(zé)處理聊天業(yè)務(wù)本身,CompressSession 負(fù)責(zé)數(shù)據(jù)的解壓縮,TcpSession負(fù)責(zé)將數(shù)據(jù)加工成網(wǎng)絡(luò)層需要的格式或者將網(wǎng)絡(luò)層發(fā)送的數(shù)據(jù)還原成業(yè)務(wù)需要的格式(如數(shù)據(jù)裝包和解包),示意圖如下。

        3. 連接信息與EventLoop/Thread
        綜合各層對(duì)象,一個(gè)socket(fd)只對(duì)應(yīng)一個(gè)Channel對(duì)象、一個(gè)Connection對(duì)象及一個(gè)Session對(duì)象,這組對(duì)象構(gòu)成了一路連接信息(技術(shù)加業(yè)務(wù)上的)。

        結(jié)合前面介紹的one thread one loop思想,每一路連接信息都只能屬于一個(gè)loop,也就是說(shuō)只屬于某個(gè)線程;但是反過(guò)來(lái),一個(gè) loop 或者一個(gè)線程可以同時(shí)擁有多個(gè)連接信息,這就保證了我們只會(huì)在同一個(gè)線程里面處理特定的socket收發(fā)事件。

        想了解更多關(guān)于C++服務(wù)器開(kāi)發(fā)的內(nèi)容,推薦閱讀《C++服務(wù)器開(kāi)發(fā)精髓》一書(shū)!



        ▊《C++服務(wù)器開(kāi)發(fā)精髓

        張遠(yuǎn)龍 著


        • 從操作系統(tǒng)原理角度講解C++服務(wù)器開(kāi)發(fā)技術(shù)棧

        • 內(nèi)容詳盡細(xì)致、版本新

        • 重磅級(jí)C++服務(wù)器開(kāi)發(fā)紅寶書(shū)


        本書(shū)詳細(xì)講解如何掌握C++服務(wù)器開(kāi)發(fā)技術(shù),以及如何成為合格的C++開(kāi)發(fā)者,秉承的思想是,通過(guò)掌握技術(shù)原理,可以輕松制造“輪子”,靈活設(shè)計(jì)出優(yōu)雅、魯棒的服務(wù),并快速學(xué)習(xí)新技術(shù)。

        無(wú)論是對(duì)于C/C++開(kāi)發(fā)者、計(jì)算機(jī)專(zhuān)業(yè)的學(xué)生,還是對(duì)于想了解操作系統(tǒng)原理的讀者,本書(shū)都極具參考價(jià)值。


        (京東限時(shí)活動(dòng),快快掃碼搶購(gòu)吧?。?/span>





        如果喜歡本文
        歡迎 在看留言分享至朋友圈 三連


         熱文推薦  





        ▼點(diǎn)擊閱讀原文,查看本書(shū)詳情~
        瀏覽 27
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 大插香蕉视频 | 青青草二区 | 18做爰免费视频网站 | 屁股撅起来c爽你 | 国产精品小电影 |