网络muduo库的实现(1)
网络库实现的核心目标muduo 网络库成功的原因将解决的问题控制在一个范围内,不追求大而全。
仿写 muduo 网络库
网络库实现的核心目标
- 线程安全,支持多核多线程,实现高并发的连接请求和低延时的客户端请求。
- 不考虑可移植性,不跨平台,只支持 Linux,不支持 Windows。
- 主要支持 x86-64,兼顾 IA32。
- 不支持 UDP,只支持 TCP。
- 不支持 IPv6,只支持 IPv4。
- 不考虑广域网应用,只考虑局域网。
- 不考虑公网,只考虑内网。不为安全性做特别的增强。
- 只支持一种使用模式:非阻塞 IO+one event loop per thread,不支持阻塞 IO。
- API 简单易用,只暴露具体类和标准库里的类。API 不使用 non-trivial templates,也不使用虚函数。
- 只满足常用需求的 90%,不面面俱到,必要的时候以 app 来适应 lib。
- 只做 libray (库),不做成 framework (构架)
muduo 网络库成功的原因将解决的问题控制在一个范围内,不追求大而全。
1. 第一版本的需求
在客户端获取日志信息,将日志信息发送到服务端,服务端接受日志信息,再接着将日志信息写入文件。代码最终的要求是:
服务器端程序:
- 指定服务器 IP 地址和 Port 端口号;
- 定义服务器对象,执行
bind绑定和listen监听操作; - 接受客户端连接(
accept); - 与客户端进行数据读写;
- 关闭客户端的连接。
客户端程序:
- 指定连接的服务器 IP 地址和 port 端口号;
- 定义客户端对象;
- 发起与服务器的连接;
- 与服务器进行数据读写;
- 关闭连接。
2. 需求分析
使用 C/S 模型实现日志信息在网络中的发送。
2.1 TCP 客户端和 TCP 服务器的工作流程

客户端工作流程
设置地址信息 ==> 创建 socket ==> 发起连接(connect) ==> 读写数据(read/write) ==> 关闭连接(close)
服务器工作流程
设置地址信息 ==> 创建 socket ==> 绑定(bind)和监听(listen) ==> 监听连接(accept) ==> 读写数据(read/write) ==> 关闭链接(close)
套接字作用说明
- 客户端:创建套接字用于连接服务端;
- 服务器:首先创建套接字用于绑定、监听和获取客户端连接,
accept成功后将获取一个新的连接套接字,专门用于 I/O 事件处理。(一个监听套接字对应多个连接套接字,连接套接字负责具体 I/O 事件)
2.3 识别问题空间中的对象
- InetAddress 对象;
- Socket 对象;
- Acceptor 接受连接对象;
- TcpConnection 全相关连接对象;
- TcpServer 对象;
- Connect 主动发起连接器对象;
- TcpClient 对象;
2.4 确定对象功能
1. InetAddress 对象
- 功能:作为
sockaddr_in的包装器(wrapper),为其它对象提供sockaddr_in地址信息。sockaddr_in是 socket 的专用地址结构体,保存的信息包括地址族、端口号、地址结构体。 - 方法:构造地址,返回地址族信息,设置地址,获取主机 IP 等。
2. Socket 对象
- 功能:为服务器端对象提供监听套接字的封装,完成
bind、listen和接受客户端连接请求(accept)。Socket 对象是 socket 文件描述符(sockfd)的轻量级封装,提供操作底层 sockfd 的常用方法。采用 RAII 方式管理 sockfd,Socket 对象本身不创建或打开 sockfd,仅负责关闭,析构时调用close关闭套接字。 - 方法:绑定 IP 地址(
bind);监听套接字(listen);接收连接请求(accept);关闭连接写方向(shutdown);获取 TCP 协议栈信息(tcp_info)等。
3. Acceptor 接受连接器对象
- 功能:包含 Socket 对象,用于服务器监听新的客户端连接请求(即 server socket)。当有新的连接请求完成时,构建
TcpConnection连接管理对象。
4. TcpConnection 连接管理对象
- 功能:在执行
accept()函数之后创建,由 Socket 对象持有连接套接字(connfd),包含本地地址(localAddr_)、对端地址(peerAddr_)、读写缓冲区等。作为整个网络库的核心,封装一次 TCP 连接(注意不能发起连接),主要实现连接套接字上的读 / 写、关闭连接等操作。 - 状态:包含四种状态 —— 已连接、未连接、正在连接、正在断开。
- 特殊说明:
TcpConnection类是网络库最核心的类,唯一默认用shared_ptr管理的类,唯一继承自enable_shared_from_this的类。这是因为其生命周期模糊:可能在连接断开时,还有其他地方持有它的引用,贸然delete会造成空悬指针。只有确保其他地方没有持有该对象的引用时,才能安全销毁对象。
5. TcpServer Tcp 服务器对象
2.3 识别问题空间中的对象
2.4 确定对象功能
1. InetAddress 对象
2. Socket 对象
3. Acceptor 接受连接器对象
4. TcpConnection 连接管理对象
5. TcpServer Tcp 服务器对象
- 功能:创建监听套接字对象,用于接收新的客户端连接,并将新连接存储到 map 容器中,管理数据的接受和发送。
- 组成:由
Acceptor连接对象和TcpConnection连接管理对象组成。Acceptor对象负责接受新的 TCP 连接,TcpConnection对象负责对连接进行管理。 - 关联方式:定义回调函数与连接的使用者取得关联。
- 提供接口:(原文未展开,保持原样)
- 对象图:
客户端工作流程
设置地址信息 ==> 创建 socket ==> 发起连接(connect) ==> 读写数据(read/write) ==> 关闭连接(close)
服务器工作流程
设置地址信息 ==> 创建 socket ==> 绑定(bind)和监听(listen) ==> 监听连接(accept) ==> 读写数据(read/write) ==> 关闭链接(close)
套接字作用说明
- 客户端:创建套接字用于连接服务端;
- 服务器:首先创建套接字用于绑定、监听和获取客户端连接,
accept成功后将获取一个新的连接套接字,专门用于 I/O 事件处理。(一个监听套接字对应多个连接套接字,连接套接字负责具体 I/O 事件) - InetAddress 对象;
- Socket 对象;
- Acceptor 接受连接对象;
- TcpConnection 全相关连接对象;
- TcpServer 对象;
- Connect 主动发起连接器对象;
- TcpClient 对象;
- 功能:作为
sockaddr_in的包装器(wrapper),为其它对象提供sockaddr_in地址信息。sockaddr_in是 socket 的专用地址结构体,保存的信息包括地址族、端口号、地址结构体。 - 方法:构造地址,返回地址族信息,设置地址,获取主机 IP 等。
- 功能:为服务器端对象提供监听套接字的封装,完成
bind、listen和接受客户端连接请求(accept)。Socket 对象是 socket 文件描述符(sockfd)的轻量级封装,提供操作底层 sockfd 的常用方法。采用 RAII 方式管理 sockfd,Socket 对象本身不创建或打开 sockfd,仅负责关闭,析构时调用close关闭套接字。 - 方法:绑定 IP 地址(
bind);监听套接字(listen);接收连接请求(accept);关闭连接写方向(shutdown);获取 TCP 协议栈信息(tcp_info)等。 - 功能:包含 Socket 对象,用于服务器监听新的客户端连接请求(即 server socket)。当有新的连接请求完成时,构建
TcpConnection连接管理对象。 - 功能:在执行
accept()函数之后创建,由 Socket 对象持有连接套接字(connfd),包含本地地址(localAddr_)、对端地址(peerAddr_)、读写缓冲区等。作为整个网络库的核心,封装一次 TCP 连接(注意不能发起连接),主要实现连接套接字上的读 / 写、关闭连接等操作。 - 状态:包含四种状态 —— 已连接、未连接、正在连接、正在断开。
- 特殊说明:
TcpConnection类是网络库最核心的类,唯一默认用shared_ptr管理的类,唯一继承自enable_shared_from_this的类。这是因为其生命周期模糊:可能在连接断开时,还有其他地方持有它的引用,贸然delete会造成空悬指针。只有确保其他地方没有持有该对象的引用时,才能安全销毁对象。 - 功能:创建监听套接字对象,用于接收新的客户端连接,并将新连接存储到 map 容器中,管理数据的接受和发送。
- 组成:由
Acceptor连接对象和TcpConnection连接管理对象组成。Acceptor对象负责接受新的 TCP 连接,TcpConnection对象负责对连接进行管理。 - 关联方式:定义回调函数与连接的使用者取得关联。
- 提供接口:(原文未展开,保持原样)
- 对象图:
客户端工作流程
设置地址信息 ==> 创建 socket ==> 发起连接(connect) ==> 读写数据(read/write) ==> 关闭连接(close)
服务器工作流程
设置地址信息 ==> 创建 socket ==> 绑定(bind)和监听(listen) ==> 监听连接(accept) ==> 读写数据(read/write) ==> 关闭链接(close)
套接字作用说明
- 客户端:创建套接字用于连接服务端;
- 服务器:首先创建套接字用于绑定、监听和获取客户端连接,
accept成功后将获取一个新的连接套接字,专门用于 I/O 事件处理。(一个监听套接字对应多个连接套接字,连接套接字负责具体 I/O 事件)
2.3 识别问题空间中的对象
- InetAddress 对象;
- Socket 对象;
- Acceptor 接受连接对象;
- TcpConnection 全相关连接对象;
- TcpServer 对象;
- Connect 主动发起连接器对象;
- TcpClient 对象;
2.4 确定对象功能
1. InetAddress 对象
- 功能:作为
sockaddr_in的包装器(wrapper),为其它对象提供sockaddr_in地址信息。sockaddr_in是 socket 的专用地址结构体,保存的信息包括地址族、端口号、地址结构体。 - 方法:构造地址,返回地址族信息,设置地址,获取主机 IP 等。
2. Socket 对象
- 功能:为服务器端对象提供监听套接字的封装,完成
bind、listen和接受客户端连接请求(accept)。Socket 对象是 socket 文件描述符(sockfd)的轻量级封装,提供操作底层 sockfd 的常用方法。采用 RAII 方式管理 sockfd,Socket 对象本身不创建或打开 sockfd,仅负责关闭,析构时调用close关闭套接字。 - 方法:绑定 IP 地址(
bind);监听套接字(listen);接收连接请求(accept);关闭连接写方向(shutdown);获取 TCP 协议栈信息(tcp_info)等。
3. Acceptor 接受连接器对象
- 功能:包含 Socket 对象,用于服务器监听新的客户端连接请求(即 server socket)。当有新的连接请求完成时,构建
TcpConnection连接管理对象。
4. TcpConnection 连接管理对象
- 功能:在执行
accept()函数之后创建,由 Socket 对象持有连接套接字(connfd),包含本地地址(localAddr_)、对端地址(peerAddr_)、读写缓冲区等。作为整个网络库的核心,封装一次 TCP 连接(注意不能发起连接),主要实现连接套接字上的读 / 写、关闭连接等操作。 - 状态:包含四种状态 —— 已连接、未连接、正在连接、正在断开。
- 特殊说明:
TcpConnection类是网络库最核心的类,唯一默认用shared_ptr管理的类,唯一继承自enable_shared_from_this的类。这是因为其生命周期模糊:可能在连接断开时,还有其他地方持有它的引用,贸然delete会造成空悬指针。只有确保其他地方没有持有该对象的引用时,才能安全销毁对象。
5. TcpServer Tcp 服务器对象
2.3 识别问题空间中的对象
2.4 确定对象功能
1. InetAddress 对象
2. Socket 对象
3. Acceptor 接受连接器对象
4. TcpConnection 连接管理对象
5. TcpServer Tcp 服务器对象
- 功能:创建监听套接字对象,用于接收新的客户端连接,并将新连接存储到 map 容器中,管理数据的接受和发送。
- 组成:由
Acceptor连接对象和TcpConnection连接管理对象组成。Acceptor对象负责接受新的 TCP 连接,TcpConnection对象负责对连接进行管理。 - 关联方式:定义回调函数与连接的使用者取得关联。
- 提供接口:(原文未展开,保持原样)
- 对象图:
客户端工作流程
设置地址信息 ==> 创建 socket ==> 发起连接(connect) ==> 读写数据(read/write) ==> 关闭连接(close)
服务器工作流程
设置地址信息 ==> 创建 socket ==> 绑定(bind)和监听(listen) ==> 监听连接(accept) ==> 读写数据(read/write) ==> 关闭链接(close)
套接字作用说明
- 客户端:创建套接字用于连接服务端;
- 服务器:首先创建套接字用于绑定、监听和获取客户端连接,
accept成功后将获取一个新的连接套接字,专门用于 I/O 事件处理。(一个监听套接字对应多个连接套接字,连接套接字负责具体 I/O 事件) - InetAddress 对象;
- Socket 对象;
- Acceptor 接受连接对象;
- TcpConnection 全相关连接对象;
- TcpServer 对象;
- Connect 主动发起连接器对象;
- TcpClient 对象;
- 功能:作为
sockaddr_in的包装器(wrapper),为其它对象提供sockaddr_in地址信息。sockaddr_in是 socket 的专用地址结构体,保存的信息包括地址族、端口号、地址结构体。 - 方法:构造地址,返回地址族信息,设置地址,获取主机 IP 等。
- 功能:为服务器端对象提供监听套接字的封装,完成
bind、listen和接受客户端连接请求(accept)。Socket 对象是 socket 文件描述符(sockfd)的轻量级封装,提供操作底层 sockfd 的常用方法。采用 RAII 方式管理 sockfd,Socket 对象本身不创建或打开 sockfd,仅负责关闭,析构时调用close关闭套接字。 - 方法:绑定 IP 地址(
bind);监听套接字(listen);接收连接请求(accept);关闭连接写方向(shutdown);获取 TCP 协议栈信息(tcp_info)等。 - 功能:包含 Socket 对象,用于服务器监听新的客户端连接请求(即 server socket)。当有新的连接请求完成时,构建
TcpConnection连接管理对象。 - 功能:在执行
accept()函数之后创建,由 Socket 对象持有连接套接字(connfd),包含本地地址(localAddr_)、对端地址(peerAddr_)、读写缓冲区等。作为整个网络库的核心,封装一次 TCP 连接(注意不能发起连接),主要实现连接套接字上的读 / 写、关闭连接等操作。 - 状态:包含四种状态 —— 已连接、未连接、正在连接、正在断开。
- 特殊说明:
TcpConnection类是网络库最核心的类,唯一默认用shared_ptr管理的类,唯一继承自enable_shared_from_this的类。这是因为其生命周期模糊:可能在连接断开时,还有其他地方持有它的引用,贸然delete会造成空悬指针。只有确保其他地方没有持有该对象的引用时,才能安全销毁对象。 - 功能:创建监听套接字对象,用于接收新的客户端连接,并将新连接存储到 map 容器中,管理数据的接受和发送。
- 组成:由
Acceptor连接对象和TcpConnection连接管理对象组成。Acceptor对象负责接受新的 TCP 连接,TcpConnection对象负责对连接进行管理。 - 关联方式:定义回调函数与连接的使用者取得关联。
- 提供接口:(原文未展开,保持原样)
- 对象图:

- 时序图:

6. Connector 主动发起连接器对象
核心功能
Connector负责主动发起连接,不负责创建sockfd,仅负责连接的建立。外部通过调用Connector::start()即可发起连接,具备重连功能和停止连接功能,连接成功建立后将结果返回给TcpClient。
与 Acceptor 的区别
与Acceptor相比,Connector少了acceptSocket_成员,因为Connector是创建新的sockfd并执行connect操作,其创建流程如下:Connector::start() → Connector::startInLoop() → void Connector::connect()
关联关系:一个
TcpClient对应一个TcpConnection和一个Connector;而一个TcpServer对应一个TcpConnection列表和一个Acceptor。
非阻塞连接的难点及解决方案
在非阻塞网络中,主动发起连接比被动接收连接更复杂,需考虑错误处理和重试机制,主要难点包括:
-
socket 一次性特性:
socket 是一次性资源,一旦出错无法恢复,仅能关闭后重新创建。使用新的文件描述符(fd)后,需搭配新的channel。 -
连接状态二次确认:
非阻塞模式下,socket可写并不意味着连接已成功建立,需通过getsockopt(sockfd, SOL_SOCKET, SO_ERROR, …)再次确认连接状态。
其他确认方法:- 调用
getpeername,若失败返回ENOTCONN,表示连接失败; - 调用
read且长度参数为 0,若失败表示connect失败; - 再次调用
connect,若返回错误EISCONN,表示套接字已成功建立连接。
- 调用
-
指数退避重试机制:
重试间隔时间应逐渐延长(直至back-off),重试通过EventLoop::runAfter实现。为防止Connector在定时器到期前析构,需在Connector的析构函数中注销定时器。 -
防止自连接:
需通过逻辑避免自连接情况的发生。
连接建立后的处理
在handleWrite()中需要调用removeAndResetChannel(),因为此时连接已建立,无需再关注channel的可写事件,最终执行channel_.reset()析构channel。此外,该函数需返回sockfd,交由TcpConnection接管。
7. TcpClient
核心功能
TcpClient负责发起连接,内部包含一个Connector连接器,通过Connector发起连接。连接建立成功后,使用socket创建TcpConnection来管理连接。每个TcpClient类仅管理一个TcpConnection,连接建立成功后会设置相应的回调函数。
组件关系
TcpClient用于管理客户端连接,实际连接操作由Connector执行。Connector类不单独使用,而是封装在TcpClient中,一个Connector对应一个TcpClient。Connector负责建立连接,建立成功后将控制权交给TcpConnection,因此TcpClient中也封装了一个TcpConnection对象。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)