计算机界有一句经典名言“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决”Any problem in computer science can be solved by anther layer of indirection.。这句话几乎概括了计算机系统软件体系结构的设计要点整个体系结构从上到下都是按照严格的层次结构设计的不仅是计算机系统软件整个体系是这样的体系里面的每个组件比如 OS 本身很多应用程序、软件系统甚至很多硬件结构都是按照这种层次的结构组织和设计的。网络库设计中的各个层在常见的网络通信库中根据功能也可以分成很多层根据离业务的远近从上到下依次是Session 层该层处于最上层在设计上不属于网络框架本身的部分其作用是记录各种业务状态数据和处理各种业务逻辑。业务逻辑处理完毕后如果需要进行网络通信则依赖 Connection 层进行数据收发。例如一个 session 类可能有如下接口和成员数据class ChatSession { public: ChatSession(const std::shared_ptrTcpConnection 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; } std::string GetNickname() { return m_userinfo.nickname; } std::string GetPassword() { return m_userinfo.password; } 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: bool Process(const std::shared_ptrTcpConnection conn, const char* inbuf, size_t buflength); void OnHeartbeatResponse(const std::shared_ptrTcpConnection conn); void OnRegisterResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnLoginResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnGetFriendListResponse(const std::shared_ptrTcpConnection conn); void OnFindUserResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnChangeUserStatusResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnOperateFriendResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnAddGroupResponse(int32_t groupId, const std::shared_ptrTcpConnection conn); void OnUpdateUserInfoResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnModifyPasswordResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnCreateGroupResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnGetGroupMembersResponse(const std::string data, const std::shared_ptrTcpConnection conn); void OnChatResponse(int32_t targetid, const std::string data, const std::shared_ptrTcpConnection conn); void OnMultiChatResponse(const std::string targets, const std::string data, const std::shared_ptrTcpConnection conn); void OnScreenshotResponse(int32_t targetid, const std::string bmpHeader, const std::string bmpData, const std::shared_ptrTcpConnection conn); void OnUpdateTeamInfoResponse(int32_t operationType, const std::string newTeamName, const std::string oldTeamName, const std::shared_ptrTcpConnection con); void OnModifyMarknameResponse(int32_t friendid, const std::string newmarkname, const std::shared_ptrTcpConnection conn); void OnMoveFriendToOtherTeamResponse(int32_t friendid, const std::string newteamname, const std::string oldteamname, const std::shared_ptrTcpConnection conn); void DeleteFriend(const std::shared_ptrTcpConnection conn, int32_t friendid); std::shared_ptrTcpConnection GetConnectionPtr() { if (tmpConn_.expired()) return NULL; return tmpConn_.lock(); } 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; int32_t m_seq; //当前Session数据包序列号 bool m_isLogin; //当前Session对应的用户是否已经登录 //TcpSession引用TcpConnection类必须是弱指针因为TcpConnection可能会因网络出错自己销毁此时TcpSession应该也要销毁 std::weak_ptrTcpConnection tmpConn_; };上述代码中除了业务状态数据和业务接口以外还有一个Send系列的函数这个函数依赖 Connection 对象进行数据收发。但是需要注意的是 Session 对象并不拥有 Connection 对象也就是说 Session 对象不控制 Connection 对象的生命周期这是因为虽然 Session 对象的主动销毁如收到客户端不合理的数据关闭 Session 对象会引起 Connection 对象的销毁但 Connection 对象本身也可能因为网络出错等原因被销毁进而引起 Session 对象被销毁。因此上述类接口描述中ChatSession 类使用了一个弱指针weak_ptr来引用 TCPConnection 对象。这是需要注意的地方。Connection 层该层是网络框架设计中最上面的一层技术层的最上层每一路客户端连接对应一个 Connection 对象。一般用于记录该路连接的各种状态常见的状态信息有如连接状态、数据收发缓冲区信息、数据流量记录状态、本端和对端地址和端口号信息等同时也提供对各种网络事件的处理接口这些接口或被本层自己使用或被 Session 层使用。Connection 持有一个 Channel 对象且掌管着 Channel 对象的生命周期。一个 Connection 对象可能提供的接口和记录的数据状态如下class TcpConnection { public: TcpConnection(EventLoop* loop, const string name, int sockfd, const InetAddress localAddr, const InetAddress peerAddr); ~TcpConnection(); const InetAddress localAddress() const { return localAddr_; } const InetAddress peerAddress() const { return peerAddr_; } bool connected() const { return state_ kConnected; } void send(const void* message, int len); void send(const string message); void send(Buffer* message); // this one will swap data void shutdown(); // NOT thread safe, no simultaneous calling void forceClose(); void setConnectionCallback(const ConnectionCallback cb) { connectionCallback_ cb; } void setMessageCallback(const MessageCallback cb) { messageCallback_ cb; } void setCloseCallback(const CloseCallback cb) { closeCallback_ cb; } void setErrorCallback(const ErrorCallback cb) { errorCallback_ cb; } Buffer* inputBuffer() { return inputBuffer_; } Buffer* outputBuffer() { return outputBuffer_; } 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) { state_ s; } private: //连接状态信息 StateE state_; //引用Channel对象 std::shared_ptrChannel channel_; //本端的地址信息 const InetAddress localAddr_; //对端的地址信息 const InetAddress peerAddr_; ConnectionCallback connectionCallback_; MessageCallback messageCallback_; CloseCallback closeCallback_; ErrorCallback errorCallback_; //接收缓冲区 Buffer inputBuffer_; //发送缓冲区 Buffer outputBuffer_; //流量统计类 CFlowStatistics flowStatistics; };Channel 层Channel 层一般持有一个 socketLinux 下也叫 fd是实际进行数据收发的地方因而一个 Channel 对象会记录当前需要监听的各种网络事件读写和出错事件状态同时提供对这些事件的状态的判断和增删改的接口。在部分网络库的实现中Channel 对象管理着 socket 对象的生命周期而另外一些库的实现则由 Connection 对象来管理 socket 的生命周期。如果实现是前者则 Channel 对象也提供对 socket 进行创建和关闭的接口。由于 TCP 收发数据是全双工的收发走独立的通道互不影响收发逻辑一般不会有什么依赖关系但收发操作一般会在同一个线程中进行操作这样的目的是为了防止收或发的过程中改变了 socket 的状态对另外一个操作产生影响。例如在一个线程中收数据时出错关闭了连接另外一个线程正在发送数据这该情何以堪呢。一个 Channel 对象提供的函数接口和状态数据如下所示class Channel { public: Channel(EventLoop* loop, int fd); ~Channel(); void handleEvent(Timestamp receiveTime); int fd() const; int events() const; void set_revents(int revt); void add_revents(int revt); void remove_events(); bool isNoneEvent() const; bool enableReading(); bool disableReading(); bool enableWriting(); bool disableWriting(); bool disableAll(); bool isWriting() const { return events_ kWriteEvent; } private: const int fd_; //当前需要检测的事件 int events_; //处理后的事件 int revents_; };socket 层严格意义上说并不存在所谓的 socket 层这一层只是对常用的 socket 函数进行了一层封装例如封装实现跨平台方便上层Channel 层或 Connection 层使用。很多网络库没有这一层。例如下面就是对常用的 socket 函数的功能做了一层简单的封装namespace sockets { 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); const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr); struct sockaddr* sockaddr_cast(struct sockaddr_in* addr); const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr); struct sockaddr_in* sockaddr_in_cast(struct sockaddr* addr); struct sockaddr_in getLocalAddr(SOCKET sockfd); struct sockaddr_in getPeerAddr(SOCKET sockfd); }在实际实践中有的服务设计网络通信模块时会将 Connection 对象与 Channel 对象合并成一个对象这取决于当前业务需要记录的技术上的数据的多少和技术上处理这些数据的复杂性高低。所以在某些服务代码中只看到 Connection 对象或者 Channel 对象请不要觉得奇怪。另外对于服务器端程序抛开业务本身在技术层面上我们需要管理许多的 Connection 对象一般会使用一个叫 Server 对象如 TcpServer来集中管理这是网络库本身需要处理好的部分。例如一个 TcpServer 对象可能提供的函数接口和状态数据如下class TcpServer { public: typedef std::functionvoid(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::mapstring, TcpConnectionPtr ConnectionMap; private: int nextConnId_; ConnectionMap connections_; };对于客户端程序同样也可以设计出一个 TCPClient 对象管理各个 Connector连接器对象。对于 Session 对象来说虽然与 Connection 对象一一对应但在业务层网络通信框架之外需要有专门的类去管理这些 Session 对象的生命周期我们一般这个专门的类称之为 SessionManager 或者 SessionFactory。Session 进一步分层不同的服务其业务可能千差万别实际开发中我们可能根据业务场景将 Session 层进一步拆分成多个层每一层专注于其自己的业务逻辑。例如对于即时聊天服务器我们可以将 Session 划分为两层ChatSession、CompressionSession 和 TcpSessionChatSession 专注于聊天业务本身的处理CompressSession 负责数据的解压缩TcpSession 用于将数据加工成网络层需要的格式或者将网络层送上来的数据还原成业务需要的格式如数据装包和解包。示意图如下连接信息与 EventLoop/Thread 的对应关系综合各层对象一个 socketfd只会对应一个channel 对象、一个 Connection 对象以及一个 Session 对象这一组对象构成了一路连接信息技术上加业务上的。结合我们前面介绍了one thread one loop思想每一路连接信息只能属于一个 loop也就是只会属于某一个线程但是反过来一个 loop 或者一个线程可以同时拥有多个连接信息。这就保证了我们只会在同一个线程里面去是处理特定的 socket 的收发事件。