【问题标题】:GAE Channels to multiple clients?多个客户的 GAE 渠道?
【发布时间】:2016-04-18 12:11:07
【问题描述】:

我正试图深入了解 Google App Engine 的 channel 功能,因为它们不(容易)提供 websocket。

我目前的情况是我有一个很长的工作(文件处理)正在通过工作人员异步执行。 该工作人员每行更新数据库中文件处理的状态,以便通知客户。

从当前的角度来看,F5 将指示处理的演变。

现在我想实现一个实时更新系统。当然,我可以每 5 秒发出一次 XHR 请求,但实时连接似乎更好......引入 Channels 因为 Websockets 似乎不可能。

据我了解,我只能channel.send_message 给一个客户,而不是一个“房间”。这里的问题是,处理文件的工作人员没有任何客户当前连接的信息(可能是一个,可能是十个)。

我可以遍历所有客户并发布到每个 client_id,怀疑他们中至少有一个会收到消息,但这非常无用且资源过多。

我希望有更好的方法来实现这一点?也许是 Google Channels 功能的一个不错的替代品,而无需重新配置我的整个 App Engine 系统(比如 Websockets)?

【问题讨论】:

    标签: python google-app-engine websocket


    【解决方案1】:

    我能想到的一种解决方案,虽然不是绝对理想但更适合,是通过以下方式管理专用数据库表(也可以在 Memcache 中实现):

    • 包含房间列表的表格
    • 包含连接到房间的client_id 列表的表

    例如:

    • 房间(ID、名称)
    • 客户(id、room_id、client_id)

    现在,与其发布到channel.send_message(client_id, Message),不如制作一个这样的包装器:

    def send_to_room(room, message):
        # Queries are SQLAlchemy like :
    
        room = Rooms.query.filter(Rooms.name === room).first()
        if not room:
            raise Something
    
        clients = Clients.query.filter(Rooms.room_id === room.id).all()
        for client in clients:
            channel.send_message(client.client_id, message)
    

    瞧,您在 Google App Engine 中有一个类似 Room 的实现。

    此解决方案的缺点是在您的数据库中添加两个表(或等效表)。

    有人有更好的吗?

    【讨论】:

      【解决方案2】:

      我假设客户端正在启动长时间运行的任务。 因此,在您开始任务之前,请从客户端向与此类似的处理程序发出 ajax 请求。这个处理程序有两个东西返回给客户端。 javascript api 用于创建通道的令牌参数,以及用于确定哪个客户端创建通道的 cid 参数。

          from google.appengine.api import channel
          @ae.route("/channel")
          class CreateChannel(webapp2.RequestHandler):
              def get(self):
                  cid = str(uuid.uuid4())
                  token = channel.create_channel(cid)
                  data = {
                      "cid":cid,
                      "token":token
                  }
                  self.response.write(json.dumps(data))
      

      现在使用频道 javascript api 创建一个新频道 https://cloud.google.com/appengine/docs/python/channel/javascript

          var onClosed = function(resp){
            console.log("channel closed");
          };
          var onOpened = function(resp){
            console.log("channel created");
          };
          var onmessage = function(resp){
              console.log("The client received a message from the backend task");
              console.log(resp);
          };
          var channel_promise = $.ajax({
          url: "/channel",
          method: "GET"
          });
          channel_promise.then(function(resp){
          //this channel id is important you need to get it to the backend process so it knows which client to send the message to.
          var client_id = resp.data.cid;
          var channel = new goog.appengine.Channel(resp.data.token);
              handler = {
                  'onopen': $scope.onOpened,
                  'onmessage': $scope.onMessage,
                  'onerror': function () {
                  },
                  'onclose': function () {
                      alert("channel closed.")
                  }
              };
              socket = channel.open(handler);
              //onOpened is the callback function to call after channel has been created
              socket.onopen = onOpened;
              //onClose is the callback function to call after channel has been        closed
              socket.onclose = onClosed;
              //onmessage is the callback function to call when receiving messages from your task queue
              socket.onmessage = onMessage;
              });
      

      现在我们都准备好收听频道消息了。 因此,当用户单击按钮时,我们需要启动后端任务。

          var letsDoSomeWorkOnClick = function(){
               //make sure you pass the client id with every ajax request
               $.ajax({
                   url: "/kickoff",
                   method: "POST",
                   params: {"cid":client_id} 
               });
          }
      

      现在应用引擎处理程序启动后端任务队列。我使用延迟库来做到这一点。 https://cloud.google.com/appengine/articles/deferred

         @ae.route("/kickoff")
         KickOffHandler(webapp2.RequestHandler):
             def post(self):
                 cid = self.request.get("cid")
                 req = {}
                 req['cid'] = cid
                 task = MyLongRunningTask()
                 deferred.defer(task.long_runner_1, req, _queue="my-queue")
      

      示例任务:

          class MyLongRunningTask:
      
                def long_runner_1(self,req):
                     # do a whole bunch of stuff
                     channel.send_message(req["cid"], json.dumps({"test":"letting client know task is done"})
      

      【讨论】:

      • 很抱歉,我相信您误解了我的问题。这里的问题是如何向连接到应用程序的许多客户端发送相同的消息(“Room”,如 Websockets),因为 Google 不允许这样做。
      • 哦,是的,我知道了,我以为您想向特定客户发送消息。我的错。
      • 不好意思,因为你写了一个全面完整的答案:/
      • 没问题,我没有仔细阅读您的问题是我的错。您是否碰巧查看了通道 api 中的“跟踪客户端连接和断开连接”部分? link
      • 如果您可以跟踪哪些客户端连接/断开到特定频道,您应该能够向“房间”中的所有客户端发布消息
      猜你喜欢
      • 2018-03-08
      • 2011-04-10
      • 2017-11-19
      • 1970-01-01
      • 1970-01-01
      • 2015-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多