在websocket连接关闭时关闭redis订阅并结束go例程

时间:2018-01-14 05:58:39

标签: go redis

我将事件从redis订阅推送到通过websocket连接的客户端。当客户端断开websocket时,我无法取消订阅并退出redis例程。

灵感来自this post,这是我迄今所拥有的。我能够通过websocket接收订阅事件并向客户端发送消息,但是当客户端关闭websocket并且defer close(done)代码触发时,我的case b, ok := <-done:不会触发。它似乎被默认情况下重载???

package api

import (
    ...

    "github.com/garyburd/redigo/redis"
    "github.com/gorilla/websocket"
)

func wsHandler(w http.ResponseWriter, r *http.Request) {
    var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }

    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        HandleError(w, err)
        return
    }
    defer conn.Close()

    done := make(chan bool)
    defer close(done)

    for {
        var req WSRequest
        err := conn.ReadJSON(&req)
        if err != nil {
            HandleWSError(conn, err)
            return
        }
        defer conn.Close()

        go func(done chan bool, req *WSRequest, conn *websocket.Conn) {
            rc := redisPool.Get()
            defer rc.Close()

            psc := redis.PubSubConn{Conn: rc}
            if err := psc.PSubscribe(req.chanName); err != nil {
                HandleWSError(conn, err)
                return
            }
            defer psc.PUnsubscribe()

            for {
                select {
                case b, ok := <-done:
                    if !ok || b == true {
                        return
                    }
                default:
                    switch v := psc.Receive().(type) {
                    case redis.PMessage:
                        err := handler(conn, req, v)
                        if err != nil {
                            HandleWSError(conn, err)
                        }

                    case redis.Subscription:
                         log.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)

                    case error:
                         log.Printf("error in redis subscription; err:\n%v\n", v)
                         HandleWSError(conn, v)

                    default:
                        // do nothing...
                        log.Printf("unknown redis subscription event type; %s\n", reflect.TypeOf(v))
                    }
                }
            }
        }(done, &req, conn)
    }
}

1 个答案:

答案 0 :(得分:1)

完成服务websocket连接后,进行这些更改以突破读取循环:

  • 维护为此websocket连接创建的Redis连接片段。

  • 完成后取消订阅所有连接。

  • 修改读取循环以在订阅计数为零时返回。

以下是代码:

func wsHandler(w http.ResponseWriter, r *http.Request) {
    var upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
    }

    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        HandleError(w, err)
        return
    }
    defer conn.Close()

    // Keep slice of all connections. Unsubscribe all connections on exit.
    var pscs []redis.PubSubConn
    defer func() {
        for _, psc := range rcs {
           psc.Unsubscribe() // unsubscribe with no args unsubs all channels
        }
    }()

    for {
        var req WSRequest
        err := conn.ReadJSON(&req)
        if err != nil {
            HandleWSError(conn, err)
            return
        }

        rc := redisPool.Get()
        psc := redis.PubSubConn{Conn: rc}
        pscs = append(pscs, psc)

        if err := psc.PSubscribe(req.chanName); err != nil {
            HandleWSError(conn, err)
            return
        }

        go func(req *WSRequest, conn *websocket.Conn) {
            defer rc.Close()
            for {
                switch v := psc.Receive().(type) {
                case redis.PMessage:
                    err := handler(conn, req, v)
                    if err != nil {
                        HandleWSError(conn, err)
                    }

                case redis.Subscription:
                     log.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
                     if v.Count == 0 {
                         return
                     }

                case error:
                     log.Printf("error in redis subscription; err:\n%v\n", v)
                     HandleWSError(conn, v)

                default:
                    // do nothing...
                    log.Printf("unknown redis subscription event type; %s\n", reflect.TypeOf(v))
                }
            }
        }(&req, conn)
    }
}

问题中的代码和此答案为每个websocket客户端拨打多个Redis连接。更典型和可扩展的方法是跨多个客户端共享单个Redis pubsub连接。鉴于高级描述,典型的方法可能适合您的应用程序,但我仍然不确定您在给出问题中的代码时尝试做什么。