tinywebserver项目讲解
tinywebserver项目讲解
一个能支撑起上万并发连接的目讲HTTP服务器,核心代码却不到一万行。目讲tinywebserver就是目讲这样的存在。它不像nginx那样庞大复杂,目讲但把网络编程中最关键的目讲几个模块——线程池、epoll、目讲定时器、目讲日志系统——全部揉碎再重组,目讲用最直白的目讲方式展示了一个服务器该有的骨架。
先看它的目讲线程池设计。传统做法是目讲来一个请求就开一个线程,代价高得吓人。目讲tinywebserver直接预创建固定数量的目讲线程,用任务队列来调度。目讲线程们没事就睡在条件变量上,目讲有活儿了被唤醒,干完继续睡。这种生产者-消费者模型不新鲜,但它的精妙之处在于——任务队列用的是互斥锁加条件变量,而不是自旋锁。为啥?因为大部分时间队列是空的,让线程睡下去比空转省CPU。这个取舍,面试时能讲出一堆道理来。
epoll是它的心脏。主线程负责accept新连接,然后把fd扔给epoll监听。这里有个细节很多人会忽略——它用的是EPOLLET边缘触发模式,而不是水平触发。边缘触发意味着每次数据到达只通知一次,你得一次性把数据读完,否则就丢了。这要求读循环必须调到EAGAIN为止。代码里那几行while循环,看着简单,写错就是线上事故。
定时器模块专门处理非活跃连接。每个连接挂一个时间戳,定期扫描,超时了就关掉。实现用的是升序链表,每次检查从头遍历,复杂度O(n)。有人觉得low,但实际场景中连接数几千个,遍历一次也就几微秒。比起红黑树那套,代码简单得多,维护成本更低。这种“够用就好”的设计哲学,贯穿整个项目。
日志系统双缓冲异步写入。前端攒日志,后端线程定时把缓冲刷到磁盘。好处是写日志不阻塞业务线程,坏处是宕机可能丢最后几秒的日志。但tinywebserver不在乎——它追求的是高吞吐,不是金融级可靠性。
我最欣赏的是它的http解析部分。用状态机逐字符解析请求行、头部、消息体。没有用正则,没有用第三方库,纯粹手写switch-case。代码看着笨,但每一步都清晰可控。遇到畸形请求直接返回400,绝不拖泥带水。这种硬核风格,比那些动不动就上boost的工程舒服多了。
跑个压测你就明白它的脾气。我用webbench压过,并发1000时QPS能到两万左右,响应时间稳定在5ms内。但并发超过5000,性能曲线开始抖——瓶颈在锁竞争上,线程池的任务队列成了热点。这就是单机服务器的天花板,再往上就得上多进程或者io_uring了。
项目里还埋了不少彩蛋。比如信号处理用统一事件源,把SIGPIPE屏蔽掉,防止写已关闭socket导致进程退出。再比如每个连接分配8KB读缓冲,用完就回收,内存碎片控制得很好。这些细节不读源码根本发现不了。
说实话,这项目不是给生产环境用的。它最大的价值是教学——把《UNIX网络编程》那本厚书里的精华,浓缩成一个能跑的demo。你把它啃透了,再去理解nginx或者redis的网络模型,会发现很多熟悉的面孔。
写到最后提醒一句:编译它需要Linux环境,Mac上部分接口行为不同。想跑起来,老老实实开个虚拟机。