必须用bufio.reader.readbytes('\n')逐帧读取并校验\r\n结尾,因scanner和readstring('\n')会错误处理\r\n分隔符,导致解析崩溃。

RESP响应字符串不能用strings.Split或bufio.Scanner流式解析,必须用bufio.Reader配合ReadBytes('\n')逐帧读取并校验\r\n结尾——否则遇到$5\r\nhello\r\n会错切成"$5\r"和"hello\r",直接解析崩溃。
为什么ReadString('\n')和Scanner在RESP里必然出错
RESP要求\r\n是硬性分隔符,不是可选换行。而bufio.Scanner默认丢弃\r\n,ReadString('\n')只认\n,遇到$5\r\nhello\r\n时,前者返回"$5"(丢失\r),后者可能阻塞或截断在\r位置。真实字节流里\r是协议一部分,删不得。
- 永远用
bufio.NewReader(conn),不用Scanner - 读一行必须调
r.ReadBytes('\n'),得到完整[]byte - 手动检查
bytes.HasSuffix(b, []byte{'\r','\n'}),不满足就报错或丢弃 - 若末尾不是
\r\n,说明网络粘包或服务端发错,不能继续解析
ReadBytes('\n')后怎么提取有效数据
拿到带\r\n的原始字节后,要剥离结尾、识别类型、再按需提取内容。比如+OK\r\n、:100\r\n、\r\nhello\r\n处理逻辑完全不同。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 先确认
b[0]是'+'、'-'、':'、'$'还是'*' - 简单类型(
+/-/:):跳过首字节,截取到倒数2字节前(去掉\r\n) - 批量字符串(
$):从$后找第一个\r\n,解析中间数字为长度n;若n == -1,直接返回nil;否则往后读n+2字节,再校验末尾是否\r\n,最后取前n字节 - 数组(
*):同理先解析元素个数n,再循环调用自身递归读取n次
批量字符串($N\r\n...)最常踩的三个坑
$类型看着简单,但实际是RESP解析里崩溃率最高的环节,尤其在网络不稳定或值含二进制数据时。
- 误把
$-1\r\n当普通字符串处理——它代表nil,必须单独判断,否则make([]byte, -1)panic - 读内容时只调
io.ReadFull(r, buf[:n]),漏掉\r\n——结果\r\n留在缓冲区,下一次ReadBytes('\n')立刻返回空行或错位 - 没校验内容末尾的
\r\n——如果服务端发来$5\r\nhellox(少一个\n),你直接截5字节,后面字节全乱套
流式解析必须应对半包与粘包
一次Read()不保证返回完整RESP帧。比如*2\r\n$3\r\nGET\r\n$3\r\nfoo\r\n可能被拆成*2\r\n$3\r\nG和ET\r\n$3\r\nfoo\r\n两段。不能依赖“一行一帧”假设。
- 解析器内部需维护状态机,比如当前是否在读
$后长度、是否等待\r\n、是否在读内容体 - 对
$N,先读完N+2字节才认为该bulk结束;中间任何io.ErrUnexpectedEOF都得缓存已读字节,等下次Read()续上 - 数组嵌套时,栈深度需限制(如最大10层),防恶意构造导致栈溢出
- 调试时用
tcpdump -i lo port 6379 -A看原始字节流,比日志更准
真正难的不是写对一个SET key val,而是让解析器在$-1、$0\r\n\r\n、*0\r\n、超长二进制值、中途断网这些边界情况下不panic、不卡死、不内存泄漏——这些点往往被忽略,直到压测才暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










