首页 > 解决方案 > syscall/js websocket 回调在非阻塞行为中不响应

问题描述

我有两个进程正在运行,用 Go 编写,其中一个(sender.go)向另一个(listener.go)发送消息,同时通过 websockets 卡在 for 循环中。

问题是 listener.go 仅在终止循环后才意识到它收到了消息。

我已经尝试了几个 websocket 库,我什至尝试过使用常规的 tcp 流,但是在 webassembly 中编译时它不起作用,因为浏览器不支持它。尽管有这种行为,但 syscall/js websocket 似乎是最合适的。

这里是 listener.go

func registerCallbacks(ws js.Value) {
    ws.Call("addEventListener", "message", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        message := args[0].Get("data").String()
        fmt.Println("message received ")
        fmt.Println(message)
        return nil
    }))
}


func main() {
    c := make(chan struct{}, 0)
    toIP := "127.0.0.1"
    toPort := 8081
    ws := js.Global().Get("WebSocket").New(fmt.Sprintf("ws://%s:%d/ws", toIP, toPort))
    registerCallbacks(ws)
    const bigNum uint64 = 100000 * 10000
    cb := func(this js.Value, args []js.Value) interface{} {
      for i := uint64(0); i < bigNum; i++ {
        doThings()
        if i%10000000 == 0 {
            fmt.Println("skimmed ten millions ")
        }
      }
      fmt.Println("Exited for loop !!")
      return nil
    }
    // call cb() in script of index.html from a button
    js.Global().Set("cb", js.FuncOf(cb))
    <-c
}

sender.go 也是如此,

func main() {
    toIP := "127.0.0.1"
    toPort := 8081
    ws := js.Global().Get("WebSocket").New(fmt.Sprintf("ws://%s:%d/ws", toIP, toPort))
    time.Sleep(1 * time.Second)
    fmt.Println("sending  ")
    ws.Call("send", js.ValueOf("msg"))
    fmt.Println("Program exit  ")
}

如果有人愿意在这里重现这个问题,那就是 websocket 服务器,它接收来自发送者的消息以将其转发给接收者,用 node.js 编写,只需复制并粘贴它,它适用于多个项目

const port = 8081;
const host = '127.0.0.1';

var WebSocketServer = require('websocket').server;
var http = require('http');

var server = http.createServer(function(request, response) {});

server.listen(port, host, () => {
    console.log('WS Server is running on port ' + port + '.');
});

wsServer = new WebSocketServer({
    httpServer: server
});

let sockets = [];


wsServer.on('request', function(request) {
  var connection = request.accept(null, request.origin);
  sockets.push(connection)
  console.log("Connection with node initiated")
  connection.on('message', function(message) {
    console.log("message received "+message)
    sockets.forEach(function(s, index, array) {
      if (message.type === 'utf8') {
        broadcastData = message.utf8Data
        console.log("message is: "+ broadcastData)
        if(s!= connection) {
          console.log('send data to ' + s.socket.remotePort + ': ' + broadcastData);
          s.sendUTF(broadcastData)
        }
      }
    });
  });
});

我期待 listener.go 在退出 for 循环之前接收消息

PS:使用 sleep 语句不是一个很好的帮助,因为当这个 for 循环在 js 回调中运行时,sleep 语句会输出一个恐慌。

标签: gowebsocketwebassembly

解决方案


首先,请注意,如果您正在生成 wasm Go 代码,它不是多线程的,至少在今天(2019 年 9 月)不是。请参阅 如何在使用 golang 创建的 wasm 中实现多线程? 所以只有一个实际的执行线程。

然后,请记住,goroutines在任何可用的线程上协同执行多任务。由于只有一个线程,因此任何时候都只有一个 goroutine 在运行。如果您正在运行的 goroutine 没有将处理器让给其他 goroutine,那么其他 goroutine 将不会运行。

您可以隐式放弃处理器(例如,通过等待通道或互斥体)或显式放弃处理器,通过time.Sleepruntime.Gosched。如果没有这样的调用,任何会注意到传入消息的 goroutine 都没有机会看到它,并且您的回调(将在这个或第三个 goroutine 中运行)永远不会被调用。

(如果您在本机运行,而不是在 wasm 中运行,您通常会获得更多线程,并且即使一个 CPU 处于紧密循环中,也能够让其他 CPU 运行其他任务。)


推荐阅读