【NowCoder】Network

PT_INET理解

PF_INETAF_INET的区别是网络编程中的一个基础且重要的问题。它们看似不同,但关系紧密。为了帮你快速把握核心信息,我先用一个表格总结它们的对比,然后再深入探讨细节和使用建议。

特性PF_INETAF_INET
全称Protocol Family (INET)Address Family (INET)
主要用途创建 socket 时指定协议族设置地址结构时指定地址族
常用场景socket()函数的第一个参数sockaddr_in结构中的 sin_family字段
理论要求应使用 PF_INET应使用 AF_INET
实际值AF_INET值相同,通常可混用PF_INET值相同,通常可混用

🌐 核心联系与历史渊源

PF_INETAF_INET都指向同一个互联网协议族(IPv4),它们的数值定义是相同的。这种设计有其历史原因。

早期的 BSD 套接字实现试图通过不同的符号(PF_INET用于协议族,AF_INET用于地址族)来为未来可能的分离预留空间。但事实上,这种协议族和地址族的一一对应关系从未被打破,所以 PF_INETAF_INET最终始终具有相同的值。

💻 不同平台下的细微差别

虽然在绝大多数情况下,两者可以互换而不出问题,但在不同系统中还是有些细微差别:

  • Windows 平台:在 Winsock2.h头文件中,PF_INET直接被定义为 AF_INET,这意味着它们完全等同
  • Unix/Linux/BSD 平台:在不同的版本中,这两者可能存在非常微小的差别。例如,在 BSD 系统中更倾向于使用 AF,而在 POSIX 标准中则更倾向于使用 PF

📝 使用建议与代码示例

虽然混用一般无碍,但遵循规范的做法能让代码意图更清晰,可读性更好:

  • 规范写法:在调用 socket()函数时使用 PF_INET,在初始化 socket 地址结构(如 struct sockaddr_in)时使用 AF_INET

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main() {
        // 创建 socket 时使用 PF_INET 指定协议族
        int sockfd = socket(PF_INET, SOCK_STREAM, 0);
        if (sockfd == -1) {
            // 错误处理
            perror("socket creation failed");
            exit(EXIT_FAILURE);
        }
    
        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(serv_addr));
    
        // 设置地址结构时使用 AF_INET 指定地址族
        serv_addr.sin_family = AF_INET; // 地址族 IPv4
        serv_addr.sin_port = htons(8080); // 端口号
        inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr); // IP地址
    
        // ... (绑定、连接、读写等操作)
    
        close(sockfd);
        return 0;
    }
    
  • 常见混用情况:正如搜索结果中提到,许多网络编程的示例和代码中,在 socket()调用时直接使用 AF_INET也非常普遍,并且能正常工作。

    // 这种写法很常见,且能正常工作,但不符合严格规范
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    

🎯 记住要点

  1. 本质相同PF_INETAF_INET的值相同,均代表 IPv4 协议族,在绝大多数情况下互换不会引起问题。
  2. 规范与意图:按照设计初衷使用(PF_INET用于协议,AF_INET用于地址)是更规范的做法,能使代码语义更清晰。
  3. 可移植性:如果你非常关心代码的标准性和可移植性,遵循规范写法是更安全的选择。如果只是编写一般应用程序,意识到它们可以互换即可。

希望这些解释能帮助你彻底理解 PF_INETAF_INET。 Happy Coding!

本地广播 & 环回测试

本地广播地址和环回测试地址是网络中的两个特殊地址,它们用途完全不同。为了让你能快速把握核心信息,我先用一个表格总结它们的对比,然后再深入探讨细节。

特性本地广播地址 (Local Broadcast Address)环回测试地址 (Loopback Address)
地址形式255.255.255.255 (有限广播) 或 网络号全1的主机地址 (定向广播)127.0.0.1 (最常用),或 127.0.0.0/8 网段内任何地址
通信范围同一局域网(广播域)内的所有设备仅限于本机内部,数据不离开主机
数据流向数据会通过物理网卡发出,但通常被路由器隔离数据不经过物理网卡,由操作系统内部直接回送
主要用途向网络中所有设备发送数据(如DHCP请求、ARP解析)测试本机网络协议栈、服务或应用程序是否正常工作
路由器处理不转发255.255.255.255;可能转发或丢弃定向广播(因安全原因常被禁用)不涉及路由器,数据根本不会进入物理网络
举例ping 255.255.255.255(通知同局域网所有设备)ping 127.0.0.1(测试本机TCP/IP协议栈是否正常)

🌐 深入理解本地广播地址

本地广播地址,特别是 255.255.255.255,也称为有限广播地址。它用于当设备(如在启动阶段)还不知道自己所处网络详情时,向同一物理网络段(广播域)内的所有设备发送数据包。一个常见的用途是无盘工作站通过DHCP或BOOTP服务器获取IP地址的初始配置过程。

另一种形式是定向广播地址,格式为 网络号.全1的主机号(例如,在 192.168.1.0/24网络中,广播地址是 192.168.1.255)。这种广播针对特定网络,但出于安全考虑,路由器通常默认不转发此类广播流量。

🖥️ 深入理解环回测试地址

环回地址最著名的是 127.0.0.1,主机名通常对应 localhost。但整个 127.0.0.0/8(从 127.0.0.1127.255.255.254)的地址块都保留用于环回。发送到这些地址的数据包不会通过物理网卡传输到外部网络,而是由操作系统内部直接“回送”给本机。这使得它非常适合:

  • 测试本机网络协议栈:例如,ping 127.0.0.1成功说明本机TCP/IP协议栈工作正常。
  • 本地服务开发和调试:在本地搭建Web服务器、数据库等,并通过 http://127.0.0.1:端口号进行访问测试,无需网络连接。

💡 核心区别与联系

简单来说:

  • 本地广播是向局域网内所有邻居喊话(“大家好!”),但声音传不出你家小区(广播域)。
  • 环回测试自言自语(“我的嘴巴和耳朵没问题吧?”),声音根本不出你家门(本机)。

它们的共同点在于,其数据包通常都不会被路由器转发到互联网上(255.255.255.255的广播绝对不转发,环回流量则不经过网卡)。

希望这些解释能帮助你清晰理解本地广播地址和环回测试地址的区别。

特殊地址

在网络编程中,除了广播地址和环回地址,确实还有一些其他的特殊IP地址需要特别注意。它们各自有独特的用途和限制,理解这些对于设计正确的网络通信逻辑至关重要。

下面这个表格汇总了这些常见的特殊地址及其主要特点,方便你快速了解:

地址类型地址范围主要用途注意事项
网络地址主机号全为0的地址,如 192.168.1.0标识一个网络本身不能分配给任何具体的主机使用
全网广播地址255.255.255.255本地物理网络(广播域)中的所有设备发送数据包路由器通常不转发此类广播,以避免广播风暴。
私有地址10.0.0.0/8 172.16.0.0/12 192.168.0.0/16用于内部网络,在互联网上不可路由通过NAT(网络地址转换) 设备将其转换为公网地址后,才能与互联网通信。
自动私有地址 (APIPA)169.254.0.0/16当设备(如启用DHCP的Windows主机)无法从DHCP服务器获取IP时,系统自动分配通常意味着网络配置(如DHCP服务器或网络连接)有问题,只能与同一网段内同为169.254开头的主机通信。
组播地址 (D类地址)224.0.0.0~ 239.255.255.255用于一对多的通信,数据包发送到一个组播组,该组的所有成员都能收到224.0.0.1所有主机224.0.0.2所有路由器。组播需要特定的协议支持(如IGMP)。
默认路由/任意地址0.0.0.0在服务器编程中,表示监听本机所有IPv4地址。在路由中,表示默认路由当程序需要监听所有网络接口上的连接时,常会绑定到 0.0.0.0

🖧 深入了解与使用场景

  • 网络地址与全网广播地址:这两个地址通常在同一网段内成对出现。例如,在 192.168.1.0/24这个网段中:
    • 网络地址192.168.1.0,它代表这个网络本身。
    • 定向广播地址192.168.1.255,向这个地址发送数据,该网段内所有主机都会接收。
    • 全网广播地址 255.255.255.255也会被同一广播域内的所有主机接收,但路由器默认不转发它,因此其作用范围通常局限于发送者所在的本地网络。
  • 私有地址与NAT:由于公网IPv4地址短缺,我们的家庭和办公室局域网内部普遍使用私有地址(如路由器常分配的 192.168.x.x)。当这些设备需要访问互联网时,NAT设备(如家用路由器)会负责将私有IP地址和端口映射为一个公网IP地址和端口,从而实现对外通信。
  • 组播地址:组播比广播更高效,因为它只将数据包发送给加入特定组播组的成员,而不是网络中的所有主机。常见的组播地址有:
    • 224.0.0.1:所有主机组播地址。
    • 224.0.0.2:所有路由器组播地址。
    • 224.0.0.9:常用于RIP(路由信息协议)版本2。
    • 239.255.255.250:用于SSDP(简单服务发现协议),如UPnP设备发现。
  • 默认路由/任意地址 (0.0.0.0):在网络编程中,当服务器程序绑定到 0.0.0.0,意味着它会监听机器上所有网络接口(如有线网卡、无线网卡)上的指定端口。例如,一个Web服务器绑定 0.0.0.0:80,那么无论是通过本地回环地址 127.0.0.1、局域网IP 192.168.1.100还是其他任何配置在本机的IP地址,都能访问到这个服务。

💡 总结

理解这些特殊IP地址的用途和限制,对于网络编程、网络故障排除以及网络规划都至关重要。

希望这些信息能帮助你更好地理解网络编程中的特殊地址。如果你对某个特定地址的用法或场景有更深入的疑问,我很乐意继续探讨。

0.0.0.0

。0.0.0.0 在 IPv4 协议中是一个特殊的保留地址,它只能作为源地址使用,而不能作为目的地址。这主要是由它的设计用途和网络协议规范决定的。

特殊IP地址主要用途可作为源地址可作目的地址备注
0.0.0.0表示"本网络上的本主机",常用于DHCP初始化、默认路由、服务监听所有接口代表"未知"或"任意",不指向具体设备
127.0.0.1环回测试,用于本机内部进程间通信数据包不离开主机,仅在操作系统内部回送
255.255.255.255受限广播地址,向本地物理网络(广播域)内的所有设备发送数据包路由器不转发此类广播
169.254.x.x自动专用IP地址(APIPA),当DHCP失败时系统自动分配通常意味着网络配置有问题
224.0.0.1组播地址(所有主机组)用于一对多通信,数据包发送给加入特定组播组的成员

🌐 深入理解 0.0.0.0

0.0.0.0 被称为"未指定地址"(unspecified address),它更像一个通配符,代表一种"未知"或"任意"的状态,而非一个具体的目标。

  • 设计初衷与协议规定:根据 RFC 1122 等网络协议规范,0.0.0.0 被明确定义为不能出现在 IP 数据包的目的地址字段。它的角色是源地址的"占位符",用于标识一个尚未获得有效IP地址的主机,或者代表"所有可能的地址"。
  • 主要应用场景
    • DHCP过程:当设备(如电脑)启动且未分配IP时,会用 0.0.0.0作为源地址广播DHCP请求,询问网络中可用的IP地址。此时设备身份未明,故用 0.0.0.0表示"无地址"。
    • 默认路由:在路由表中,0.0.0.0/0表示默认路由或"任意网络"。当数据包的目的IP与路由表中其他条目都不匹配时,会使用这条默认路由。
    • 服务器监听:在服务器编程中,将服务绑定到 0.0.0.0表示监听本机所有网络接口(如有线网卡、无线网卡)上的请求。这使得外部设备可以通过该服务器的任一IP地址访问服务。

⚠️ 注意事项

  • 安全风险:在服务器配置中,将服务绑定到 0.0.0.0会监听所有接口,这可能意外暴露服务到公网,增加安全风险。建议根据实际需求,谨慎配置防火墙规则。
  • 无法 ping 通:由于 0.0.0.0不代表一个具体的可路由的终端设备,你无法 ping 通 0.0.0.0

💎 总结

简单来说,0.0.0.0 代表的是"还没有身份"或"任意身份",因此它适合作为一个请求的起点(源地址),但无法作为一个明确的、可抵达的终点(目的地址)。网络协议这样规定,是为了避免在数据包投递时出现歧义和混淆。

希望这些信息能帮助你理解 0.0.0.0 这个特殊地址的行为。

网络接口

当一个程序需要"监听所有网络接口上的连接"时,意味着它已经做好准备,可以通过该主机上任何一个有效的IP地址来接收来自外部客户端的连接请求。这就像是你家的房子在所有可能的大门(前门、后门、车库门)都安排了接待人员,等待任何一扇门外有人敲门。

为了让你快速了解"所有网络接口"通常包括哪些,我用一个表格来汇总:

接口类型典型地址范围/示例可被访问的范围备注
环回接口127.0.0.1 (IPv4), ::1 (IPv6)仅本机内部用于本机进程间通信,数据不离开主机
有线/无线网卡192.168.1.100, 10.0.0.2 (IPv4 私有地址)同一局域网或通过路由/公网可达最常见的"物理"或"无线"网络接口
Docker/虚拟网卡172.17.0.1, 其他虚拟网络分配的地址取决于虚拟网络配置用于容器或虚拟机之间的通信

🌐 深入理解“网络接口”与“监听”

  • 网络接口:是你设备上进行网络通信的"门户"或"通道"。一台设备可以有多个网络接口,例如物理网卡(有线以太网、无线Wi-Fi)、虚拟接口(如VPN、Docker创建的虚拟网卡)以及环回接口(loopback,用于本机内部通信)。
  • 监听:在服务器编程中,“监听"是一个特定动作。服务器程序通过操作系统 API(如 socket, bind, listen在一个或多个特定的端口号上等待连接请求。例如,Web服务器通常在80端口监听。

当程序配置为监听"所有网络接口"时,它通常通过绑定一个特殊的 IP地址 0.0.0.0 (IPv4) 或 ::(IPv6) 来实现。这告诉操作系统:”我不挑入口,只要是通过我机器上任何一个网络接口、发往我监听的这个端口的连接请求,我都接受"。

💻 常见场景与命令示例

  • Web服务器:像Nginx、Apache这类服务,通常需要监听所有接口,以便既能通过本地回环地址(127.0.0.1)调试,也能通过局域网IP(如192.168.1.100)供内网用户访问,还可能通过公网IP被互联网用户访问。
  • 文件与打印机共享:Windows网络共享或Samba服务需要监听所有接口,以响应局域网内其他电脑的访问请求。
  • 数据库服务器:某些情况下(如分布式应用),数据库服务(如MySQL、Redis)可能需要监听所有接口,以接受来自不同服务器的连接。
  • 游戏联机或P2P软件:这些软件需要监听所有接口,以便与互联网上的其他玩家或节点建立直接连接。

你可以使用 netstat -anss -tuln 命令来查看当前系统中哪些端口正在监听,以及它们监听的地址是特定的还是所有接口(显示为 0.0.0.0::)。例如,TCP 0.0.0.0:80 LISTENING表示该进程正在所有IPv4接口上监听80端口。

🔍 检查监听端口的命令示例 (Windows)

netstat -ano | findstr :80

🔍 检查监听端口的命令示例 (Linux)

ss -tuln | grep :80

⚠️ 安全提示

将服务绑定到所有接口(0.0.0.0)虽然方便,但也意味着暴露的点变多了。因此务必:

  1. 做好防火墙配置,只允许必要的IP地址访问相应的端口。
  2. 确保服务本身有良好的安全措施,如强密码、加密通信(TLS/SSL)。
  3. 如果服务只需要在本地使用,应将其绑定到 127.0.0.1,这样更安全。

希望这些解释能帮助你理解"监听所有网络接口"的含义。

CIDR

CIDR(Classless Inter-Domain Routing,无类别域间路由)是现代互联网网络寻址和路由的核心技术之一。它通过更灵活的IP地址管理方式,有效缓解了IPv4地址短缺问题,并优化了网络路由效率。下面我将为你详细解释CIDR。

📜 CIDR 出现的原因

在 CIDR 出现之前,互联网主要使用 有类编址(Classful Addressing),即IP地址被划分为固定的A、B、C、D、E类:

  • A类:前8位为网络号,后24位为主机号,子网掩码为 255.0.0.0(/8),支持大量主机。
  • B类:前16位为网络号,后16位为主机号,子网掩码为 255.255.0.0(/16)。
  • C类:前24位为网络号,后8位为主机号,子网掩码为 255.255.255.0(/24),主机数量较少。

这种固定分类方式非常不灵活,容易导致IP地址的严重浪费。例如,一个需要300个IP地址的公司,分配一个C类网段(最多254个主机)不够用,但分配一个B类网段(65534个主机)又会造成极大浪费。CIDR 的提出,正是为了克服有类编址的这些缺点,它消除了传统的A类、B类和C类地址以及划分子网的概念。

🧮 CIDR 的核心概念与表示法

CIDR 的核心思想是使用可变长度子网掩码(VLSM),不再受限于固定的8、16或24位网络掩码。它采用 “IP地址/前缀长度” 的格式来表示一个网络地址块:

  • IP地址:通常是一个网络段的起始地址。
  • 前缀长度(Prefix Length):用一个斜杠(/)后跟数字表示,指明了网络部分占用的位数。例如,192.168.1.0/24表示前24位是网络前缀,剩下的8位用于主机编址。

通过这种表示法,可以快速计算出该网段的子网掩码、包含的IP地址范围以及可用的主机数量。

🔢 CIDR 地址块计算理解

理解CIDR的关键在于掌握其计算方式,下表展示了不同CIDR前缀长度对应的子网掩码和可用IP地址数量(以IPv4为例):

CIDR 表示法子网掩码网络部分主机部分总IP地址数可用主机数适用场景
/24255.255.255.024位8位256254小型局域网(如家庭、小办公室)
/25255.255.255.12825位7位128126中小型子网
/26255.255.255.19226位6位6462中型子网
/27255.255.255.22427位5位3230小型子网(如部门网络)
/28255.255.255.24028位4位1614小型子网(如网络设备)
/30255.255.255.25230位2位42点对点链路

计算示例

192.168.1.0/26为例:

  • 子网掩码:前缀长度为26,即子网掩码为 255.255.255.192
  • 可用主机数:主机部分有6位 (32-26=6),所以有 2^(32-26) = 64 个IP地址,扣除网络地址(192.168.1.0)和广播地址(192.168.1.63),可用主机地址为62个。
  • IP地址范围:192.168.1.0 到 192.168.1.63。

CIDR 的主要优势

CIDR 带来了几个显著的好处:

  1. 提高IP地址利用率:允许根据实际需要分配合适大小的地址块,极大地减少了IP地址的浪费。
  2. 减少路由表条目(路由聚合):CIDR允许将多个连续的小网络地址块合并(聚合)为一个更大的网络地址块在路由表中通告。例如,将 192.168.0.0/24192.168.1.0/24192.168.2.0/24192.168.3.0/24聚合为 192.168.0.0/22。这显著减少了全球互联网路由表的规模,提高了路由器的处理和查找效率。
  3. 增强灵活性:网络管理员可以非常灵活地划分不同规模的子网,更好地适应各种规模的网络需求。

🌐 CIDR 的实际应用

CIDR 技术广泛应用于以下场景:

  • ISP分配IP地址:互联网服务提供商(ISP)可以根据客户规模分配不同大小的CIDR地址块。
  • 企业内部子网划分:企业可以在获得的IP地址段内,根据需要进一步划分子网(如不同部门、不同楼层使用不同子网)。
  • 路由聚合:如前所述,在网络边界路由器上聚合路由,优化网络性能。
  • 构建超网:将多个连续的C类网络合并成一个更大的地址块,相当于反向子网划分。

🔍 CIDR 与 VLSM 和路由聚合

  • VLSM:CIDR的实现依赖于VLSM。它允许在一个网络内使用不同的掩码进行多次子网划分,从而实现更精细的地址规划。
  • 路由聚合:是CIDR的核心应用之一,它通过在网络边界将多个连续地址块合并为一个前缀更短的路由通告,来减少路由表条目。

⚖️ CIDR 的注意事项

使用CIDR时,需要注意:

  • 地址分配规划:不合理的CIDR地址块分配可能导致后续无法进行有效的路由聚合。
  • 非连续地址问题:路由聚合要求被聚合的地址块是连续的。

🧭 CIDR 与 IPv6

CIDR的原则同样适用于IPv6。IPv6地址本身就采用CIDR表示法,例如 2001:0db8::/32。IPv6巨大的地址空间使得地址分配更加灵活,CIDR在其中继续发挥着高效管理路由的作用。

💎 总结

CIDR通过消除传统IP地址分类的界限,引入了可变长子网掩码和“IP地址/前缀长度”的表示法,极大地提高了IP地址空间的利用率,并通过路由聚合有效控制了全球路由表的规模。它是现代互联网能够持续发展和高效运行的重要基石之一。

希望以上信息能帮助你全面理解CIDR。如果你对特定场景下的CIDR应用或计算有更多疑问,我很乐意继续探讨。

慢启动

当发送方发生超时时,会进入慢启动阶段:

  1. 超时时刻(t时刻),拥塞窗口cwnd从8KB变为1KB(MSS大小)
  2. 慢启动阶段,每收到一个ACK,cwnd增加1个MSS
  3. 当cwnd达到慢启动阈值ssthresh时(原拥塞窗口的一半,即4KB),进入拥塞避免阶段
  4. 在拥塞避免阶段,每过一个RTT,cwnd增加1个MSS

计算过程: - 前2个RTT处于慢启动阶段:1KB→2KB→4KB - 后8个RTT处于拥塞避免阶段:每RTT增加1KB - 4KB + 8KB = 12KB(拥塞窗口cwnd) - 最终发送窗口 = min(接收窗口rwnd, 拥塞窗口cwnd) = min(10KB, 12KB) = 10KB

TCP拥塞控制的慢启动阶段,其核心机制是 “每收到一个ACK,拥塞窗口(cwnd)就增加1个MSS”,这确实会导致 “每个往返时间(RTT),cwnd大致翻倍” 的效果。这是因为ACK的返回速率反映了网络当前的数据交付速率。

为了更直观地理解这个动态过程,下面这个表格模拟了慢启动初期cwnd的增长情况(假设初始cwnd=2 MSS,无丢包):

发送轮次发送数据量 (单位: MSS)收到ACK数量cwnd 增量 (单位: MSS)cwnd 增长后总值 (单位: MSS)阶段说明
初始---2连接建立,初始cwnd通常为1或2 MSS
第1个RTT22+24发送2个报文,收到2个ACK,cwnd从2变为4
第2个RTT44+48cwnd=4,可发送4个报文,收到4个ACK后变为8
第3个RTT88+816cwnd=8,发送8个报文,收到8个ACK后变为16

从表格可以看出,每一个RTT内,cwnd的大小都大约是上一个RTT时的两倍,呈现出指数级增长的趋势。

🧠 原理深入分析

这种增长模式背后的逻辑如下:

  1. ACK是确认,也是“指令”:TCP规定,发送方每收到一个对新数据的确认(ACK),就可以将cwnd增加最多1个MSS。这意味着ACK的到达速率直接决定了cwnd的增长速度。
  2. RTT与ACK的关系:在一个理想的、无丢包的网络中,发送方在一个RTT开始时发送一整个窗口的数据(cwnd个报文),那么它将会在这个RTT结束时,收到大致相同数量的ACK(假设接收方每收到一个报文就回复一个ACK)。
  3. 数学关系
    • 设当前RTT开始时,cwnd = N
    • 发送方会发出N个数据包。
    • 在一个RTT后,发送方预计会收到N个ACK。
    • 根据慢启动算法,每收到一个ACK,cwnd增加1。因此,cwnd总共增加了N
    • 所以,下一个RTT开始时,cwnd = N + N = 2N

因此,从效果上看,每经过一个RTT,拥塞窗口的大小就会翻倍

⚠️ 重要说明

  • “慢启动”并不慢:这个名字有点误导性。它指的是开始时以很小的窗口(1或2个MSS)“谨慎”启动,但其增长速率(指数增长)其实非常快,目的是为了迅速探测出网络的可用带宽容量。
  • 现实世界的限制:这种理想的翻倍增长不会无限持续下去。它主要发生在网络状况良好、没有丢包、且接收窗口(rwnd)足够大的情况下。增长过程会在以下情况下停止:
    • 达到慢启动阈值(ssthresh),随后转为拥塞避免阶段(线性增长)。
    • 检测到数据包丢失(超时或收到3个重复ACK),这时会触发拥塞控制机制,大幅减小cwnd

希望以上的解释和模拟能帮助你理解TCP慢启动中这个巧妙的设计!

Licensed under CC BY-NC-SA 4.0
Last updated on Sep 09, 2025 19:05 CST
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy