<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文搞懂網(wǎng)絡(luò)庫的分層設(shè)計

          共 10471字,需瀏覽 21分鐘

           ·

          2021-08-10 11:53

          關(guān)注「開源Linux」,選擇“設(shè)為星標(biāo)”
          回復(fù)「學(xué)習(xí)」,有我為您特別篩選的學(xué)習(xí)資料~


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

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

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

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

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

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

                                                      

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

          ▊ Session層

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

          例如,一個IM服務(wù)的Session類可能有如下接口和成員數(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: //各個業(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對應(yīng)的用戶信息 int32_t m_seq; //當(dāng)前Session數(shù)據(jù)包的序列號 bool m_isLogin; //當(dāng)前Session對應(yīng)的用戶是否已登錄 //引用下層Connection層的成員變量 //但不管理TcpConnection對象的生命周期 std::weak_ptr<TcpConnection> m_tmpConn;};
          在以上代碼中除了業(yè)務(wù)狀態(tài)數(shù)據(jù)和業(yè)務(wù)接口,還有一個send系列的函數(shù),這個函數(shù)依賴Connection對象進(jìn)行數(shù)據(jù)收發(fā)。

          但是,Session對象并不擁有Connection對象,也就是說Session對象不控制Connection對象的生命周期。這是因為雖然Session對象的主動銷毀(如收到非法的客戶端數(shù)據(jù)并關(guān)閉Session對象)會引起Connection對象的銷毀,但Connection對象本身也可能因為網(wǎng)絡(luò)出錯等原因被銷毀,進(jìn)而引起Session對象被銷毀。

          因此,在上述類接口描述中,ChatSession類使用了一個std::weak_ptr來引用TCPConnection對象。這是需要注意的地方。

          Connection層

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

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

          Connection持有一個Channel對象,而且掌管Channel對象的生命周期。

          一個Connection對象可以提供的接口和記錄的數(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對象 std::shared_ptr<Channel> m_spChannel; //本端的地址信息 const InetAddress m_localAddr; //對端的地址信息 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)計類 CFlowStatistics m_flowStatistics;};

          Channel層

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

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

          所以,Channel層不是必需的。

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

          一個Channel對象提供的函數(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)前需要檢測的事件 int m_events; //處理后的事件    int                m_revents;


          Socket層

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

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

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

          下面是某Socket層對常用socket函數(shù)的功能進(jì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);}

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

          另外,對于服務(wù)端程序,拋開業(yè)務(wù)本身,從技術(shù)層面上來說,我們需要一個 Server對象(如TcpServer)來集中管理多個Connection對象,這也是網(wǎng)絡(luò)庫自身需要處理好的部分。一個TcpServer對象可能需要提供如下函數(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;};

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

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

          例如,假設(shè)現(xiàn)在有一個需要支持聊天消息壓縮的即時通信服務(wù),我們可以將Session劃分為三個層,從上到下依次是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
          綜合各層對象,一個socket(fd)只對應(yīng)一個Channel對象、一個Connection對象及一個Session對象,這組對象構(gòu)成了一路連接信息(技術(shù)加業(yè)務(wù)上的)。

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


          往期推薦



          存儲系統(tǒng)基礎(chǔ)知識介紹

          深入研究Docker聯(lián)合文件系統(tǒng)

          Linux交換分區(qū)要點(diǎn)匯總

          如何優(yōu)雅的在 Linux 下開機(jī)自動重啟腳本

          今年云計算的主要趨勢,混合云/邊緣計算/Serverless

          超全面的 Kubernetes 容器網(wǎng)絡(luò)技能,運(yùn)維看后都說好

          網(wǎng)絡(luò)常見的 9 大命令,非常實用!

          數(shù)據(jù)中心為什么需要大二層網(wǎng)絡(luò)

          超詳細(xì)!一文詳解容器網(wǎng)絡(luò)發(fā)展

          推薦 | 網(wǎng)絡(luò)編程面試題

          Docker容器網(wǎng)絡(luò)通信原理分析

          容器網(wǎng)絡(luò)其實并不難

          一文讀懂 Kubernetes 容器網(wǎng)絡(luò)

          入網(wǎng)指南:一文讀懂你身邊的網(wǎng)絡(luò)

          關(guān)注「開源Linux」加星標(biāo),提升IT技能


          點(diǎn)個在看少個 bug ??

          瀏覽 70
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青操逼视频在线观看 | 四房五月天婷婷丁香在线 | 淫荡在线 | 亚洲五月天激情 | A级黄色网 |