网络muduo库的实现(1)

仿写 muduo 网络库

网络库实现的核心目标

  1. 线程安全,支持多核多线程,实现高并发的连接请求和低延时的客户端请求。
  2. 不考虑可移植性,不跨平台,只支持 Linux,不支持 Windows。
  3. 主要支持 x86-64,兼顾 IA32。
  4. 不支持 UDP,只支持 TCP。
  5. 不支持 IPv6,只支持 IPv4。
  6. 不考虑广域网应用,只考虑局域网。
  7. 不考虑公网,只考虑内网。不为安全性做特别的增强。
  8. 只支持一种使用模式:非阻塞 IO+one event loop per thread,不支持阻塞 IO。
  9. API 简单易用,只暴露具体类和标准库里的类。API 不使用 non-trivial templates,也不使用虚函数。
  10. 只满足常用需求的 90%,不面面俱到,必要的时候以 app 来适应 lib。
  11. 只做 libray (库),不做成 framework (构架)

muduo 网络库成功的原因将解决的问题控制在一个范围内,不追求大而全。

1. 第一版本的需求

在客户端获取日志信息,将日志信息发送到服务端,服务端接受日志信息,再接着将日志信息写入文件。代码最终的要求是:

服务器端程序:

  1. 指定服务器 IP 地址和 Port 端口号;
  2. 定义服务器对象,执行bind绑定和listen监听操作;
  3. 接受客户端连接(accept);
  4. 与客户端进行数据读写;
  5. 关闭客户端的连接。

客户端程序:

  1. 指定连接的服务器 IP 地址和 port 端口号;
  2. 定义客户端对象;
  3. 发起与服务器的连接;
  4. 与服务器进行数据读写;
  5. 关闭连接。

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 识别问题空间中的对象

  1. InetAddress 对象;
  2. Socket 对象;
  3. Acceptor 接受连接对象;
  4. TcpConnection 全相关连接对象;
  5. TcpServer 对象;
  6. Connect 主动发起连接器对象;
  7. TcpClient 对象;

2.4 确定对象功能

1. InetAddress 对象

  • 功能:作为sockaddr_in的包装器(wrapper),为其它对象提供sockaddr_in地址信息。sockaddr_in是 socket 的专用地址结构体,保存的信息包括地址族、端口号、地址结构体。
  • 方法:构造地址,返回地址族信息,设置地址,获取主机 IP 等。

2. Socket 对象

  • 功能:为服务器端对象提供监听套接字的封装,完成bindlisten和接受客户端连接请求(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 等。
  • 功能:为服务器端对象提供监听套接字的封装,完成bindlisten和接受客户端连接请求(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 识别问题空间中的对象

  1. InetAddress 对象;
  2. Socket 对象;
  3. Acceptor 接受连接对象;
  4. TcpConnection 全相关连接对象;
  5. TcpServer 对象;
  6. Connect 主动发起连接器对象;
  7. TcpClient 对象;

2.4 确定对象功能

1. InetAddress 对象

  • 功能:作为sockaddr_in的包装器(wrapper),为其它对象提供sockaddr_in地址信息。sockaddr_in是 socket 的专用地址结构体,保存的信息包括地址族、端口号、地址结构体。
  • 方法:构造地址,返回地址族信息,设置地址,获取主机 IP 等。

2. Socket 对象

  • 功能:为服务器端对象提供监听套接字的封装,完成bindlisten和接受客户端连接请求(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 等。
  • 功能:为服务器端对象提供监听套接字的封装,完成bindlisten和接受客户端连接请求(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

非阻塞连接的难点及解决方案

在非阻塞网络中,主动发起连接比被动接收连接更复杂,需考虑错误处理和重试机制,主要难点包括:

  1. socket 一次性特性
    socket 是一次性资源,一旦出错无法恢复,仅能关闭后重新创建。使用新的文件描述符(fd)后,需搭配新的channel

  2. 连接状态二次确认
    非阻塞模式下,socket可写并不意味着连接已成功建立,需通过getsockopt(sockfd, SOL_SOCKET, SO_ERROR, …)再次确认连接状态。
    其他确认方法:

    • 调用getpeername,若失败返回ENOTCONN,表示连接失败;
    • 调用read且长度参数为 0,若失败表示connect失败;
    • 再次调用connect,若返回错误EISCONN,表示套接字已成功建立连接。
  3. 指数退避重试机制
    重试间隔时间应逐渐延长(直至back-off),重试通过EventLoop::runAfter实现。为防止Connector在定时器到期前析构,需在Connector的析构函数中注销定时器。

  4. 防止自连接
    需通过逻辑避免自连接情况的发生。

连接建立后的处理

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对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值