buffer是连续内存块,用于精准字节操作;stream是抽象数据流管道,关注事件驱动的流动过程。buffer适合协议解析,stream适合大文件io搬运。

Buffer是内存块,Stream是数据流管道
Buffer本质是一段可读写的连续内存空间,像C语言里的char数组,你得自己管读写位置、长度、是否越界;Stream则是抽象的数据流动模型,关注“有数据来了”“可以写了”“结束了”这些事件,底层可能用Buffer做载体,但你不直接操作内存。
常见错误现象:把$buffer->write()当成流式推送,结果发现客户端没收到——因为Buffer只是存着,不自动发;而stream.write()在Node里会触发drain或自动调度,Swoole里压根没有stream对象(除非你自己封装)。
使用场景差异:
- 需要拼协议头+体(如自定义TCP帧),用
Swoole\Buffer最直接 - 处理大文件上传/下载、实时日志推送,必须走协程
file或http_response的流式接口,而非攒满Buffer再send - HTTP响应中想分块返回(SSE),靠
$response->write()+$response->flush(),这不是Buffer操作,是响应生命周期控制
Swoole里根本没有原生Stream类
别被Node.js或PHP Stream API带偏了。Swoole的Swoole\Coroutine\Stream只是个协程版文件/Socket封装,不提供pipe()、transform()等高级流语义;它底层用的是read()/write(),和Buffer一样是同步IO包装,不是事件驱动流。
容易踩的坑:
- 误以为
Swoole\Coroutine\Stream::create()能像Node那样链式.pipe(),实际只能单向读或写,无法自动背压 - 在HTTP Server里对
$request->fd直接new Swoole\Coroutine\Stream(),结果连接被提前关闭——因为HTTP请求生命周期由Server管理,不能绕过$response - 想用Stream做TCP粘包处理?不如老实用
Buffer缓存+on('receive')解析,更可控
Buffer适合协议解析,Stream适合IO搬运
Buffer的强项是“精准控制字节”,比如WebSocket帧解析:先读2字节掩码标志,再按MASK KEY解密后续payload,这种随机访问必须用Buffer的substr()、read();Stream只适合“整块吞吐”,比如把一个100MB视频文件从磁盘读出再写入Socket,用Swoole\Coroutine\file\copy()比手动Buffer循环快得多。
性能影响点:
- 频繁
$buffer = new Swoole\Buffer()再unset(),会加剧内存碎片(Swoole常驻进程不回收) - 用
Swoole\Coroutine\Stream读大文件时未设chunk_size,默认4KB太小,导致系统调用过多 - HTTP响应启用了
gzip压缩,再用Buffer拼接body,压缩逻辑可能失效——因为Swoole的压缩发生在响应发送前最后阶段,Buffer只是中间容器
真正要流式响应,别碰Buffer也别造Stream
HTTP Server里实现SSE或长轮询,唯一正解是:$response->header('Content-Type', 'text/event-stream') + 多次$response->write("data: ...\n\n") + 每次后跟$response->flush()。这里flush()不是清Buffer,而是把当前已写入Swoole内部响应缓冲区的内容推到TCP层。
为什么不能自己用Buffer攒数据再write()?因为:
-
$response->write()本身就有缓冲行为,再套一层Buffer纯属冗余 - 没调
$response->flush(),哪怕Buffer写满了2MB,也不发;调了但没设tcp_nodelay => true,Nagle算法还会攒包 - 并发高时,每个连接维护独立Buffer对象,内存占用是Buffer数量 × 平均大小,而
$response的缓冲是复用的
复杂点在于:flush时机和网络栈行为不完全可控,测试时用curl -N看实时输出,别依赖浏览器开发者工具的Network面板——它自带缓冲。











