第六章节:WebSocket断线自动重连

前言

上一章节我们完成了一个单机IM服务该具备的基本功能,但是还存在一些问题,比如当server因为其他原因重启或者宕机时,client端还持有一个已经断开的连接,这个连接无法收发消息,所以client端需要检测当前连接是否可用,当发现不可用时需要重连。

检查连接

gorilla-websocket提供的websocket.Conn相关ReadXX方法中,当连接被关闭时会返回websocket.CloseError这个错误,所以client可以通过返回的错误是websocket.CloseError时进行重连。

首先将连接WS server的方法抽取出来, 在client.go中添加connect方法

go 复制代码
// 连接到WS server,并带有retry
func connect(url string, header http.Header) (*websocket.Conn, error) {
    var (
        conn *websocket.Conn
        err  error
    )
    maxRetries := 5
    for attempt := 0; attempt < maxRetries; attempt++ {
        conn, _, err = websocket.DefaultDialer.Dial(url, header)
        if err == nil {
            log.Println("connected to server ...")
            return conn, nil
        }
        if attempt < maxRetries {
            // 失败后的延迟
            time.Sleep(time.Second)
        }
    }
    return nil, err
}

client.go中添加判断连接断开

go 复制代码
func isClosed(err error) bool {
    _, ok := err.(*websocket.CloseError)
    return ok
}

修改client.go的main方法

go 复制代码
func main() {
    ... // 省略 
    u := model.User{
        ID:   userID,
        Name: args[1],
    }
    token := u.GenToken()
    header := http.Header{
        "X-TOKEN": []string{token},
    }
    conn, err := connect(uri, header)
    if err != nil {
        log.Fatal("dial failed:", err)
    }
    defer conn.Close()

    go func() {
        for {
            m := &model.Message{}
            err = conn.ReadJSON(m)
            if err != nil {
                // server shutdown or socket closed
                if isClosed(err) {
                    // reconnect
                    log.Println("Trying reconnect ...")
                    conn, err = connect(uri, header)
                    if err != nil {
                        log.Fatalln("Reconnect Failed...", err)
                        return
                    }
                    continue
                }
                log.Println("Read WS message failed:", err)
                continue
            }
            log.Printf("Received Message %v From %v \n", m.Data, m.FromUserID)
            err = markMsgRead(*m, token)
            if err != nil {
                log.Println("MarkRead failed:", err)
            }
        }
    }()
    ... // 省略
}

调试

首先分别在2个Terminal中启动server和client, 然后停止server,观察client端的日志,5秒内立即重启server,观察server和client的日志,可以在server端看到client连接的日志,然后再新开一个Terminal用另一个用户登录,可以看到用户间的收发消息功能正常。

Client日志

Server日志

小结

本章节中我们在客户端通过识别错误类型判断连接是否可用,并在连接不可用时进行重连,这样当server端短暂重启或因网络波动导致连接断开时可以自动重新进行连接,提高了服务的可靠性。但是随着客户端(用户)变多,要提高服务的高可用性,服务端必须采用多实例部署方案,也就是分布式系统,下一章节我们将探讨服务端如何在分布式环境下共享连接状态、通信。

相关推荐
AD钙奶-lalala10 小时前
SpringBoot实现WebSocket服务端
spring boot·后端·websocket
lypzcgf16 小时前
Coze源码分析-资源库-创建知识库-后端源码-应用/领域/数据访问
后端·go·coze·coze源码分析·智能体平台·ai应用平台·agent平台
wow_DG21 小时前
【WebSocket✨】入门之旅(五):WebSocket 的安全性
网络·websocket·网络协议
往事随风去21 小时前
别再纠结了!IM场景下WebSocket和MQTT的正确选择姿势,一文讲透!
后端·websocket·架构
lypzcgf1 天前
Coze源码分析-资源库-创建知识库-基础设施/存储/安全
安全·go·coze·coze源码分析·智能体平台·ai应用平台·agent开发平台
程序员爱钓鱼1 天前
Go语言实战案例 — 项目实战篇:图书管理系统(文件存储)
后端·google·go
郝亚军1 天前
websocket 服务器往客户端发送的数据要加掩码覆盖吗?
服务器·网络·websocket
郭京京2 天前
goweb原生实现HTTP文件上传功能
go
郭京京2 天前
goweb中间件
go
威斯软科的老司机2 天前
WebSocket压缩传输优化:机器视觉高清流在DCS中的低延迟方案
网络·websocket·网络协议