curl测试WebSocket返回400因缺少Upgrade头,需手动添加Connection、Upgrade、Sec-WebSocket-Version、Sec-WebSocket-Key四条头;并发写需单协程+channel;读写须设超时并处理心跳与关闭。

为什么用 curl 测试 WebSocket 总是返回 400 或 pending
不是服务没跑起来,是 curl 默认不发 WebSocket 升级头。浏览器 new WebSocket() 会自动带 Upgrade: websocket 和 Connection: Upgrade,而裸 curl 不会。
必须手动补全四条关键头,且 Sec-WebSocket-Key 要随机生成:
curl -i -N \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \ http://localhost:8080/ws
- 漏掉任意一个头,
upgrader.Upgrade()就直接返回websocket: bad handshake - 如果服务监听的是
0.0.0.0:8080但你 curllocalhost:8080,在某些系统(如 macOS Docker)下也会静默失败 - Nginx 反向代理时,还要确认配置了
proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade"
并发测试时 panic: concurrent write to websocket connection 怎么办
这是最常踩的坑:*websocket.Conn 的写操作完全不支持并发——哪怕两个 goroutine 同时调 WriteMessage(),就会立刻 panic。
正确做法是每个连接只配一个「写协程」,所有业务消息走 channel 中转:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
type Client struct {
conn *websocket.Conn
send chan []byte
}
<p>func (c *Client) writer() {
for msg := range c.send {
if err := c.conn.WriteMessage(websocket.TextMessage, msg); err != nil {
break
}
}
c.conn.Close()
}- 别用
for i := 0; i 这类裸并发 - 每个测试 client 必须独占一个
*websocket.Conn,不能复用连接对象 - 写之前建议设一次写超时:
c.conn.SetWriteDeadline(time.Now().Add(2 * time.Second)),防卡死
连接建立后秒断,或 ReadMessage 长时间不返回错误
现象是前端显示已连接,但几秒后断开,或服务端读循环卡住不动。根本原因有两个:HTTP 服务器超时强制断连,或 TCP 层 NAT 超时未触发探测。
-
http.Server的ReadTimeout/WriteTimeout必须设为 0,否则空闲连接会被干掉 - 要主动心跳:服务端调
conn.SetPingHandler(),客户端定期发PingMessage;不响应 pong 就关连接 -
ReadMessage()默认阻塞,需配合SetReadDeadline()控制单次读等待时长,比如每次读前设time.Now().Add(30 * time.Second) - 收到
websocket.CloseMessage类型时,别再写,立即跳出循环并调conn.Close()
压测时 CPU 飙高但连接数上不去
不是代码慢,是底层文件描述符(fd)耗尽或 goroutine 泄漏。典型表现是 lsof -p <pid> | wc -l</pid> 远超预期,pprof 显示大量 runtime.gopark。
- 每个测试 client 必须显式调
conn.Close(),且用defer包裹,别等 GC - 用
semaphore.NewWeighted(int64(maxConns))控制并发 client 数量,避免瞬间打爆 - Linux 下检查
/proc/<pid>/fd</pid>数量,确认没突破ulimit -n;测试前可临时执行ulimit -n 65536 - 注意 Nginx 或云厂商 SLB 的空闲连接超时设置(常见 60s),它比你的服务端更早断连
真实链路验证不只看“连得上”,得校验 seq_id 是否跳变、是否重复、顺序是否一致。这些细节一旦忽略,上线后丢消息或乱序就很难复现。


















