【发布时间】:2018-09-17 00:40:32
【问题描述】:
我在这里面临两难境地,试图让特定用户的某些 websocket 保持同步。这是基本设置:
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。在这里,我只是在测试向其他正在运行的 goroutine 发送消息,但似乎我的 for 循环实际上并没有持续触发。它仅在 websocket 从它所连接的打开的选项卡/窗口接收到消息时触发。 (如果有人能澄清这个机制,我很想知道为什么它不经常循环?)
3) 对于用户在给定设备上打开的每个窗口或选项卡,都有一个 websocket 和存储在数组中的通道。我希望能够向数组中的所有通道(本质上是该设备上打开的选项卡/窗口的其他 goroutines)发送消息,并在其他 goroutines 中接收消息以更改在不断运行的 goroutine 中设置的一些变量。
我现在所拥有的仅适用于设备上的第一次连接,并且(当然)它会发送“sup dude?”因为它是当时阵列中唯一的通道。然后,如果您打开一个新标签(甚至很多),则消息根本不会发送给任何人!奇怪?...然后当我关闭所有选项卡(并且我注释掉的逻辑从guestMap 中删除设备项)并启动新设备会话时,仍然只有第一个连接得到它自己的消息。
我已经有了向设备上所有其他 websocket 发送消息的方法,但是发送到 goroutine 似乎比我想象的要复杂一些。
【问题讨论】:
-
关于 2)
echo中的 for 循环调用ReadMessage。由于此方法在收到消息之前会一直阻塞,因此预计在客户端发送消息之前不会发生任何事情。 -
@CeriseLimón 我在想可能有什么东西被阻止了……如果什么都没读,你有什么建议可以让它通过吗?同样用于阻止
WriteJSON()?你会为了性能不推荐这个吗?我正在考虑一种方法来不断收听频道以更新在echo()goroutines 中设置的变量 -
另一个问题是有一个比赛设置值
guestMap。考虑这个时间线:goroutine 1 调用 guestMap.Load,goroutine 2 调用 guestmap,Load,goroutine 1 调用 guestMap.Store,goroutine 2 调用 guestMap.Store。第二个 goroutine 破坏了第一个 goroutine 设置的值。我建议从chat example开始。 -
@CeriseLimón 如果我没记错的话,
sync.Map不应该为我处理存储队列吗?我完全没有同时加载/存储的问题。 -
sync.Map 不会同步您的应用程序逻辑。请参阅我之前评论中的时间线,其中应用程序将覆盖应用程序先前写入的值。与地图问题不同,您应该使用竞赛检测器运行应用程序。
标签: go concurrency websocket synchronization gorilla