Golang中处理WebSocket写入错误的解决方案
Golang中处理WebSocket写入错误的解决方案 我将WebSocket连接保存在一个使用UUID作为键的映射中,例如:
sockets["123456"] = *websocket.Conn
并且有一个将消息传递到通道的函数:
func (mr MessageRequest) Send(msg []byte) {
mr.mu.RLock()
defer mr.mu.RUnlock()
for _, socket := range mr.sockets {
mr.chat.broadcast <- Message{
socket: socket,
message: msg,
chat: mr.chat,
}
}
}
处理消息的通道如下:
case mr := <-c.broadcast:
msg := mr.message
if c.sockets[mr.socket] != nil {
if err := c.sockets[mr.socket].WriteMessage(websocket.TextMessage, msg); err != nil {
c.sockets[mr.socket].SetWriteDeadline(time.Now().Add(10 * time.Second))
c.sockets[mr.socket].WriteMessage(websocket.CloseMessage, []byte{})
c.sockets[mr.socket].Close()
go c.Unregister(c.sockets[mr.socket])
}
}
当有许多客户端连接,并且一些客户端快速断开连接时,问题出现在这一行:
if err := c.sockets[mr.socket].WriteMessage(websocket.TextMessage, msg); err != nil
错误信息是:
panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x139b430] goroutine 138 [running]: github.com/fasthttp/websocket.(*Conn).WriteMessage(0x164f3c0?, 0xc0002a57d0?, {0xc0000380e0, 0x70, 0x70}) /Users/username/go/pkg/mod/github.com/fasthttp/websocket@v1.5.0/conn.go:760 +0x30
我认为这是因为当执行注销操作时,sockets["123456"] 已经被删除了。我已经使用 if c.sockets[mr.socket] != nil { 进行了检查,但我的应用程序仍然因为上面那行代码而崩溃。
我不知道如何解决这种情况,我需要帮助。
PS:如果有所帮助,我的注销函数如下所示
case connection := <-c.unregister:
uuid := connection.uuid
if c.sockets[uuid] != nil { // 避免多次删除
c.mu.Lock()
delete(c.sockets, uuid)
c.mu.Unlock()
}
更多关于Golang中处理WebSocket写入错误的解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html
哎呀!!!谢谢,很高兴知道这一点……
更多关于Golang中处理WebSocket写入错误的解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
请展示地图的定义…
要检查一个项是否已从映射中删除,请执行以下操作
if c, ok := sockets["123456"]; ok {
// 仍然存在
我认为在发送消息之前,你需要检查套接字的状态。
if socket != nil && !socket.IsCloseError && !socket.IsUnexpectedCloseError {
if err := socket.WriteMessage(websocket.TextMessage, msg); err != nil {
....
}
}
@Yamil_Bracho 感谢您抽出时间。
经过长时间的摸索,结果发现这是 fasthttp websocket 的一个 bug,它在应该返回错误时却抛出了不必要的 panic。此问题已在此处修复 → Description: by gokpm · Pull Request #31 · fasthttp/websocket · GitHub
这是我的结构体
type Chat struct {
register chan Connection
broadcast chan Message
unregister chan Connection
join chan RoomRequest
leave chan RoomRequest
rooms map[string][]string
sockets map[string]*websocket.Conn. //<-- 这是导致内存问题的那个
mu *sync.RWMutex
stats *Stats
}
提前感谢。
感谢。我已经改用了您的代码,但是当并发连接/断开连接很多时,我仍然遇到错误。
我尝试在向套接字写入之前打印套接字的 uuid,访问切片没有问题。
我也像下面代码那样添加了读锁(Rlock),但问题仍然存在。理论上讲,不应该出现空指针解引用错误,对吧?
panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x139b430]
c.mu.RLock()
// if c.sockets[mr.socket] != nil {
if socket, ok := c.sockets[mr.socket]; ok {
pp.Print(socket.Locals("uuid")) //<--- 这里打印(可访问)
if err := socket.WriteMessage(websocket.TextMessage, msg); err != nil { //<-- 这里出错
socket.SetWriteDeadline(time.Now().Add(10 * time.Second))
socket.WriteMessage(websocket.CloseMessage, []byte{})
socket.Close()
go c.Unregister(socket)
}
}
c.mu.RUnlock()
错误也指向 fasthttp 中的这一行:websocket/conn.go at c98746c10029a4885af35f497980303ea218f409 · fasthttp/websocket · GitHub
问题出现在并发访问和竞态条件上。虽然你使用了 if c.sockets[mr.socket] != nil 检查,但在检查之后、调用 WriteMessage 之前,连接可能已经被注销并从映射中删除。
以下是解决方案:
case mr := <-c.broadcast:
msg := mr.message
// 获取连接时需要加锁
c.mu.RLock()
conn := c.sockets[mr.socket]
c.mu.RUnlock()
if conn != nil {
if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
// 写入失败时也需要加锁
c.mu.Lock()
if c.sockets[mr.socket] == conn { // 再次验证连接是否仍然相同
delete(c.sockets, mr.socket)
}
c.mu.Unlock()
// 关闭连接
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
conn.WriteMessage(websocket.CloseMessage, []byte{})
conn.Close()
}
}
同时修改注销函数,确保在删除连接后不会留下悬空指针:
case connection := <-c.unregister:
uuid := connection.uuid
c.mu.Lock()
conn := c.sockets[uuid]
if conn != nil {
delete(c.sockets, uuid)
}
c.mu.Unlock()
// 如果找到了连接,关闭它
if conn != nil {
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
conn.WriteMessage(websocket.CloseMessage, []byte{})
conn.Close()
}
对于广播消息发送函数,建议改为:
func (mr MessageRequest) Send(msg []byte) {
mr.mu.RLock()
defer mr.mu.RUnlock()
// 收集所有需要发送的连接
connections := make([]*websocket.Conn, 0, len(mr.sockets))
for _, socket := range mr.sockets {
connections = append(connections, socket)
}
// 发送到通道
for _, conn := range connections {
mr.chat.broadcast <- Message{
socket: conn, // 这里应该存储连接标识符,而不是连接本身
message: msg,
chat: mr.chat,
}
}
}
注意:在 Message 结构中,socket 字段应该存储连接的唯一标识符(如UUID),而不是 *websocket.Conn 指针,这样可以避免在通道中传递可能已经失效的连接指针。

