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

相关推荐
MetaverseMan13 小时前
WebRTC 和 WebSocket
websocket·网络协议·webrtc
可可爱爱的你吖1 天前
webSocket的使用文档
网络·websocket·网络协议
写bug的小屁孩1 天前
websocket身份验证
开发语言·网络·c++·qt·websocket·网络协议·qt6.3
专注VB编程开发20年1 天前
WebSocket和HTTP协议的性能比较与选择
websocket·网络协议·http
写bug的小屁孩1 天前
websocket初始化
服务器·开发语言·网络·c++·websocket·网络协议·qt creator
清尘沐歌2 天前
有什么好用的 WebSocket 测试工具吗?
websocket·网络协议·测试工具
清尘沐歌2 天前
有什么好用的 WebSocket 调试工具吗?
网络·websocket·网络协议
专注VB编程开发20年2 天前
一篇介绍 Websocket 和 Http 的很好的帖子
websocket·网络协议·http·聊天协议·传输文件
郝同学的测开笔记2 天前
云原生探索系列(十二):Go 语言接口详解
后端·云原生·go
heilai42 天前
workerman的安装与使用
c++·websocket·http·php·phpstorm·visual studio code