GAE频道到多个客户端?

时间:2016-04-18 12:11:07

标签: python google-app-engine websocket

我试图绕过Google App Engine的channel功能,因为他们不(轻松)提供网页套件。

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

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

现在我想实施一个实时更新系统。当然,我可以每5秒做一次XHR请求,但实时连接似乎更好......引入频道,因为Websockets看起来不可能。

根据我的理解,我可以channel.send_message仅限一个客户,而不是#34;房间"。这里的问题是,处理文件的工人没有任何客户当前连接的信息(可能是一个,可能是10个)。

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

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

2 个答案:

答案 0 :(得分:0)

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

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

e.g。 :

  • 房间(身份证,姓名)
  • 客户端(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中有类似房间的实现。

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

有人有更好的吗?

答案 1 :(得分:0)

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

    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} 
         });
    }

现在app引擎处理程序启动后端任务队列。我使用deffered库来做到这一点。 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"})