在Golang中将websocket循环与通道同步

时间:2018-04-06 23:02:24

标签: go concurrency websocket synchronization gorilla

我在这里面临一个困境,试图让某个特定的websockets与给定用户保持同步。这是基本设置:

type msg struct {
    Key         string
    Value   string
}

type connStruct struct {
    //...

    ConnRoutineChans []*chan string
    LoggedIn        bool
    Login       string

    //...

    Sockets         []*websocket.Conn
}

var (
    //...

    /*  LIST OF CONNECTED USERS AN THEIR IP ADDRESSES  */

        guestMap sync.Map

)

func main() {
    post("Started...")
    rand.Seed(time.Now().UTC().UnixNano())
    http.HandleFunc("/wss", wsHandler)
    panic(http.ListenAndServeTLS("...", "...", "...", nil))
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("Origin")+":8080" != "https://...:8080" {
        http.Error(w, "Origin not allowed", 403)
        fmt.Println("Client origin not allowed! (https://"+r.Host+")")
        fmt.Println("r.Header Origin: "+r.Header.Get("Origin"))
        return
    }
    ///
    conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
    if err != nil {
        http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
        fmt.Println("Could not open websocket connection with client!")
    }

    //ADD CONNECTION TO guestMap IF CONNECTION IS nil
    var authString string = /*gets device identity*/;
    var authChan chan string = make(chan string);
    authValue, authOK := guestMap.Load(authString);
    if !authOK {
        // NO SESSION, CREATE A NEW ONE
        newSession = getSession();
        //defer newSession.Close();
        guestMap.Store(authString, connStruct{ LoggedIn: false,
                                ConnRoutineChans: []*chan string{&authChan},
                                         Login: "",
                                        Sockets: []*websocket.Conn{conn}
                                        /* .... */ });
    }else{
        //SESSION STARTED, ADD NEW SOCKET TO Sockets
        var tempConn connStruct = authValue.(connStruct);
        tempConn.Sockets = append(tempConn.Sockets, conn);
        tempConn.ConnRoutineChans = append(tempConn.ConnRoutineChans, &authChan)
        guestMap.Store(authString, tempConn);
    }

    //
    go echo(conn, authString, &authChan);
}

func echo(conn *websocket.Conn, authString string, authChan *chan string) {

    var message msg;

    //TEST CHANNEL
    authValue, _ := guestMap.Load(authString);
    go sendToChans(authValue.(connStruct).ConnRoutineChans, "sup dude?")

    fmt.Println("got past send...");

    for true {
        select {
            case val := <-*authChan:
                // use value of channel
                fmt.Println("AuthChan for user #"+strconv.Itoa(myConnNumb)+" spat out: ", val)
            default:
                // if channels are empty, this is executed
        }

        readError := conn.ReadJSON(&message)
        fmt.Println("got past readJson...");

        if readError != nil || message.Key == "" {
            //DISCONNECT USER
            //.....
            return
        }

        //
        _key, _value := chief(message.Key, message.Value, &*conn, browserAndOS, authString)

        if writeError := conn.WriteJSON(_key + "|" + _value); writeError != nil {
            //...
            return
        }

        fmt.Println("got past writeJson...");
    }
}

func sendToChans(chans []*chan string, message string){
    for i := 0; i < len(chans); i++ {
        *chans[i] <- message
    }
}

我知道,一大堆代码呃?我评论了大部分内容......

无论如何,如果你曾经使用过websocket,那么大部分都应该非常熟悉:

1)func wsHandler()每次用户连接时都会触发。它在guestMap(对于每个连接的唯一设备)中生成一个条目,其中包含connStruct,其中包含一个频道列表:ConnRoutineChans []*chan string。这一切都传递给:

2)echo(),这是一个不断为每个websocket连接运行的goroutine。在这里,我只是测试向其他正在运行的goroutines发送消息,但似乎我的for循环实际上并没有持续触发。它仅在websocket从其连接的打开选项卡/窗口接收消息时触发。 (如果有人能澄清这个机制,我很想知道它为什么不能不断循环?)

3)对于用户在给定设备上打开的每个窗口或选项卡,都有一个websocket和channel存储在一个数组中。我希望能够向阵列中的所有通道发送消息(本质上是该设备上打开的选项卡/窗口的其他goroutine),并在其他goroutine中接收消息以更改在不断运行的goroutine中设置的一些变量。 / p>

我现在拥有的只适用于设备上的第一个连接,并且(当然)它发送“sup dude?”因为它是当时阵列中唯一的通道。然后,如果您打开一个新标签(甚至许多标签),则该消息根本不会发送给任何人!奇怪吗?...然后,当我关闭所有选项卡(并且我的注释逻辑从guestMap中移除设备项目)并启动新的设备会话时,仍然只有第一个连接获得它自己的消息。

我已经有了一种向设备上的所有其他websockets发送消息的方法,但发送到goroutine似乎比我想象的要复杂一点。

1 个答案:

答案 0 :(得分:0)

回答我自己的问题:

首先,我已经从sync.map切换到普通地图。其次,为了让没有人同时读/写它我已经建立了一个你要求在地图上进行任何读/写操作的通道。我一直在努力保持我的数据访问和操作快速执行,因此通道不会那么容易拥挤。以下是一个小例子:

package main

import (
    "fmt"
)

var (
  guestMap map[string]*guestStruct = make(map[string]*guestStruct);
  guestMapActionChan = make (chan actionStruct);

)

type actionStruct struct {
    Action      func([]interface{})[]interface{}
    Params      []interface{}
    ReturnChan  chan []interface{}
}

type guestStruct struct {
    Name string
    Numb int
}

func main(){
    //make chan listener
    go guestMapActionChanListener(guestMapActionChan)

    //some guest logs in...
    newGuest := guestStruct{Name: "Larry Josher", Numb: 1337}

    //add to the map
    addRetChan := make(chan []interface{})
    guestMapActionChan <- actionStruct{Action: guestMapAdd,
                                       Params: []interface{}{&newGuest},
                                       ReturnChan: addRetChan}
    addReturned := <-addRetChan

    fmt.Println(addReturned)
    fmt.Println("Also, numb was changed by listener to:", newGuest.Numb)

    // Same kind of thing for removing, except (of course) there's
    // a lot more logic to a real-life application.
}

func guestMapActionChanListener (c chan actionStruct){
    for{
        value := <-c;
        //
        returned := value.Action(value.Params);
        value.ReturnChan <- returned;
        close(value.ReturnChan)
    }
}

func guestMapAdd(params []interface{}) []interface{} {
    //.. do some parameter verification checks
    theStruct := params[0].(*guestStruct)
    name := theStruct.Name
    theStruct.Numb = 75
    guestMap[name] = &*theStruct

    return []interface{}{"Added '"+name+"' to the guestMap"}
}

对于连接之间的通信,我只是让每个套接字循环保持在guestStruct上,并且有更多guestMapActionChan函数负责将数据分发给其他访客guestStruct

现在,我不打算将此标记为正确的答案,除非我得到更好的建议,如何以正确的方式做这样的事情。但是现在这是有效的,应该保证没有用于阅读/写入地图的比赛。

相关问题