第六章节: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端短暂重启或因网络波动导致连接断开时可以自动重新进行连接,提高了服务的可靠性。但是随着客户端(用户)变多,要提高服务的高可用性,服务端必须采用多实例部署方案,也就是分布式系统,下一章节我们将探讨服务端如何在分布式环境下共享连接状态、通信。

相关推荐
代码扳手6 小时前
Golang 实战:用 Watermill 构建订单事件流系统,一文掌握概念与应用
后端·go
百锦再12 小时前
Python、Java与Go:AI大模型时代的语言抉择
java·前端·vue.js·人工智能·python·go·1024程序员节
CodeCaptain13 小时前
可直接落地的「Flutter 桥接鸿蒙 WebSocket」端到端实施方案
websocket·flutter·harmonyos
豆浆Whisky14 小时前
掌握Go context:超越基础用法的正确实践模式|Go语言进阶(13)
后端·go
心无旁骛~15 小时前
Socket和Websocket编程的区别
网络·websocket·网络协议
ErizJ1 天前
IM|im-service
golang·kafka·go·im·心跳检测
光头闪亮亮1 天前
curl库应用-c++客户端示例及golang服务端应用示例
c++·go
-芒果酱-1 天前
对中兴光猫zteOnu.exe项目的简单分析(提供下载地址)
go
BAGAE2 天前
HTTPS 加密原理介绍
java·c++·websocket·http·均值算法·启发式算法·最小二乘法