双栈 TCP 回声服务器 -- v3.0
要将双栈 TCP 回声服务器从线程池模式改造为Reactor 模式,核心是基于I/O 多路复用(epoll) 实现 “事件驱动” 的高效 I/O 处理 —— 通过单线程(或主线程)监听多个 Socket 的 I/O 事件,事件触发时再分发到对应处理器,避免线程上下文切换开销,更适合高并发场景。 一、Reactor 模式核心原理与组件Reactor 模式(反应器模式)是一种事件驱动架构,核心思想是 “将 I/O 事件从业务逻辑中分离”,通过以下组件实现: 组件 作用 Reactor(反应器) 核心调度器:运行事件循环,通过 I/O 多路复用(epoll)等待事件,分发事件到处理器 EventDemultiplexer 事件多路分离器:封装 epoll,负责注册 / 删除事件、等待事件触发(本文用 epoll) EventHandler(处理器) 事件处理接口:定义handleRead/handleWrite/handleError等统一接口,子类实现具体逻辑(如 “新连接处理”“回声...
简单echo服务器 -- v2.0
在原有双栈兼容基础上,新增线程池替代进程、文件日志、超时处理、自定义协议四大核心优化,解决进程开销高、排查难、资源占用、粘包等问题,适用于高并发场景。 一、核心优化方案设计 优化功能 实现思路 线程池优化 设计固定大小线程池(基于pthread),复用线程处理客户端连接,避免频繁创建销毁进程的开销 文件日志系统 实现线程安全的日志类,记录时间戳、日志级别、事件详情,写入本地文件(如echo_server.log) 超时处理 通过setsockopt设置SO_RCVTIMEO/SO_SNDTIMEO,为recv/send设置超时(默认 5 秒) 自定义协议 定义 “4 字节长度头 + 数据” 格式,解决 TCP 粘包问题(长度头用网络字节序传输) 二、通用工具类实现(日志 + 线程池)1. 线程安全的文件日志类(Logger)负责将日志写入文件,支持INFO/ERROR级别,包含时间戳,多线程下通过互斥锁保证安全。 12345678910111213141516171819202122232425262728293031323...
简单echo服务器 -- IPv4/IPv6 双栈兼容
一、核心技术:IPv4/IPv6 双栈兼容的关键设置要实现双栈兼容,需理解四个核心概念:hints.ai_family=AF_UNSPEC、hints.ai_flags=AI_PASSIVE、getaddrinfo函数、INET6_ADDRSTRLEN宏。它们共同解决了 IPv4 与 IPv6 协议差异带来的适配问题。 1. hints.ai_family = AF_UNSPEC:协议无关的地址解析hints是getaddrinfo的查询条件结构体,ai_family指定地址族(协议类型): AF_INET:仅解析 IPv4 地址(对应struct sockaddr_in); AF_INET6:仅解析 IPv6 地址(对应struct sockaddr_in6); AF_UNSPEC:不限制协议,同时解析 IPv4 和 IPv6 地址。 为什么选AF_UNSPEC 现代服务器需同时响应 IPv4 和 IPv6 客户端的连接(例如用户可能通过192.168.100或fe80::1访问)。AF_UNSPEC让getaddrinfo返回...
timefd定时器封装
导言在 Linux 系统开发中,定时器是一个非常常见的需求。除了传统的setitimer、alarm等接口,Linux 还提供了一种基于文件描述符的定时器机制 ——timerfd。这种机制将定时器事件转化为文件描述符的可读事件,非常适合与 I/O 多路复用(如poll、epoll)结合使用。 一、简介timerfd是 Linux 内核 2.6.25 版本后引入的接口,它将定时器功能抽象为一个文件描述符:当定时器到期时,该文件描述符会变为可读状态,我们可以通过read操作获取到期次数,从而处理定时事件。 相比传统定时器,timerfd的优势在于: 可以无缝集成到 I/O 多路复用模型中,无需单独的信号处理逻辑 支持绝对时间和相对时间,支持周期性触发 线程安全,可在多线程环境中安全使用 二、定时器类设计(Timerfd.h)我们首先设计一个Timerfd类,封装timerfd的创建、设置、启动、停止等操作,核心思路是通过回调函数处理定时事件。 12345678910111213141516171819202122232425262728293031323334...
getaddrinfo 查找网络IP
一、网络地址解析的必要性在网络通信中,应用程序通常需要通过域名(如www.example.com)而非直接使用 IP 地址(如93.184.216.34)来定位目标主机,同时需要通过服务名(如http)而非直接使用端口号(如80)来指定通信端口。这种抽象带来了以下核心需求: 用户友好性:人类更容易记忆域名而非数字 IP 协议兼容性:需同时支持 IPv4 与 IPv6,避免硬编码地址类型 服务灵活性:服务端口可能动态分配,通过服务名解析更可靠 网络拓扑适应:同一域名可能对应多个 IP(负载均衡场景),需支持多地址选择 传统地址解析方法(如gethostbyname、getservbyname)存在明显局限性:仅支持 IPv4、无法统一处理主机名与服务名、线程安全性差。getaddrinfo作为 POSIX 标准接口,完美解决了这些问题,是现代 C++ 网络编程的首选地址解析方案。 二、getaddrinfo 函数getaddrinfo是一个统一的地址解析接口,可同时处理主机名→IP 地址、服务名→端口号的解析,并返回可直接用于socket调用的地址结构。 2.1 函数原...
C++ 断言(assert)机制
一、assert 宏的基本语法与工作机制断言是 C++ 标准库提供的调试工具,核心通过头文件(兼容 C 的<assert.h>)中的assert宏实现,其本质是条件检查宏,仅在调试阶段生效。 1.1 核心语法assert宏接收一个布尔表达式作为参数,语法如下: 12345678910111213#include <cassert> // 必须包含的头文件int main() { int* ptr = new int(10); // 检查指针是否非空(调试阶段生效) assert(ptr != nullptr); delete ptr; ptr = nullptr; // 此时断言会失败(指针已置空) assert(ptr != nullptr); return 0;} 1.2 工作机制与 NDEBUG 宏assert的行为完全由 **NDEBUG宏 **("No Debug")控制,这是 C++ 标准规定的编译级开关: 调试模式(未定义NDEBUG): ...
基于Trie树的词频统计与前缀匹配
一、为什么需要 Trie 树?—— 先搞懂核心价值在开始写代码前,我们先明确 Trie 树的 “不可替代性”: 数据结构 插入 / 查询复杂度 前缀匹配能力 内存效率(重复前缀) 适用场景 哈希表(unordered_map) 平均 O (1) 不支持 低(存完整字符串) 单键精准查询(如缓存) 红黑树(map) O(log n) 支持(遍历) 低 有序键值对查询 Trie 树 O (k)(k 为字符串长度) 原生支持 高(前缀共享) 前缀相关操作(自动补全) 简单说:如果你的需求涉及 “前缀”(如输入 “app” 要提示 “apple”“application”),Trie 树是最优解之一。 本文实现的 Trie 树将包含以下核心功能: 单词插入(自动统计重复单词的出现次数) 词频查询(返回单词出现次数,0 表示不存在) 前缀匹配(返回所有以指定前缀开头的单词,支持字典序 / 词频排序) 单词删除(智能回收无用节点,不破坏共享前缀) 整体清空(安全释放所有内存,避免泄漏) 二、代码结构设计 —— 工程化拆分为了保证代码的可维护性,...
C/C++ 中两种结构体 typedef 定义的差异与实践
导言在 C 和 C++ 编程中,结构体(struct)是组织复杂数据的核心工具,而 typedef 则常用于简化类型名、提升代码可读性。实际开发中,我们常会见到两种结构体 + typedef 的定义方式:typedef struct Person{} Person; 与 typedef struct {} Person;。这两种写法看似相似,却因语言特性(C/C++ 差异)和结构体标签(tag)的存在与否,在使用场景、功能限制上有显著区别。 一、基础认知:结构体标签与 typedef 的作用在深入差异前,需先明确两个核心概念: 结构体标签(tag):紧跟 struct 后的标识符(如 struct Person 中的 Person),是结构体的 “原生名称”,仅在 struct 关键字后生效。 typedef 别名:通过 typedef 为类型(包括结构体)定义的简化名称(如 Person),可直接作为类型名使用。 C 和 C++ 对 “结构体标签” 的处理规则不同,这是两种定义方式差异的根源 ——C 语言中,结构体必须通过 struct 标签 或 typede...
Python asyncio事件循环与协程调度深度解析
一、为什么单线程也能"同时"做很多 IO写爬虫或网关时,常遇到这样的场景:要同时发起上千个网络请求,每个请求大部分时间在等——等 DNS、等 TCP 握手、等对端响应。如果用"一个请求一个线程"的模型,上千线程的上下文切换和内存开销会压垮机器;如果"一个请求一个进程",更不现实。 核心矛盾是:CPU 在等 IO 时其实是闲着的,而线程/进程的切换成本却很高。理想状态是——一个线程在等 A 的回复时,去处理 B、C 的就绪事件,等 A 回来了再回头接着 A 干。 **事件循环(event loop)**就是干这件事的调度器:它在一个线程里维护"谁在等 IO、谁已经就绪、谁该到点执行",按需把控制权交给对应的协程。 本质一句话:事件循环是一个单线程的、基于 IO 多路复用的协作式调度器,靠"协程主动让出"而非"系统抢占"来切换任务。 C++ 对照:C++ 标准库没有内建事件循环。要么自己用 epoll/kqueue/IOCP 手写 ...
Python生成器与协程底层深度解析
一、为什么需要"能暂停"的函数普通函数一旦被调用,就一路执行到 return 或抛异常,然后整个调用栈帧销毁,中间状态全部丢失。这在两种场景下很别扭: 处理大序列不能一次性物化。读一个几十 GB 的日志文件,如果用 readlines() 一把读进内存,内存直接爆。你真正想要的是"读一行、处理一行、扔掉一行"的流式能力。 生产/消费节奏不对等。上游产得快、下游吃得慢,或者反过来,你希望双方各自按自己的节奏走,而不是一方阻塞另一方。 生成器(generator)就是 Python 给出的答案:一个可以暂停、也可以恢复执行的函数。它在 yield 处把值交出来并"冻结"整个调用栈帧,下次再被驱动时从冻结点接着跑。 本质一句话:生成器把"函数的执行"变成了一个可以逐步消费的数据流,暂停时连调用栈一起冻结在堆上。 C++ 对照:在 C++20 之前,语言层面没有协程,要么用回调、要么自己用状态机或栈模拟"可暂停";Python 从 PEP 255(2.2)起就有 yiel...
Python pickle 与对象序列化深度解析:把对象连结构带指令一起冻住再复活
一、引言:为什么需要序列化你写好的对象活在内存里,进程一关就没了。可真实工程里,我们常常要把内存里的对象存盘、跨进程传、丢进缓存、或者发到另一台机器——这就离不开"序列化":把活的 Python 对象变成一串能落盘/能传输的字节,再在另一端"复活"成等价对象。 Python 标准库给的方案是 pickle:一行 dumps 把对象冻成字节串,一行 loads 再把它 thaw 回来,几乎零样板。 1234567891011121314import pickleconfig = { "model": "resnet50", "lr": 0.01, "layers": [3, 4, 6, 3], "pretrained": True,}blob = pickle.dumps(config) # 序列化: 对象 -> 字节串restored = pickle.loads(bl...
Python 垃圾回收与循环引用深度解析:引用计数是主力,分代 GC 收拾残局
一、引言:Python 也会"内存泄漏"?很多人对 Python 的印象是"不用管内存"。这话对了一半。 Python 确实没有 C++ 那种"忘了 delete 就泄漏"的日常负担,底层靠**引用计数(reference counting)**在对象不再被引用时立刻回收。但引用计数有一个它自己解决不了的死角:循环引用。两个对象互相指着对方,引用计数永远归不了零,计数机制就失效了。 所以你会在真实项目里看到这种诡异现象:一个处理完的请求对象明明"没人用了",进程 RSS 却缓缓往上爬,爬到几 GB 才被周期性 GC 清掉,严重的甚至一直爬——这就是循环引用漏网。本文把这套机制讲透,并和 C++ 的内存管理正面比对。 123456789101112import tracemalloc, gctracemalloc.start()# 模拟一个"看似释放、实则泄漏"的请求上下文class Ctx: passdef handle(): a = Ctx(); b = Ctx() ...
C++/MySQL/Redis 锁机制 - 1
导言在并发编程与分布式系统中,锁机制是保障数据一致性的核心技术。不同技术栈因运行环境(本地进程 / 数据库 / 分布式集群)差异,锁的实现逻辑、核心特性与适用场景存在显著区别。 一、C++ 锁机制:本地进程内的并发控制C++ 作为系统级编程语言,其锁机制基于操作系统内核态同步原语(如互斥量、信号量)与用户态原子操作实现,核心解决单进程内多线程共享内存的线程安全问题。C++11 及以后通过 <thread>、 <mutex>、 <atomic> 等标准库提供统一锁接口,同时支持自定义锁实现。 1. 互斥锁(std::mutex):C++ 基础悲观锁核心定义C++ 标准库中的基础悲观锁,通过操作系统互斥量(Mutex)实现,保证同一时间只有一个线程进入临界区,其他竞争线程会阻塞等待,直到锁释放。是解决本地线程并发冲突的 “通用方案”。 底层实现 依赖操作系统内核态同步原语(如 Linux 的 pthread_mutex_t、Windows 的 CRITICAL_SECTION); 线程竞争失败时会从用户态切换到内核态,进入阻塞...
线程局部存储
一、TLS 在多线程环境中的关键技术作用 核心定义:线程局部存储(Thread Local Storage,TLS)是多线程编程中的一种内存隔离机制,为每个线程分配独立的内存空间(即 “线程私有副本”),使线程对该空间的数据访问无需竞争锁资源,且数据仅对所属线程可见。 解决的核心问题: 避免多线程数据竞争:当多个线程需使用同一逻辑变量但无需共享时(如线程内计数器),TLS 替代共享内存 + 锁的方案,消除锁开销与死锁风险。 保证线程数据独立性:确保线程在生命周期内的私有数据(如上下文信息、临时计算结果)不被其他线程篡改,维持线程运行稳定性。 简化线程数据管理:无需手动为每个线程分配 / 释放私有内存,由 TLS 机制自动管理内存生命周期(随线程创建而分配,随线程退出而释放)。 二、TLS 的实现机制2.1 静态分配(编译期确定)原理: 在编译阶段,编译器将标注 “线程局部” 的变量(如 C++ 的thread_local、POSIX 的__thread)分配到特定的 TLS 段(ELF 文件中的.tbss/.tls 段),并生成访问该段的指令。 ...
Python描述符与property机制深度解析:把"属性访问"做成一等公民
一、引言:obj.x 背后到底发生了什么写 Python 的人几乎每天都在敲 obj.x,但很少有人追问一句:这一行点号访问,解释器到底干了什么? 直觉上它像 C++ 的 obj.x 字段访问——直接按偏移量取内存。但 Python 里 x 既不是编译期固定的偏移,也不保证每次都返回同一个东西。下面这段代码会让你知道它有多"活": 12345678class C: def __init__(self): self.x = 1c = C()print(c.x) # 1,来自实例 __dict__c.__dict__['x'] = 99print(c.x) # 99,直接改底层字典就变了 输出: 12199 注意:我们没有碰任何方法,只是写了 c.__dict__,c.x 的返回值就变了。这说明 obj.x 不是"取字段",而是一次有协议、可拦截、有查找顺序的访问。这套协议,就是描述符(Descriptor)。 本文顺着"点号访问 → 描述符协议 → pr...
std::tuple 的使用
一、tuple 核心定位与基本特性std::tuple(定义于 头文件)是 C++17 标准库中用于打包多个异构数据类型的轻量级容器,其核心价值在于: 无需定义自定义结构体 / 类,即可承载任意数量的不同类型数据; 配合 C++17 新特性(如类模板参数推导 CTAD、结构化绑定),大幅简化异构数据的创建与访问; 无动态内存分配,内存开销与手动定义的结构体相当,性能高效。 关键区别: 与 std::array:array 仅支持同构类型(如 array<int, 3>),tuple 支持异构类型(如 tuple<int, string, double>); 与 std::pair:pair 仅支持最多 2 个元素,tuple 无元素数量限制。 二、tuple 基本用法(创建与访问)1. 创建方式(C++17 CTAD 特性重点)C++17 引入类模板参数推导(CTAD),创建 tuple 时无需显式指定模板参数,编译器会自动推导类型。 创建方式 代码示例 说明 CTAD 直接初始化 tuple t1(42, &qu...
Python类装饰器实战:用装饰器在类定义时加工类(及C++注册表视角)
一、当"给类加功能"开始重复:注册表、计时、单例、校验写过一点 Python 的人,都见过函数装饰器——@staticmethod、@property、@lru_cache 那一套。但还有一类更狠的:类装饰器。它不是贴在函数上,而是贴在类上: 123@some_decoratorclass MyClass: ... 区别在于:函数装饰器接收"一个函数"、返回"一个函数";类装饰器接收"一个类"、返回"一个类"(或它的替身)。它在 class 语句执行完、类对象刚诞生的那一刻被调用,让你有机会当场加工这个类——加方法、改属性、登记进注册表、甚至换成单例。 什么场景会用到?四个最常见的: 插件注册表:定义 class BM25Scorer,希望它自动进全局 registry,不用在别处再写一行 register(...)。 统一计时/日志:给类里所有方法自动包一层耗时统计。 单例化:让某个配置类无论 new 几次都返回同一实例。 字段校验:类一定义就检查"...
读写锁技术:原理、实现
一、读写锁同步模型与核心概念1.1 核心锁类型定义读写锁(Read-Write Lock)是一种细粒度并发控制机制,通过拆分锁权限解决 “读多写少” 场景下的资源竞争问题,包含两类锁: 读锁(共享锁,Shared Lock):允许多个线程同时持有,适用于只读操作 写锁(排他锁,Exclusive Lock):仅允许单个线程持有,适用于修改操作 1.2 同步控制核心条件读写锁通过严格的权限控制实现并发安全,核心同步规则如下: 锁组合 允许并发? 核心原因 读锁 + 读锁 是 只读操作不修改数据,无竞争 读锁 + 写锁 否 读写操作存在数据一致性冲突 写锁 + 写锁 否 多写操作会导致数据覆盖 1.3 内部状态机设计读写锁通过状态计数器维护锁的持有状态,主流实现采用 “高位存读计数 + 低位存写标记” 的紧凑设计(以 32 位状态为例): 1[31位:读锁持有数量] | [1位:写锁标记(0=无写锁,1=有写锁)] 状态转换逻辑示例: 无锁状态(0x00000000)→ 加读锁 → 0x00000001(读计数 = 1,无写锁) 读锁状...
Python dataclass深度解析:用装饰器干掉手写样板代码
一、写到手酸的样板代码:一个类要抄多少遍但凡用类装数据的 Python 程序员,都写过这种东西: 1234567891011121314151617class Point: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return f"Point(x={self.x}, y={self.y})" def __eq__(self, other): if not isinstance(other, Point): return NotImplemented return self.x == other.x and self.y == other.yp1 = Point(1, 2)p2 = Point(1, 2)print(p1) # Point(x=1, y=2)print(p1 == p2) ...
单元测试在软件工程中的核心价值
一、引言:单元测试的定位与行业标准根据IEEE 829 测试文档标准,单元测试是软件测试体系中最底层、最基础的测试层级,聚焦于 “最小可测试单元”(如函数、类、模块)的功能验证。作为软件开发流程的关键环节,单元测试并非 “可选优化项”,而是经过行业实践验证的 “质量保障基础设施”。截至 2025 年,全球 Top 100 科技企业中,97% 已将单元测试纳入强制开发规范,其核心价值体现在技术合理性、风险控制、协作效率等多维度的综合收益。 二、单元测试的五大核心技术价值(附数据支撑)1. 缺陷前置预防:降低修复成本的关键机制缺陷的发现阶段直接决定修复成本。根据IBM 软件工程研究院的研究数据: 需求阶段发现的缺陷,修复成本为 “1x”; 编码阶段(未做单元测试)发现的缺陷,修复成本升至 “5x”; 系统测试阶段发现的缺陷,修复成本达 “10x”; 生产环境发现的缺陷,修复成本高达 “100x-1000x”。 单元测试可在编码阶段直接拦截 60%-70% 的逻辑缺陷,使缺陷修复成本降低 80% 以上。 2. 保障代码可维护性:支撑系统演进的 “安全网”软件系统的长期价值依...
Python lru_cache深度解析:用备忘录把递归从指数砍到线性
一、一个被忽略的时间黑洞:重复计算写递归的人几乎都写过斐波那契: 12345678910import timedef fib(n): if n < 2: return n return fib(n - 1) + fib(n - 2)t0 = time.perf_counter()print(fib(35))print("耗时:", round(time.perf_counter() - t0, 2), "秒") 输出(64 位 Python): 129227465耗时: 2.13 秒 fib(35) 看起来不大,却把 fib 调用了约 2980 万次。一旦换成 fib(50),基本就卡死了——因为 fib(n-1) 和 fib(n-2) 各自又把 fib(n-3) 算一遍,子树大量重叠。这种"同一个子问题被反复求解"的现象叫重叠子问题(overlapping subproblems),是动态规划与记忆化要解决的核心。 问题来了:既然算过,为什么不复用? 答案是——默认情况下,Pytho...
Python __slots__深度解析:用固定布局干掉实例字典
一、一个被忽略的内存黑洞:每个实例都有一本"字典"写 Python 的人几乎都见过这种类: 1234class Point: def __init__(self, x, y): self.x = x self.y = y 看着简单,但它藏着一个代价。Python 为了实现"运行时随便加属性"的灵活性,默认给每个实例都挂了一本字典 __dict__,用来存 x、y 这些实例变量。这本字典本身是个哈希表,哪怕你只放两个字段,它也要预分配几十个字节的槽位。 做个实验就知道了: 1234567891011import sysclass Plain: def __init__(self, x, y): self.x = x self.y = yp = Plain(1, 2)print("实例大小:", sys.getsizeof(p)) # 实例对象自身print("__dict__ 大小:", sys.getsizeof(...
Python元类深度解析:用「类的类」在创建期定制类(及C++模板元编程视角)
一、引子:当你写下 class 时,到底发生了什么写 Python 久了,很容易把 class 当成"定义一个模板"的声明。但 Python 里没有"声明",只有"执行"。 1234567class Person: name = "Ada" def greet(self): return f"Hi, I'm {self.name}"print(type(Person)) # <class 'type'>print(type(type(Person))) # <class 'type'> 输出: 12<class 'type'><class 'type'> Person 不是某种静态蓝图,它是 type 这个类当场造出来的一个实例。type 就是"类的类&q...
Python异步编程深度解析:asyncio事件循环与async/await(及与多线程对比)
一、引子:为什么 time.sleep 会"卡死"整个服务写过网络爬虫或高并发接口的人,迟早会撞上同一个问题:明明机器有 8 个核、网络在等 I/O,程序却像单车道一样,一个请求没回来,后面的全堵着。 先看一个"看似没问题"的代码: 1234567891011import timedef fetch(name, delay): time.sleep(delay) # 模拟一次网络请求 print(f"{name} 完成,耗时 {delay}s")def main(): for i in range(3): fetch(f"任务{i}", 1)main() 输出: 1234任务0 完成,耗时 1s任务1 完成,耗时 1s任务2 完成,耗时 1s# 总耗时约 3s time.sleep 是阻塞的:线程睡着的这段时间,CPU 什么也干不了,后面的任务只能干等。三个任务串行,总耗时 ...
自定义对象支持 C++ 范围循环(Range-based for)的实现
导言范围循环(C++11 引入)是现代 C++ 中遍历容器的便捷方式,其核心依赖迭代器协议与begin/end 接口。 一、范围循环的底层实现原理C++ 标准规定,对于表达式for (range_declaration : range_expression),编译器会自动将其展开为以下逻辑(伪代码): 123456789// 1. 获取范围的起始与结束迭代器auto __begin = begin(range_expression);auto __end = end(range_expression);// 2. 遍历逻辑:依赖迭代器的 !=、++、* 操作for (; __begin != __end; ++__begin) { range_declaration = *__begin; // 解引用获取元素 loop_statement; // 循环体} 关键依赖接口要支持范围循环,自定义对象需满足: 存在可被调用的 begin() 和 end() 函数(成员函数或非成员函数); begin()...

