网络muduo库的实现(2)

3. 类型设计

1. InetAddress 类型

InetAddress 类作为 sockaddr_in 结构体的包装器,提供对网络地址信息的封装和操作。

复制代码
class InetAddress
{
private:
    struct sockaddr_in addr_;  // 底层地址结构体
public:
    explicit InetAddress(uint16_t port = 0);  // 仅指定端口构造
    InetAddress(const std::string &ip, uint16_t port);  // 指定IP和端口构造
    InetAddress(const struct sockaddr_in &addr);  // 通过sockaddr_in构造
    
    std::string getIp() const;  // 获取IP地址字符串
    std::string getIpPort() const;  // 获取IP:端口字符串
    uint16_t getPort() const;  // 获取端口号
    
    const struct sockaddr_in & getSockInet() const;  // 获取底层sockaddr_in
    void setSockAddrInet(const struct sockaddr_in &addr);  // 设置底层sockaddr_in
    
    // 将主机名解析为IP地址,不更改端口或sin_family
    // 成功后返回真值,线程安全
    static bool resolve(const string &hostname, InetAddress &result); 
};

2. Socket 类型

Socket 类封装了 socket 文件描述符的操作,采用 RAII 方式管理资源。

复制代码
// in <netinet/tcp.h>
struct tcp_info;

class InetAddress;

class Socket
{
private:
    const int sockfd_;  // socket文件描述符
public:
    explicit Socket(int sockfd);  // 通过已有fd构造
    ~Socket();  // 析构时关闭socket
    
    int fd() const;  // 获取文件描述符
    
    bool getTcpInfo(const struct tcp_info &tcpinfo) const;  // 获取TCP信息
    bool getTcpInfoString(std::string &buf) const;  // 获取TCP信息字符串
    
    void bindAddress(const InetAddress &localaddr);  // 绑定地址
    void listen();  // 开始监听
    int accept(InetAddress &peeraddr);  // 接受连接
    void shutdownWrite();  // 关闭写方向
    
    // 设置socket属性
    void setTcpNoDelay(bool on);  // 设置TCP_NODELAY
    void setReuseAddr(bool on);  // 设置SO_REUSEADDR
    void setReusePort(bool on);  // 设置SO_REUSEPORT
    void setKeepAlive(bool on);  // 设置SO_KEEPALIVE
};

3. Acceptor 类型

Acceptor 类负责服务器端监听新连接,封装了监听 socket 和连接接受逻辑。

复制代码
class EventLoop;
class InetAddress;

class Acceptor
{
public:
    // 新连接回调函数类型:参数为新连接的sockfd和对端地址
    using NewConnectionCallback = std::function<void(const int sockfd, const InetAddress &)>;
private:
    Socket acceptSocket_;  // 监听socket
    NewConnectionCallback newConnectionCallback_;  // 新连接建立后的回调
    bool listenning_;  // 是否正在监听
    int idleFd_;  // 空闲文件描述符,用于处理文件描述符耗尽情况
    
    void handleRead();  // 处理读事件(新连接到来)
public:
    Acceptor(const InetAddress &listenAddr, bool resueport);  // 构造函数
    ~Acceptor();  // 析构函数
    
    void setNewConnectionCallback(const NewConnectionCallback& cb);  // 设置新连接回调
    bool listenning() const;  // 判断是否正在监听
    void listen();  // 开始监听
};

4. TcpConnection 全相关连接类型

TcpConnection 类封装一次 TCP 连接的生命周期管理,是网络库的核心类。

复制代码
class Channel;
class EventLoop;
class Socket;

class TcpConnection : public std::enable_shared_from_this<TcpConnection>
{
private:
    // 连接状态枚举
    enum class StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
    
    const std::string name_;  // 连接名称
    StateE state_;  // 当前连接状态
    std::unique_ptr<Socket> socket_;  // 连接socket
    const InetAddress localAddr_;  // 本地地址
    const InetAddress peerAddr_;  // 对端地址
    size_t highWaterMark_;  // 高水位标记,用于流量控制
    
    std::string inputBuffer_;  // 输入缓冲区
    std::string outputBuffer_;  // 输出缓冲区
    
    // 各类回调函数
    ConnectionCallback connectionCallback_;  // 连接状态变化回调
    MessageCallback messageCallback_;  // 消息到达回调
    WriteCompleteCallback writeCompleteCallback_;  // 写完成回调
    HighWaterMarkCallback highWaterMarkCallback_;  // 高水位回调
    CloseCallback closeCallback_;  // 关闭回调

private:
    // 事件处理函数
    void handleRead(const Timestamp &receiveTime);  // 处理读事件
    void handleWrite();  // 处理写事件
    void handleClose();  // 处理关闭事件
    void handleError();  // 处理错误事件
    
    void setState(StateE s);  // 设置连接状态
public:
    // 构造函数:参数为连接名称、sockfd、本地地址、对端地址
    TcpConnection(const std::string &name,
                  int sockfd,
                  const InetAddress &localAddr,
                  const InetAddress &peerAddr);
    ~TcpConnection();  // 析构函数
    
    EventLoop * getLoop() const;  // 获取所属事件循环
    const std::string & name() const;  // 获取连接名称
    const InetAddress & localAddress() const;  // 获取本地地址
    const InetAddress & peerAddress() const;  // 获取对端地址
    bool connected() const;  // 判断是否已连接
    
    bool getTcpInfo(struct tcp_info &) const;  // 获取TCP信息
    std::string getTcpInfoString() const;  // 获取TCP信息字符串
    
    // 数据发送接口
    void send(const void *message, int len);
    void send(const std::string &message);
    
    void shutdown();  // 关闭连接
    void forceClose();  // 强行关闭连接
    void forceCloseWithDelay(int64_t seconds);  // 延迟强行关闭
    
    void setTcpNoDelay(bool on);  // 设置TCP_NODELAY
    
    // 设置回调函数
    void setConnectionCallback(const ConnectionCallback & cb);
    void setMessageCallback(const MessageCallback &cb);
    void setWriteCompleteCallback(const WriteCompleteCallback &cb);
    void setHighWaterMarkCallback(const HighWaterMarkCallback &cb, size_t highWaterMark);
    void setCloseCallback(const CloseCallback &cb);
    
    std::string & inputBuffer();  // 获取输入缓冲区
    std::string & outputBuffer();  // 获取输出缓冲区
    
    void connectEstablished();  // 连接建立完成
    void connectDestroyed();  // 连接销毁
};

5. TcpServer 服务器类型

TcpServer 类作为服务器端入口,管理 acceptor 和连接池,提供服务器启动和连接管理功能。

复制代码
class Acceptor;
class EventLoop;
class EventLoopThreadPool;

class TcpServer
{
public:
    using ThreadInitCallback = std::function<void(EventLoop*)>;  // 线程初始化回调
    
    // 服务器选项枚举
    enum class Option
    {
        kNoReusePort,  // 不重用端口
        kReusePort     // 重用端口
    };
private:
    using ConnectionMap = std::map<std::string, TcpConnectionPtr>;  // 连接映射表
    
    EventLoop *loop_;  // 主事件循环
    const std::string hostport_;  // 主机端口字符串
    const std::string name_;  // 服务器名称
    std::unique_ptr<Acceptor> acceptor_;  //  acceptor对象
    std::shared_ptr<EventLoopThreadPool> threadPool_;  // 事件循环线程池
    
    ConnectionCallback connectionCallback_;  // 连接回调
    
    // 新连接处理函数
    void newConnection(int sockfd, const InetAddress & peerAddr);
    // 移除连接
    void removeConnection(const TcpConnectionPtr & conn);
    void removeConnectionInLoop(const TcpConnectionPtr &conn);
};

4. 类图

5. TcpServer 对象的创建和连接建立过程

复制代码
    //Buffer * outputBuffer();
    std::string & outputBuffer();
    void setCloseCallback(const CloseCallback &cb);
    
    void connectEstablished();
    void connectDestroyed(); 
};

TcpServer 类完整定义

复制代码
class Acceptor;
class EventLoop;
class EventLoopThreadPool;

class TcpServer
{
public:
    using ThreadInitCallback = std::function<void(EventLoop*)>;  // 线程初始化回调
    enum class Option
    {
        kNoReusePort,  // 不重用端口
        kReusePort     // 重用端口
    };
private:
    using ConnectionMap = std::map<std::string, TcpConnectionPtr>;  // 连接映射表
    EventLoop * loop_;  // 主事件循环
    const std::string hostport_;  // 主机端口字符串
    const std::string name_;  // 服务器名称
    std::unique_ptr<Acceptor> acceptor_;  // Acceptor对象
    std::shared_ptr<EventLoopThreadPool> threadPool_;  // 事件循环线程池
    ConnectionCallback connectionCallback_;  // 连接回调函数
    
    // 新连接处理函数
    void newConnection(int sockfd, const InetAddress & peerAddr);
    // 移除连接
    void removeConnection(const TcpConnectionPtr & conn);
    void removeConnectionInLoop(const TcpConnectionPtr &conn);
};

实施和测试

2. 问题

我们通过类型封装 Sockets API,目的之一是在进行网络编程时,从 Sockets API 的琐碎细节中解脱出来。但尚未解决高并发的问题。

3. 第二个版本的需求

使用 Reactor (反应堆) 模式实现高性能的服务器程序。

网络开发的基本概念

1. 阻塞 I/O 模式和非阻塞 I/O 模式
  • 阻塞 I/O 模式:缺省或默认状态下,套接字处于阻塞 I/O 模式。当调用 Sockets 函数不能立即完成时,系统内核会使执行程序(进程或线程)进入阻塞状态,等待操作完成。
  • 非阻塞 I/O 模式:若将套接字设置为非阻塞 I/O 模式,调用 Sockets 函数不能立即完成时,系统内核不会阻塞当前程序(进程或线程),而是返回一个错误信息。
2. 可能阻塞的 Sockets 函数分类

将可能阻塞的 Sockets 函数调用分为以下四类:

  1. 输入操作readreadvrecvrecvfromrecvmsg 函数。

    • 阻塞模式:在阻塞 TCP 套接字上调用这些函数时,若接收缓冲区中无数据,进程会阻塞至数据到来(TCP 是字节流,数据到来时进程被唤醒,可能是一个字节或完整分节)。可通过自定义 readn 函数或指定 MSG_WAIT_ALL 标志等待固定数据量。
    • 非阻塞模式:若输入操作无法满足(TCP 至少需 1 字节数据,UDP 需完整数据报),会立即返回 EWOULDBLOCK 错误码(或 EAGAIN,表示资源暂时不可用)。
  2. 输出操作writewritevsendsendtosendmsg 函数。

    • 阻塞模式:对 TCP socket,内核从应用缓冲区向套接字发送缓冲区拷贝数据。若发送缓冲区无可用空间,进程会阻塞至内核腾出空间。
    • 非阻塞模式:若发送缓冲区无空间,输出操作立即返回 EWOULDBLOCK 错误;若有部分空间,返回值为内核可拷贝的字节数(称为 "不足计数")。UDP 无发送缓冲区,内核直接拷贝数据并传递至下层协议,因此阻塞 UDP 套接字的输出操作不会阻塞。
  3. 接收外来连接accept 函数。

    • 阻塞模式:调用 accept 时若无新连接,进程会阻塞。
    • 非阻塞模式:调用 accept 时若无新连接,返回 EWOULDBLOCK 错误。
  4. 初始化外出连接 :TCP 的 connect 函数。

    • TCP 连接建立需三次握手,connect 函数在客户接收 SYN 的 ACK 前不会返回,通常阻塞至少一个 RTT(往返时间),默认超时 75s。
    • 非阻塞模式:若连接不能立即建立,连接建立过程启动(如发送三次握手第一个分组),但返回 EINPROGRESS 错误。需注意同一主机上的连接可能立即建立,因此需处理 connect 返回成功的情况。

说明 :系统 V 对不能立即完成的非阻塞 I/O 操作返回 EAGAIN 错误,Berkeley 实现返回 EWOULDBLOCK 错误。多数系统(如 SVR4 和 4.4BSD)将两者定义为相同值(可查看 <sys/errno.h>),本文使用 Posix.1g 规定的 EWOULDBLOCK

相关推荐
BingoGo1 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack1 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户3074596982072 天前
PHP 扩展——从入门到理解
php
鹏仔先生3 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
小宇宙Zz3 天前
Maven依赖冲突
java·服务器·maven
网络研究院3 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展
酣大智3 天前
ARP代理--工作原理
运维·网络·arp·arp代理
云水一下3 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
treesforest3 天前
AI安全系统如何识别异常访问?IP风险识别正在成为关键能力
网络·人工智能·tcp/ip·安全·web安全