ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

仿muduo高并发网络库——InetAddress、Acceptor

仿muduo高并发网络库——InetAddress、Acceptor 一、InetAddress#ifndef MUDUO_INETADDRESS_H #define MUDUO_INETADDRESS_H #include string #include netinet/in.h #include arpa/inet.h #include cstring // 封装 sockaddr_in提供便捷的 IP/端口访问方法 class InetAddress { public: // 默认构造0.0.0.0:0 InetAddress() { memset(addr_, 0, sizeof(addr_)); addr_.sin_family AF_INET; } // 指定端口0.0.0.0:port explicit InetAddress(uint16_t port) { memset(addr_, 0, sizeof(addr_)); addr_.sin_family AF_INET; addr_.sin_addr.s_addr htonl(INADDR_ANY); addr_.sin_port htons(port); } // 指定 IP 和端口 InetAddress(const std::string ip, uint16_t port) { memset(addr_, 0, sizeof(addr_)); addr_.sin_family AF_INET; inet_pton(AF_INET, ip.c_str(), addr_.sin_addr); addr_.sin_port htons(port); } // 从 sockaddr_in 构造 explicit InetAddress(const struct sockaddr_in addr) : addr_(addr) {} // 获取 IP 字符串如 127.0.0.1 std::string toIp() const { char buf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, addr_.sin_addr, buf, sizeof(buf)); return std::string(buf); } // 获取端口主机字节序 uint16_t port() const { return ntohs(addr_.sin_port); } // 获取 IP:Port 字符串如 127.0.0.1:8080 std::string toIpPort() const { return toIp() : std::to_string(port()); } // 获取底层 sockaddr_in 指针给 bind/accept 等系统调用用 struct sockaddr_in* getSockAddr() { return addr_; } const struct sockaddr_in* getSockAddr() const { return addr_; } private: struct sockaddr_in addr_; }; #endif// 封装 sockaddr_in提供便捷的 IP/端口访问方法 class InetAddress { public: // 默认构造0.0.0.0:0 InetAddress() { memset(addr_, 0, sizeof(addr_)); addr_.sin_family AF_INET; }作用 默认构造函数- memset(addr_, 0, sizeof(addr_)) 清零sockaddr_in 结构- addr_.sin_family AF_INET 设置地址族为 IPv4sockaddr_in 结构struct sockaddr_in { sa_family_t sin_family; // 地址族AF_INET in_port_t sin_port; // 端口网络字节序 struct in_addr sin_addr; // IP 地址网络字节序 char sin_zero[8]; // 填充字节 }; struct in_addr { in_addr_t s_addr; // 32位 IP 地址 };为什么用memset 清零- sin_zero 字段需要清零- 避免未初始化的内存导致问题// 指定端口0.0.0.0:port explicit InetAddress(uint16_t port) { memset(addr_, 0, sizeof(addr_)); addr_.sin_family AF_INET; addr_.sin_addr.s_addr htonl(INADDR_ANY); addr_.sin_port htons(port); }作用 指定端口的构造函数- explicit 防止隐式转换- htonl(INADDR_ANY) 监听所有 IP0.0.0.0- htonl 主机字节序转网络字节序32位- INADDR_ANY 常量 0表示所有 IP- htons(port) 主机字节序转网络字节序16位字节序问题- 主机字节序小端x86低字节在前- 网络字节序大端高字节在前- 网络协议统一使用大端所以需要转换使用场景InetAddress addr(8080); // 监听 0.0.0.0:8080// 指定 IP 和端口 InetAddress(const std::string ip, uint16_t port) { memset(addr_, 0, sizeof(addr_)); addr_.sin_family AF_INET; inet_pton(AF_INET, ip.c_str(), addr_.sin_addr); addr_.sin_port htons(port); }作用 指定 IP 和端口的构造函数- inet_pton(AF_INET, ip.c_str(), addr_.sin_addr) 字符串转二进制- AF_INET IPv4- ip.c_str() IP 字符串如 127.0.0.1- addr_.sin_addr 输出参数inet_pton vsinet_addr - inet_addr 旧函数不支持 IPv6错误返回 -1和 255.255.255.255 冲突- inet_pton 新函数支持 IPv4 和 IPv6返回值明确使用场景InetAddress addr(127.0.0.1, 8080); // 连接 127.0.0.1:8080// 从 sockaddr_in 构造 explicit InetAddress(const struct sockaddr_in addr) : addr_(addr) {}作用 从sockaddr_in 构造- explicit 防止隐式转换- 直接拷贝sockaddr_in 结构使用场景struct sockaddr_in client_addr; accept(listenfd, (struct sockaddr*)client_addr, len); InetAddress peerAddr(client_addr); // 封装对端地址// 获取 IP 字符串如 127.0.0.1 std::string toIp() const { char buf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, addr_.sin_addr, buf, sizeof(buf)); return std::string(buf); }作用 获取 IP 字符串- INET_ADDRSTRLEN 常量 16IPv4 字符串最大长度- inet_ntop 二进制转字符串- AF_INET IPv4- addr_.sin_addr 输入参数- buf 输出缓冲区- sizeof(buf) 缓冲区大小InetAddress addr(127.0.0.1, 8080); std::string ip addr.toIp(); // 127.0.0.1// 获取端口主机字节序 uint16_t port() const { return ntohs(addr_.sin_port); }作用 获取端口- ntohs 网络字节序转主机字节序16位使用场景uint16_t port addr.port(); // 8080// 获取 IP:Port 字符串如 127.0.0.1:8080 std::string toIpPort() const { return toIp() : std::to_string(port()); }作用 获取 IP:Port 字符串使用场景std::string addrStr addr.toIpPort(); // 127.0.0.1:8080// 获取底层 sockaddr_in 指针给 bind/accept 等系统调用用 struct sockaddr_in* getSockAddr() { return addr_; } const struct sockaddr_in* getSockAddr() const { return addr_; }作用 获取底层指针- 第一个版本返回非 const 指针可修改- 第二个版本返回 const 指针只读使用场景bind(fd, (struct sockaddr*)addr.getSockAddr(), sizeof(struct sockaddr_in));private: struct sockaddr_in addr_; };作用 成员变量- addr_ 底层sockaddr_in 结构二、Acceptor1.Acceptor.h//新连接回调函数类型 //参数新连接的connfd, 对端地址 using NewConnectionCallback std::functionvoid(int connfd, const InetAddress peerAddr);作用 定义回调函数类型- 参数connfd 新连接的 fd、peerAddr 对端地址- 由TcpServer 设置class Acceptor { public: Acceptor(EventLoop* loop, int port); ~Acceptor();作用 构造和析构声明//设置新连接回调由TcpServer设置 void setNewConnectionCallback(const NewConnectionCallback cb) { newConnectionCallback_ cb; }作用 设置新连接回调-由TcpServer 在构造时调用//开始监听listenfd把listenfd的Channel注册到epoll void listen();作用 开始监听- 调用listen() 系统调用- 注册 Channel 到 epollprivate: //listenfd可读时调用有新连接 void handleRead();作用 私有方法处理新连接EventLoop* loop_; //所属的EventLoop int listenfd_; //监听socket Channel listenChannel_; //listenfd对应的Channel InetAddress listenAddr_; //监听地址 NewConnectionCallback newConnectionCallback_; //新连接回调 };作用 成员变量- loop_ 所属的 EventLoop- listenfd_ 监听 socket- listenChannel_ listenfd 对应的 Channel- listenAddr_ 监听地址- newConnectionCallback_ 新连接回调2.Acceptor.cppAcceptor::Acceptor(EventLoop* loop, int port) : loop_(loop) , listenfd_(socket(AF_INET, SOCK_STREAM, 0)) , listenChannel_(loop, listenfd_) , listenAddr_(port) { //1.设置SO_REUSEADDR避免TIME_WAIT导致bind失败 int optval 1; setsockopt(listenfd_, SOL_SOCKET, SO_REUSEADDR, optval, sizeof(optval)); //2.bind if (bind(listenfd_, (struct sockaddr*)listenAddr_.getSockAddr(), sizeof(struct sockaddr_in)) 0) { printf(Acceptor bind failed!\n); } }作用 构造函数初始化列表- loop_(loop) 保存 EventLoop 指针- listenfd_(socket(AF_INET, SOCK_STREAM, 0)) 创建 TCP socket- AF_INET IPv4- SOCK_STREAM TCP- 0 自动选择协议- listenChannel_(loop, listenfd_) 创建 Channel- listenAddr_(port) 创建监听地址socket() 系统调用- 返回文件描述符- 失败返回 -1setsockopt() 设置SO_REUSEADDR - 允许重用本地地址和端口- 避免TIME_WAIT 状态导致 bind 失败为什么需要SO_REUSEADDR - TCP 连接关闭后会进入TIME_WAIT 状态2MSL约 60 秒- 期间不能重用相同的端口- 设置SO_REUSEADDR 后可以立即重用bind() 系统调用- 将 socket 绑定到指定地址和端口- 失败返回 -1void Acceptor::listen() { //3.开始监听 ::listen(listenfd_, 5);作用 开始监听- ::listen 全局listen 函数避免和成员函数冲突- listenfd_ 监听 socket- 5 backlog等待队列的最大长度listen() 系统调用- 将 socket 从CLOSED 状态转为LISTEN 状态- backlog 已完成三次握手的连接队列长度//4.把listenfd的Channel注册到epoll监听读事件 listenChannel_.setReadCallback([this]() { handleRead(); }); listenChannel_.enableReading();作用 注册 Channel 到 epoll- setReadCallback 设置读回调为handleRead()- enableReading() 启用读事件监听Lambda 表达式- [this]() 捕获this 指针- handleRead() 调用成员函数为什么用 Lambda- 可以捕获this 调用成员函数- 比std::bind 更简洁void Acceptor::handleRead() { //有新连接到达accept struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int connfd accept(listenfd_, (struct sockaddr*)client_addr, client_len);作用 处理新连接- accept() 接受新连接- listenfd_ 监听 socket- client_addr 输出参数对端地址- client_len 输入输出参数地址长度- 返回新的 socketconnfdaccept() 系统调用- 从等待队列中取出一个连接- 创建新的 socketconnfd- 返回 connfd为什么accept() 会返回- 因为 listenfd 可读表示有新连接到达- epoll 通知EPOLLIN 事件if (connfd 0) { printf(Acceptor accepted connfd%d\n, connfd); //调用回调把connfd交给上层TcpServer if (newConnectionCallback_) { InetAddress peerAddr(client_addr); newConnectionCallback_(connfd, peerAddr); } } else { printf(Acceptor accept failed!\n); } }作用 处理 accept 结果- connfd 0 成功- 封装对端地址- 调用回调交给TcpServer- connfd 0 失败- 打印错误信息
返回列表