net / http:协程之间的并发和消息传递

时间:2017-06-03 04:15:36

标签: http go websocket goroutine

我正在开发REST API服务器,当创建新资源或修改现有资源时,服务器的一项功能是能够通过websocket通知任意数量的客户端。

我有一个自定义操作路由器将URL绑定到一个函数和 gorillas 的websocket库的实现。对于IPC,我决定依赖于渠道,因为它似乎是 惯用方式在协同程序之间进行通信。它的行为就像一个我熟悉的概念管道。

函数Create的原型如下所示:

func Create (res http.ResponseWriter, req *http.Request, userdata interface {}) (int, string, interface {})

作为userdata传递结构PipeSet的实例。它是一个在所有协程之间共享的映射,其中键是Pipe的地址(指向的指针)并且值相同。这里的基本原理是在删除时加快查找过程。

type Pipe chan string                                                           

type PipeSet struct {                                                           
    sync.Mutex                                                                  
    Pipes map [*Pipe] *Pipe                                                     
}                                                                               

func NewPipe () Pipe {                                                          
    return make (Pipe)                                                          
}                                                                               

func NewPipeSet () PipeSet {                                                    
    var newSet PipeSet                                                      
    newSet.Pipes = make (map[*Pipe] *Pipe)                                  
    return newSet                                                           
}                                                                               

func (o *PipeSet) AddPipe (pipe *Pipe) {                                        
    o.Lock ()                                                                   
    o.Pipes[pipe] = pipe                                                        
    o.Unlock ()                                                                 
}                                                                               

func (o *PipeSet) ForeachPipe (f func (pipe Pipe)) {                            
    o.Lock ()                                                                   
    for k := range (o.Pipes) {                                                  
        f (*o.Pipes[k])                                                         
    }                                                                           
    o.Unlock ()                                                                 
}                                                                               

func (o *PipeSet) DeletePipe (pipe *Pipe) {                                     
    o.Lock ()                                                                   
    delete (o.Pipes, pipe)                                                      
    o.Unlock ()                                                                 
}

当客户通过websocket连接时,会创建一个新频道(Pipe)并将其添加到共享PipeSet。然后,如果创建了新资源,则协程将通过整个PipeSet向每个Pipe发送消息。然后,该消息将转发到另一侧的已连接客户端。

问题区域

我无法检测客户端的websocket连接是否仍然那里。我需要知道确定是否应从Pipe中移除PipeSet。在这种情况下,我依靠CloseNotifier。它永远不会发射。

代码如下(摘录):

var upgrader = websocket.Upgrader {
    CheckOrigin: func (r *http.Request) bool { return true },
}

conn, err := upgrader.Upgrade (res, req, nil)

if err != nil {
    marker.MarkError (err)
    return http.StatusBadRequest, "", nil
}

defer conn.Close ()

exitStatus = http.StatusOK
pipe := genstore.NewPipe ()
quit := res.(http.CloseNotifier).CloseNotify ()

genStore.WSChannels.AddPipe (&pipe)

for {
    log.Printf ("waiting for a message")

    select {
        case wsMsg = <-pipe:
            log.Printf ("got a message: %s (num pipes %d)", wsMsg, len (genStore.WSChannels.Pipes))

            if err = conn.WriteMessage (websocket.TextMessage, []byte (wsMsg)); err != nil {
                marker.MarkError (err)
                goto egress
            }

        case <-quit:
            log.Printf ("quit...")
            goto egress
    }
}

egress:
genStore.WSChannels.DeletePipe (&pipe)

1 个答案:

答案 0 :(得分:3)

当您使用Gorilla将HTTP连接升级到WebSocket连接时,它会劫持该连接并且net / http服务器停止为其提供服务。这意味着,您不能依赖那一刻的网络/ http事件。

请检查:https://github.com/gorilla/websocket/issues/123

所以,你可以在这里做的是为每个新的WebSocket连接启动新的goroutine,它将从这个连接读取数据并在失败时向quit通道写一条消息。