[C++]sylar高性能服务器框架学习记录:Address模块

记录一下最近这学期学习的sylar服务器框架项目,输出整理一下项目的结构,用到的知识和自己的体会

项目仓库地址

https://github.com/sylar-yin/sylar/

整理博客过程中参考的大佬资料链接:

============================================

基础介绍

封装了各种Address类,提供方便的操作接口,支持IPv4,IPv6,UnixAddress

所有类都派生自基类Address

Address类

作为后续所有地址类的基类,提供域名解析(使用getaddrinfo()),获取网卡配置,获取sockaddr,获取协议族,字符串化等方法

// 所有Address类的抽象基类
class Address {
public:
    typedef std::shared_ptr
<Address> ptr;

    static Address::ptr Create(const sockaddr* addr, socklen_t addrlen);    // 通过sockaddr创建地址

    static bool Lookup(std::vector<Address::ptr>& result, const std::string& host,
            int family = AF_UNSPEC, int type = 0, int protocol = 0);        // 通过host返回所有Address

    static Address::ptr LookupAny(const std::string& host,
            int family = AF_UNSPEC, int type = 0, int protocol = 0);        // 通过host返任意Address

    static std::shared_ptr
<IPAddress> LookupAnyIPAddress(const std::string& host,
            int family = AF_UNSPEC, int type = 0, int protocol = 0);        // 通过host返回任意IPAddress

    // 获取本机所有网卡配置
    static bool GetInterfaceAddresses(std::multimap<std::string
                    , std::pair<Address::ptr, uint32_t>>& result,
                    int family = AF_UNSPEC);
    // 获取指定网卡配置
    static bool GetInterfaceAddresses(std::vector<std::pair<Address::ptr, uint32_t>>& result
                    , const std::string& iface, int family = AF_UNSPEC);

    virtual ~Address() {}

    int getFamily() const;  // 返回地址协议族

    virtual const sockaddr* getAddr() const = 0;    // 获取sockaddr指针
    virtual sockaddr* getAddr() = 0;                // 获取sockaddr指针(非const版本)
    virtual socklen_t getAddrLen() const = 0;       // 获取sockaddr长度(socklen_t类型)

    virtual std::ostream& insert(std::ostream& os) const = 0;   // 将地址转成字符串插入到流
    std::string toString() const;                               // 返回字符串格式的地址

    bool operator<(const Address& rhs) const;
    bool operator==(const Address& rhs) const;
    bool operator!=(const Address& rhs) const; 
};

Address::ptr Address::Create(const sockaddr* addr, socklen_t addrlen) {
    if(addr == nullptr) {
        return nullptr;
    }
    Address::ptr result;
    // 根据地址协议族创建地址
    switch(addr->sa_family) {
        case AF_INET:
            result.reset(new IPv4Address(*(const sockaddr_in*)addr));
            break;
        case AF_INET6:
            result.reset(new IPv6Address(*(const sockaddr_in6*)addr));
            break;
        default:
            result.reset(new UnknownAddress(*addr));
            break;
    }
    return result;
}

bool Address::Lookup(std::vector<Address::ptr>& result, const std::string& host,
                    int family, int type, int protocol) {
    addrinfo hints, *results, *next;
    hints.ai_flags = 0;
    hints.ai_family = family;
    hints.ai_socktype = type;
    hints.ai_protocol = protocol;
    hints.ai_addrlen = 0;
    hints.ai_addr = NULL;
    hints.ai_canonname = NULL;
    hints.ai_next = NULL;

    std::string node;
    const char* service = NULL;

    // 解析字符串地址(设置node和service的值,用于getaddrinfo函数进行DNS查询)
    // 检查 IPv6address service
    if(!host.empty() && host[0] == '[') {
        const char* endipv6 = (const char*)memchr(host.c_str() + 1, ']', host.size() - 1);
        if(endipv6) {
            // todo check out of range
            if(*(endipv6 + 1) == ':') {
                service = endipv6 + 2;
            }
            node = host.substr(1, endipv6 - host.c_str() - 1);
        }
    }

    // 检查 node service
    if(node.empty()) {
        service = (const char*)memchr(host.c_str(), ':', host.size());
        if(service) {
            if(!memchr(service + 1, ':', host.c_str() + host.size() - service - 1)) {
                node = host.substr(0, service - host.c_str());
                service++;
            }
        }
    }

    if(node.empty()) {
        node = host;
    }

    // 使用getaddrinfo(),进行DNS查询,返回地址链表(addrinfo链表)
    int error = getaddrinfo(node.c_str(), service, &hints, &results);
    if(error) {
        SYLAR_LOG_ERROR(g_logger) << "Address::Lookup getaddress(" << host << ","
            << family << ", " << type << ") err=" << error << " errstr="
            << strerror(errno);
        return false;
    }

    // 将地址链表遍历,构造Address对象存入result容器
    next = results;
    while(next) {
        result.push_back(Create(next->ai_addr, (socklen_t)next->ai_addrlen));
        next = next->ai_next;
    }

    freeaddrinfo(results);  // 释放addrinfo指针
    return !result.empty();
}

Address::ptr Address::LookupAny(const std::string& host,
                int family, int type, int protocol) {
    std::vector<Address::ptr> result;
    if(Lookup(result, host, family, type, protocol)) {
        return result[0];   // 返回链表的第一个元素构造的地址
    }
    return nullptr;
}

IPAddress类

派生自Address,仍然为抽象类,同时将作为IPv4Address和IPv6Address的基类,相比Address类新增了Create方法以及获取广播地址,网络段地址和子网掩码地址的虚方法,等待子类各自实现

// 继承自Address的抽象类
class IPAddress : public Address {
public:
    typedef std::shared_ptr
<IPAddress> ptr;

    static IPAddress::ptr Create(const char* address, uint16_t port = 0);   // 创建IPAddress

    virtual IPAddress::ptr broadcastAddress(uint32_t prefix_len) = 0;   // 获取广播地址
    virtual IPAddress::ptr networkAddress(uint32_t prefix_len) = 0;     // 获取网络地址
    virtual IPAddress::ptr subnetAddress(uint32_t prefix_len) = 0;      // 获取子网掩码地址

    virtual uint32_t getPort() const = 0;   // 获取端口号
    virtual void setPort(uint16_t v) = 0;   // 设置端口号
};

IPAddress::ptr IPAddress::Create(const char* address, uint16_t port) {
    addrinfo hints, *results;
    memset(&hints, 0, sizeof(addrinfo));

    hints.ai_family = AF_UNSPEC;

    // 不管是不是纯IP或域名都解析一下,然后使用链表第一个结果
    int error = getaddrinfo(address, NULL, &hints, &results);
    if(error) {
        SYLAR_LOG_ERROR(g_logger) << "IPAddress::Create(" << address
                << ", " << port << " error=" << error
                << " errno=" << errno
                << " errstr=" << strerror(errno);
        return nullptr;
    }

    try {
        // 根据返回的链表的第一个结果创建Address,并转换成IPAddress格式
        IPAddress::ptr result = std::dynamic_pointer_cast
<IPAddress>(
            Address::Create(results->ai_addr, (socklen_t)results->ai_addrlen));
        if(result) {
            // 设置端口
            result->setPort(port);
        }
        freeaddrinfo(results);
        return result;
    } catch (...) {
        freeaddrinfo(results);
        return nullptr;
    }
}

IPv4Address

继承自IPAddress,实现了基类的虚方法,成员变量存储了一个sockaddr_in结构体

// 继承自IPAddress类,表示一个IPv4地址
class IPv4Address : public IPAddress {
public:
    typedef std::shared_ptr
<IPv4Address> ptr;

    static IPv4Address::ptr Create(const char* address, uint16_t port = 0);     // 创建IPv4Address

    IPv4Address(const sockaddr_in& address);                            // 构造函数(通过sockaddr_in)
    IPv4Address(uint32_t address = INADDR_ANY, uint32_t port = 0);      // 构造函数(通过IP地址和端口号)

    const sockaddr* getAddr() const override;               // 获取sockaddr指针
    sockaddr* getAddr() override;                           // 获取sockaddr指针(非const版本)
    socklen_t getAddrLen() const override;                  // 获取sockaddr长度(socklen_t类型)
    std::ostream& insert(std::ostream& os) const override;  // 将地址转成字符串插入到流

    IPAddress::ptr broadcastAddress(uint32_t prefix_len) override;  // 获取广播地址
    IPAddress::ptr networkAddress(uint32_t prefix_len) override;    // 获取网络地址
    IPAddress::ptr subnetAddress(uint32_t prefix_len) override;     // 获取子网掩码地址
    uint32_t getPort() const override;  // 获取端口号
    void setPort(uint16_t v) override;  // 设置端口号
private:
    sockaddr_in m_addr;     //sockaddr_in 结构体,存放IPv4地址
};

// IPv4
IPv4Address::ptr IPv4Address::Create(const char* address, uint16_t port) {
    // 创建一个IPv4Address对象
    IPv4Address::ptr rt(new IPv4Address);
    // 设置端口(转成网络字节序)
    rt->m_addr.sin_port = byteswapOnLittleEndian(port);
    // 将一个IP地址的字符串表示转换成网络字节序的二进制表示
    int result = inet_pton(AF_INET, address, &rt->m_addr.sin_addr);
    if(result <= 0) {
        SYLAR_LOG_ERROR(g_logger) << "IPv4Address::Create(" << address << ", "
                << port << ") rt=" << result << " erron=" << errno
                << " errstr=" << strerror(errno);
        return nullptr;
    }
    // 返回对象
    return rt;
}

IPv4Address::IPv4Address(const sockaddr_in& address) {
    m_addr = address;
}

IPv4Address::IPv4Address(uint32_t address, uint32_t port) {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sin_family = AF_INET;
    m_addr.sin_port = byteswapOnLittleEndian(port);
    m_addr.sin_addr.s_addr = byteswapOnLittleEndian(address);
}

sockaddr* IPv4Address::getAddr() {
    return (sockaddr*)&m_addr;
}

const sockaddr* IPv4Address::getAddr() const {
    return (sockaddr*)&m_addr;
}

socklen_t IPv4Address::getAddrLen() const {
    return sizeof(m_addr);
}

std::ostream& IPv4Address::insert(std::ostream& os) const {
    uint32_t addr = byteswapOnLittleEndian(m_addr.sin_addr.s_addr);
    // 转成十进制数字 + '.' 分割的格式,输出到流
    os << ((addr >> 24) & 0xff) << "."
       << ((addr >> 16) & 0xff) << "."
       << ((addr >> 8) & 0xff) << "."
       << (addr & 0xff);
    os << ":" << byteswapOnLittleEndian(m_addr.sin_port);
    return os;
}

// 获取广播地址
IPAddress::ptr IPv4Address::broadcastAddress(uint32_t prefix_len) {
    if(prefix_len > 32) {
        return nullptr;
    }

    sockaddr_in baddr(m_addr);
    baddr.sin_addr.s_addr |= byteswapOnLittleEndian(
        CreateMask
<uint32_t>(prefix_len)
    );
    return IPv4Address::ptr(new IPv4Address(baddr));
}

// 获取网络地址
IPAddress::ptr IPv4Address::networkAddress(uint32_t prefix_len) {
    if(prefix_len > 32) {
        return nullptr;
    }

    sockaddr_in baddr(m_addr);
    baddr.sin_addr.s_addr &= ~byteswapOnLittleEndian(
        CreateMask
<uint32_t>(prefix_len)
    );
    return IPv4Address::ptr(new IPv4Address(baddr));
}

// 获取子网掩码
IPAddress::ptr IPv4Address::subnetAddress(uint32_t prefix_len) {
    sockaddr_in subnet;
    memset(&subnet, 0, sizeof(subnet));
    subnet.sin_family = AF_INET;
    subnet.sin_addr.s_addr = ~byteswapOnLittleEndian(CreateMask
<uint32_t>(prefix_len));
    return IPv4Address::ptr(new IPv4Address(subnet));
}

uint32_t IPv4Address::getPort() const {
    return byteswapOnLittleEndian(m_addr.sin_port);
}

void IPv4Address::setPort(uint16_t v) {
    m_addr.sin_port = byteswapOnLittleEndian(v);
}

IPv6Address

继承自IPAddress,实现了基类的虚方法,成员变量存储了一个sockaddr_in6结构体

注意IPv6其实没有像IPv4那样的广播地址,sylar为了保证接口一致性就没有删除这些方法

// 继承自IPAddress类,表示一个IPv6地址
class IPv6Address : public IPAddress {
public:
    typedef std::shared_ptr
<IPv6Address> ptr;

    static IPv6Address::ptr Create(const char* address, uint16_t port = 0);     // 创建IPv6Address

    IPv6Address();                                              // 构造函数(默认)
    IPv6Address(const sockaddr_in6& address);                   // 构造函数(通过sockaddr_in6)
    IPv6Address(const uint8_t address[16], uint16_t port = 0);  // 构造函数(通过地址和端口)

    const sockaddr* getAddr() const override;               // 获取sockaddr指针
    sockaddr* getAddr() override;                           // 获取sockaddr指针(非const版本)
    socklen_t getAddrLen() const override;                  // 获取sockaddr长度(socklen_t类型)
    std::ostream& insert(std::ostream& os) const override;  // 将地址转成字符串插入到流

    IPAddress::ptr broadcastAddress(uint32_t prefix_len) override;  // 获取广播地址
    IPAddress::ptr networkAddress(uint32_t prefix_len) override;    // 获取网络地址
    IPAddress::ptr subnetAddress(uint32_t prefix_len) override;     // 获取子网掩码地址
    uint32_t getPort() const override;  // 获取端口号
    void setPort(uint16_t v) override;  // 设置端口号
private:
    sockaddr_in6 m_addr;    // sockaddr_in6 结构体,存放IPv6地址
};

IPv6Address::ptr IPv6Address::Create(const char* address, uint16_t port) {
    IPv6Address::ptr rt(new IPv6Address);
    // 设置端口号
    rt->m_addr.sin6_port = byteswapOnLittleEndian(port);
    // 将字符串表示的IP地址转换成网络字节序的二进制表示
    int result = inet_pton(AF_INET6, address, &rt->m_addr.sin6_addr);
    if(result <= 0) {
        SYLAR_LOG_ERROR(g_logger) << "IPv6Address::Create(" << address << ", "
                << port << ") rt=" << result << " erron=" << errno
                << " errstr=" << strerror(errno);
        return nullptr;
    }
    return rt;
}

IPv6Address::IPv6Address() {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sin6_family = AF_INET6;
}

IPv6Address::IPv6Address(const sockaddr_in6& address) {
    m_addr = address;
}

IPv6Address::IPv6Address(const uint8_t address[16], uint16_t port) {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sin6_family = AF_INET6;
    m_addr.sin6_port = byteswapOnLittleEndian(port);
    memcpy(&m_addr.sin6_addr.s6_addr, address, 16);
}

sockaddr* IPv6Address::getAddr() {
    return (sockaddr*)&m_addr;
}

const sockaddr* IPv6Address::getAddr() const {
    return (sockaddr*)&m_addr;
}

socklen_t IPv6Address::getAddrLen() const {
    return sizeof(m_addr);
}

std::ostream& IPv6Address::insert(std::ostream& os) const {
    os << "[";
    uint16_t* addr = (uint16_t*)m_addr.sin6_addr.s6_addr;
    bool used_zeros = false;
    for(size_t i = 0; i < 8; i++) {
        if(addr[i] == 0 && !used_zeros) {
            continue;
        }
        if(i && addr[i - 1] == 0 && !used_zeros) {
            os << ":";
            used_zeros = true;
        }
        if(i) {
            os << ":";
        }
        os << std::hex << (int)byteswapOnLittleEndian(addr[i]) << std::dec;
    }

    // 若最后一块为0则省略
    if(!used_zeros && addr[7] == 0) {
        os << "::";
    }

    os << "]:" << byteswapOnLittleEndian(m_addr.sin6_port);
    return os;
}

UnixAddress

派生自Address,表示UNIX域套接字地址

// 继承自Address,表示UNIX域套接字地址
class UnixAddress : public Address {
public:
    typedef std::shared_ptr
<UnixAddress> ptr;
    UnixAddress();                          // 构造函数(默认)
    UnixAddress(const std::string& path);   // 构造函数(通过路径字符串)

    const sockaddr* getAddr() const override;               // 获取sockaddr指针
    sockaddr* getAddr() override;                           // 获取sockaddr指针(非const版本)
    socklen_t getAddrLen() const override;                  // 获取地址长度(socklen_t类型)
    void setAddrLen(uint32_t v);                            // 设置地址长度
    std::ostream& insert(std::ostream& os) const override;  // 将地址转成字符串插入到流
private:
    struct sockaddr_un m_addr;  // sockaddr_un结构体,存放UNIX地址
    socklen_t m_length;         // 地址长度
};

// UnixAddress
static const size_t MAX_PATH_LEN = sizeof(((sockaddr_un*)0)->sun_path) - 1;

UnixAddress::UnixAddress() {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sun_family = AF_UNIX;
    m_length = offsetof(sockaddr_un, sun_path) + MAX_PATH_LEN;
}

UnixAddress::UnixAddress(const std::string& path) {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sun_family = AF_UNIX;
    m_length = path.size() + 1;

    if(!path.empty() && path[0] == '\0') {
        m_length--;
    }

    if(m_length > sizeof(m_addr.sun_path)) {
        throw std::logic_error("path too long");
    }
    memcpy(m_addr.sun_path, path.c_str(), m_length);
    m_length += offsetof(sockaddr_un, sun_path);
}

sockaddr* UnixAddress::getAddr() {
    return (sockaddr*)&m_addr;
}

const sockaddr* UnixAddress::getAddr() const {
    return (sockaddr*)&m_addr;
}

socklen_t UnixAddress::getAddrLen() const {
    return m_length;
}

void UnixAddress::setAddrLen(uint32_t v) {
    m_length = v;
}

std::ostream& UnixAddress::insert(std::ostream& os) const {
    if(m_length > offsetof(sockaddr_un, sun_path)
            && m_addr.sun_path[0] == '\0') {
        return os << "\\0" << std::string(m_addr.sun_path + 1,
                m_length - offsetof(sockaddr_un, sun_path) - 1);
    }
    return os << m_addr.sun_path;
}

UnknownAddress

表示未知地址类型

// 继承自Address,表示未知地址类型
class UnknownAddress : public Address {
public:
    typedef std::shared_ptr
<UnknownAddress> ptr;
    UnknownAddress(int famliy);
    UnknownAddress(const sockaddr& addr);
    sockaddr* getAddr() override;
    const sockaddr* getAddr() const override;
    socklen_t getAddrLen() const override;
    std::ostream& insert(std::ostream& os) const override;
private:
    sockaddr m_addr;
};

// UnknowAddress
UnknownAddress::UnknownAddress(int family) {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sa_family = family;
}

UnknownAddress::UnknownAddress(const sockaddr& addr) {
    m_addr = addr;
}

sockaddr* UnknownAddress::getAddr() {
    return (sockaddr*)&m_addr;
}

const sockaddr* UnknownAddress::getAddr() const {
    return &m_addr;
}

socklen_t UnknownAddress::getAddrLen() const {
    return sizeof(m_addr);
}

std::ostream& UnknownAddress::insert(std::ostream& os) const {
    os << "[UnknownAddress family=" << m_addr.sa_family << "]";
    return os;
}

关于字节序

网络字节序采用大端序,故需要为该地址模块提供自动适配的字节序转换功能

上面的各个地址类的实现中使用了sylar编写的字节序转换函数,可以根据主机的配置进行选择性编译,确保字节序正确

之前有一次修改了一点源码然后编译报错几百行,最后发现是endian.h命名和系统头文件重复了,最后明确制定了路径才解决问题

#ifndef __SYLAR_ENDIAN_H__
#define __SYLAR_ENDIAN_H__

#define SYLAR_LITTLE_ENDIAN 1
#define SYLAR_BIG_ENDIAN 2

#include <byteswap.h>
#include <stdint.h>
#include 
<type_traits>

namespace sylar {

// SFINAE,根据模板函数参数类型编译不同版本
template<class T>
typename std::enable_if<sizeof(T) == sizeof(uint64_t), T>::type
byteswap(T value) {
    return (T)bswap_64((uint64_t)value);
}

template<class T>
typename std::enable_if<sizeof(T) == sizeof(uint32_t), T>::type
byteswap(T value) {
    return (T)bswap_32((uint32_t)value);
}

template<class T>
typename std::enable_if<sizeof(T) == sizeof(uint16_t), T>::type
byteswap(T value) {
    return (T)bswap_16((uint16_t)value);
}

// 设置主机字节序宏
#if BYTE_ORDER == BIG_ENDIAN
#define SYLAR_BYTE_ORDER SYLAR_BIG_ENDIAN
#else
#define SYLAR_BYTE_ORDER SYLAR_LITTLE_ENDIAN
#endif

// 以下是对byteswapOnLittleEndian()和byteswapOnBigEndian()的条件编译
// byteswapOnLittleEndian()只在小端序机器上byteswap,大端序机器什么都不做(可以用来转换成网络字节序)
// byteswapOnBigEndian()只在大端序机器上byteswap,小端序机器什么都不做

#if SYLAR_BYTE_ORDER == SYLAR_BIG_ENDIAN    // 大端序机器编译
template<class T>   
T byteswapOnLittleEndian(T t) {
    return t;
}

template<class T>   
T byteswapOnBigEndian(T t) {
    return byteswap(t);
}

#else   // 小端序机器编译

template<class T>   
T byteswapOnLittleEndian(T t) {
    return byteswap(t);
}

template<class T>   
T byteswapOnBigEndian(T t) {
    return t;
}

#endif

}

#endif // __SYLAR_ENDIAN_H__

测试

#include "sylar/address.h"
#include "sylar/log.h"

sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();

void test() {
    std::vector<sylar::Address::ptr> addrs;

    SYLAR_LOG_INFO(g_logger) << "begin";
    bool v = sylar::Address::Lookup(addrs, "www.ayanami.blue");
    SYLAR_LOG_INFO(g_logger) << "end";

    if(!v) {
        SYLAR_LOG_ERROR(g_logger) << "lookup fail";
        return;
    }

    for(size_t i = 0; i < addrs.size(); i++) {
        SYLAR_LOG_INFO(g_logger) << i << " - " << addrs[i]->toString();
    }
}

void test_iface() {
    std::multimap<std::string, std::pair<sylar::Address::ptr, uint32_t>> results;

    bool v = sylar::Address::GetInterfaceAddresses(results);
    if(!v) {
        SYLAR_LOG_ERROR(g_logger) << "GetInterfaceAddress fail";
        return;
    }

    for(auto& i : results) {
        SYLAR_LOG_INFO(g_logger) << i.first << " - " << i.second.first->toString()
                << " - " << i.second.second;
    }
}

void test_ipv4() {
    auto addr = sylar::IPAddress::Create("www.ayanami.blue");
    // auto addr = sylar::IPAddress::Create("127.0.0.8");
    if(addr) {
        SYLAR_LOG_INFO(g_logger) << addr->toString();
    }
}

void test_addr() {
    {
        auto addr  = sylar::IPv4Address::Create("192.168.123.114");
        auto addr1 = addr->broadcastAddress(16);
        auto addr2 = addr->networkAddress(16);
        auto addr3 = addr->subnetAddress(16);
        SYLAR_LOG_INFO(g_logger) << addr->toString();
        SYLAR_LOG_INFO(g_logger) << addr1->toString();
        SYLAR_LOG_INFO(g_logger) << addr2->toString();
        SYLAR_LOG_INFO(g_logger) << addr3->toString();
    }
    SYLAR_LOG_INFO(g_logger) << "---------------------";

    // {
    //     auto addr  = sylar::IPv6Address::Create("fe80::5fdf:717c:30:d23f");
    //     auto addr1 = addr->broadcastAddress(128);
    //     auto addr2 = addr->networkAddress(128);
    //     auto addr3 = addr->subnetAddress(128);
    //     SYLAR_LOG_INFO(g_logger) << addr->toString();
    //     SYLAR_LOG_INFO(g_logger) << addr1->toString();
    //     SYLAR_LOG_INFO(g_logger) << addr2->toString();
    //     SYLAR_LOG_INFO(g_logger) << addr3->toString();
    // }
}

int main(int argc, char** argv) {

    test();
    std::cout << "============================" << std::endl;
    test_iface();
    std::cout << "============================" << std::endl;
    test_ipv4();
    std::cout << "============================" << std::endl;
    test_addr();

    return 0;
}

可以看到测试结果符合预期

image-20260310211010339

总结

后续网络编程可以很方便地创建和使用地址

评论

  1. sankkooos
    Android Chrome 142.0.7444.173
    2 周前
    2026-3-11 18:29:18

    φ( ̄∇ ̄o)!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇