网络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

相关推荐
阿昭L4 分钟前
HTTP原理
网络·网络协议·http
2301_7943339112 分钟前
实验室服务器配置|通过Docker实现Linux系统多用户隔离与安全防控
linux·服务器·docker·实验室
hazy1k1 小时前
STM32H750 RTC介绍及应用
网络·stm32·实时音视频
没书读了1 小时前
考研复习-计算机网络-第三章-数据链路层
网络·计算机网络·考研
on the way 1231 小时前
多线程之HardCodedTarget(type=OssFileClient, name=file, url=http://file)异常
网络·网络协议·http
荣光波比1 小时前
Nginx 实战系列(一)—— Web 核心概念、HTTP/HTTPS协议 与 Nginx 安装
linux·运维·服务器·nginx·云计算
武文斌771 小时前
单片机:DS18B20测温度、74HC595扩展芯片、8*8LED矩阵
运维·服务器·单片机·嵌入式硬件
WhoisXMLAPI2 小时前
WhoisXML API再次荣登2025年美国Inc. 5000快速成长企业榜单
网络·安全
阿sir1982 小时前
ZYNQ 自定义IP
服务器·网络·tcp/ip
星马梦缘3 小时前
计算机网络4 第四章 网络层——网络间的通信问题(省际之间如何规划信件运输路线)
网络·计算机网络·路由·ip地址·子网掩码·icmp·ipv4/ipv6