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

相关推荐
初九之潜龙勿用11 分钟前
技术与情感交织的一生 (十一)
服务器·笔记·microsoft·印象笔记
xw544 分钟前
免费的个人网站托管-InfinityFree
服务器·前端
wuzuyu3651 小时前
Laravel The requested URL /hellowzy was not found on this server. 404 问题的解决
php·laravel
!win !1 小时前
免费的个人网站托管-InfinityFree
服务器·前端工具
liunim901 小时前
linux服务器上word转pdf后乱码问题
linux·服务器·pdf
小王努力学编程2 小时前
【Linux系统编程】线程概念与控制
linux·服务器·开发语言·c++·学习·线程·pthread库
EndingCoder2 小时前
HTTP性能优化实战:解决高并发场景下的连接瓶颈与延迟问题
网络·网络协议·http·性能优化·高并发
IPIDEA全球IP代理2 小时前
IPIDEA:全球领先的企业级代理 IP 服务商
网络·网络协议·tcp/ip
十年一梦实验室2 小时前
工业机器人控制系统 IP-PP-EXEC 流水线
网络·人工智能·网络协议·tcp/ip·机器人