jjzjj

python aiohttp websockets关闭浏览器选项卡处理

coder 2023-07-18 原文

我正在尝试使用 aiohttp WebSockets 和 aioredis 进行存储来创建简单的活跃用户计数器。当我在 Google Chrome 中添加新标签时,我的计数器在所有已打开的标签中完美递增。但是,当我关闭一个选项卡时,其他选项卡中没有任何变化。

我想我应该在整个 async/await 机制中遗漏一些东西,但找不到可能出错的地方。

这是我的应用

import asyncio

import aiohttp
from aiohttp import web
import aioredis


class CounterView(web.View):
    async def get(self):
        request = self.request
        app = request.app

        ws = web.WebSocketResponse()

        app['websockets'].append(ws)
        await ws.prepare(request)

        count = int(await app['db'].incr('counter'))
        for ws in app['websockets']:
            await ws.send_json({'msg': {'count': count}})

        async for msg in ws:
            if msg.type == aiohttp.WSMsgType.TEXT:
                await ws.send_str(msg.data)
            elif msg.type == aiohttp.WSMsgType.ERROR:
                print('ws connection closed with exception %s' %
                      ws.exception())
        app['websockets'].remove(ws)

        # Execution stops here (on await app['db'] ...) and never returns
        count = int(await app['db'].decr('counter'))
        for ws in app['websockets']:
            await ws.send_json({'msg': {'count': count}})
        return ws


async def init_app(loop):
    app = web.Application(loop=loop)
    db = await aioredis.create_redis('redis://localhost', loop=loop)
    app['db'] = db
    app['websockets'] = []
    app.add_routes([
        web.get('', CounterView),
    ])
    return app

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    web.run_app(init_app(loop))

和index.html模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    How many people seeing this page now: <span id="counter"></span>
</body>
<script>
    window.onload = function () {
      const ws = new WebSocket('ws://localhost:8080');
      ws.onmessage = function (event) {
          const data = JSON.parse(event.data);
          let span = document.getElementById('counter');
          console.log(data.msg.count);
          span.innerHTML = data.msg.count;
      }
    };
</script>
</html>

我也在 Firefox 中尝试过,那里发生了一些非常奇怪的事情。

打开了两个选项卡,在两个选项卡上都得到了 counter = 2。然后先重新加载 - 在其中得到 1,在第二个中仍然是 2。再次重新加载第一个选项卡 - 得到 2。此后,每次重新加载都会得到 2

直到我重新加载第二个选项卡 - 相同的过程(重新加载 - 1 - 重新加载 - 2 发生在那里并在第一个选项卡中重复)

我也尝试申请 https://stackoverflow.com/a/48695448/6627564这个答案,但没有任何改变。

调试显示代码执行了 count = int(await app['db'].decr('counter')) 然后跳到某处永远不会返回。

非常感谢任何帮助。据我所知,事件循环应该在这一行之后返回执行。也许协程以某种方式被破坏了,但我没有在库中找到任何执行此操作的代码。

我的问题与 Python Asyncio Websocket not detecting a disconnect on wifi but does on localhost 中描述的不同

首先,我的连接都在本地主机上。

其次,async for msg in ws循环之后的代码才真正开始执行,调试显示确实调用了ws.close()方法。但是在下一个 await 上有一个上下文切换,并且执行不会继续。

我也尝试过使用 ws = web.WebSocketResponse(heartbeat=1.0) 激活 ping-pong,但我在 Dev Tools 中看不到任何消息。我在 await ws.prepare(request) 之后添加了单个 await ws.ping() ,不幸的是,Dev Tools 中没有出现任何消​​息。这里肯定出了问题......

最佳答案

对于任何对此问题感兴趣的人 - 解决方案。

此代码中存在三个问题)。其中两个实际上与 asyncio 无关。

首先,app['websockets']list 并且由于某种原因 remove(ws) 无法找到正确的 WebSocketResponse 实例并从列表中删除另一个 WebSocketResponse。 解决方案是使用 set() 而不是 list 来存储事件的 websocket。这是因为set.discard()使用了__hash__魔术方法,而list.remove()使用了__eq__方法.不幸的是,我在 WebSocketResponse 中找不到 __eq__ 的实现细节,但是 __hash__ 使用内置的 id 函数保证正确的工作。

其次,看这几行

ws = web.WebSocketResponse()
....
......
for ws in app['websockets']:
   await ws.send_json({'msg': {'count': count}})

局部变量 wsfor 循环中被覆盖。 解决方案是只使用其他变量名进行迭代,如 other_ws

第三个在aiohttp 的文档中有描述Web Handler Cancellation .

它声明在每次 await 调用时,如果客户端已断开连接,则可以终止处理程序。情况确实如此 - 在断开连接后的第一个 await 上,我的处理程序死了。文档中也提供了解决方案,我决定使用 asyncio.shield .

关于python aiohttp websockets关闭浏览器选项卡处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50318875/

有关python aiohttp websockets关闭浏览器选项卡处理的更多相关文章

  1. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  2. ruby - 默认情况下使选项为 false - 2

    这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb

  3. ruby - 在 Ruby 中用键盘诅咒数组浏览 - 2

    我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作

  4. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  5. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  6. ruby - 如何关闭 ruby​​ gem "Spreadsheet?"中的文件 - 2

    下面的代码在我第一次运行它时就可以正常工作:require'rubygems'require'spreadsheet'book=Spreadsheet.open'/Users/me/myruby/Mywks.xls'sheet=book.worksheet0row=sheet.row(1)putsrow[1]book.write'/Users/me/myruby/Mywks.xls'当我再次运行它时,我会收到更多消息,例如:/Library/Ruby/Gems/1.8/gems/spreadsheet-0.6.5.9/lib/spreadsheet/excel/reader.rb:11

  7. ruby-on-rails - Ruby 的 'open_uri' 是否在读取或失败后可靠地关闭套接字? - 2

    一段时间以来,我一直在使用open_uri下拉ftp路径作为数据源,但突然发现我几乎连续不断地收到“530抱歉,允许的最大客户端数(95)已经连接。”我不确定我的代码是否有问题,或者是否是其他人在访问服务器,不幸的是,我无法真正确定谁有问题。本质上,我正在读取FTPURI:defself.read_uri(uri)beginuri=open(uri).readuri=="Error"?nil:urirescueOpenURI::HTTPErrornilendend我猜我需要在这里添加一些额外的错误处理代码...我想确保我采取一切预防措施来关闭所有连接,这样我的连接就不是问题所在,但是我

  8. Ruby-vips 图像处理库。有什么好的使用示例吗? - 2

    我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby​​代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby​​-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby​​-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby​​-vips的github页面上的链接,我们将不胜感激!如果有ruby​​-

  9. Ruby on Rails regexp equals-tilde 与 array include 用于检查选项列表 - 2

    我正在使用Rails3.2.3和Ruby1.9.3p0。我发现我经常需要确定某个字符串是否出现在选项列表中。看来我可以使用Ruby数组.includemethod:或正则表达式equals-tildematchshorthand用竖线分隔选项:就性能而言,一个比另一个好吗?还有更好的方法吗? 最佳答案 总结:Array#include?包含String元素,在接受和拒绝输入时均胜出,对于您的示例只有三个可接受的值。对于要检查的更大的集合,看起来Set#include?和String元素可能会获胜。如何测试我们应该根据经验对此进行测试

  10. ruby - Faye WebSocket,关闭处理程序被触发后重新连接到套接字 - 2

    我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d

随机推荐