最好(最简单和优雅的)方式在长时间后向客户发送消息是什么?

huangapple go评论76阅读模式
英文:

What is best (most easiest and elegant) way to send message to client after long period of time?

问题

我正在创建一个带有服务器和客户端应用程序的资源预订系统。客户端应用程序是一个简单的命令行工具,可用于向系统中添加和删除资源以及预订资源。在客户端完成资源使用后,它负责通知服务器已完成预订,资源变为可供其他人使用。如果在预订时资源不可用,客户端应用程序将等待资源变为可用。服务器跟踪预订情况,并负责在请求的资源变为可用时通知等待的客户端。

我正在努力找出通知等待客户端资源可用的最佳方法。我的想法是在客户端应用程序中请求预订时启动一个小型服务器,并在请求中包含地址和端口号。然后,服务器可以向客户端发送消息。

当前的预订系统包含了大量代码,因此我创建了一个较小的示例来演示这个机制。客户端可以将其地址发送给服务器,服务器会定期向客户端发送消息,直到客户端不再响应为止。

以下是您提供的Python代码的翻译部分:

server.py

import asyncio
from typing import List

from aiohttp import ClientConnectorError, ClientSession, web
from aiohttp.web_request import Request


async def handle(request: Request):
    subscriber = await request.text()

    print(f"新订阅者 {subscriber}")

    request.app["subscribers"].append(subscriber)

    return web.Response()


app = web.Application()
app.add_routes([web.post("/", handle)])


async def periodic_publish(subscribers: List[str]):
    while True:
        await asyncio.sleep(2)

        print(f"{subscribers=}")

        async with ClientSession() as session:
            to_remove = []
            for i, subscriber in enumerate(subscribers):
                try:
                    async with session.post(
                        subscriber, data="这是您的订阅"
                    ) as response:
                        body = await response.text()
                        print(f"来自订阅者的响应: {body}")
                except ClientConnectorError:
                    print(f"无法连接到 {subscriber}")
                    to_remove.append(i)

        for subscriber_i in reversed(to_remove):
            subscribers.pop(subscriber_i)


if __name__ == "__main__":
    subscribers = []
    loop = asyncio.get_event_loop()
    loop.create_task(periodic_publish(subscribers))
    app["subscribers"] = subscribers
    web.run_app(app, port=8080, loop=loop)

client.py

import asyncio
import sys

import aioconsole
import aiohttp
from aiohttp import web
from aiohttp.web_request import Request

port = int(sys.argv[1])


async def handle(request: Request):
    message = await request.text()

    print(f'新消息 "{message}"')

    return web.Response(text="谢谢!")


app = web.Application()
app.add_routes([web.post("/", handle)])


async def cli():
    async with aiohttp.ClientSession() as session:
        async with session.post(
            "http://localhost:8080/", data=f"http://localhost:{port}/"
        ) as response:
            print("状态:", response.status)
            print("内容类型:", response.headers["content-type"])

            body = await response.text()
            print("内容:", body)

    await aioconsole.aprint("按回车键退出。")
    await aioconsole.ainput()
    await aioconsole.aprint("再见!")


loop = asyncio.get_event_loop()
runner = web.AppRunner(app)
loop.run_until_complete(runner.setup())
site = web.TCPSite(runner, port=port)
loop.run_until_complete(site.start())
loop.run_until_complete(cli())

这也是一个学习机会,考虑到客户端只是一个命令行工具,用户可能会因等待而感到厌倦,还有哪些其他方法可以实现这一目标?

关于我的 asyncio 和 aiohttp 的使用,欢迎提供其他评论。这是我第一次使用它们。

英文:

I am creating resource booking system with server and client applications. Client application is simple command line tool that can be used to add and remove resources into and from the system and book them. After client is done with the resource it is responsible to inform server that it is done with the booking and resource becomes available for someone else to use. If resource is not available at the time of booking, the client application stays waiting for resource to become available. Server keeps track of bookings and is responsible to notify waiting client when requested resource becomes available.

I am trying to figure out what is best way to make possible to notify waiting client about their resource becoming available. My idea was to spin up small server in the client application when booking is requested and include the address and the port number in the request. Server could then send message back to the client.

Current booking system contains so much code that I created smaller example of this mechanism. Client can send it's address to server and server periodically sends message to the client until it doesn't respond anymore.

server.py

import asyncio
from typing import List

from aiohttp import ClientConnectorError, ClientSession, web
from aiohttp.web_request import Request


async def handle(request: Request):
    subscriber = await request.text()

    print(f"New subscriber {subscriber}")

    request.app["subscribers"].append(subscriber)

    return web.Response()


app = web.Application()
app.add_routes([web.post("/", handle)])


async def periodic_publish(subscribers: List[str]):
    while True:
        await asyncio.sleep(2)

        print(f"{subscribers=}")

        async with ClientSession() as session:
            to_remove = []
            for i, subscriber in enumerate(subscribers):
                try:
                    async with session.post(
                        subscriber, data="Here is your subscription"
                    ) as response:
                        body = await response.text()
                        print(f"Response from subscriber: {body}")
                except ClientConnectorError:
                    print(f"Could not connect to {subscriber}")
                    to_remove.append(i)

        for subscriber_i in reversed(to_remove):
            subscribers.pop(subscriber_i)


if __name__ == "__main__":
    subscribers = []
    loop = asyncio.get_event_loop()
    loop.create_task(periodic_publish(subscribers))
    app["subscribers"] = subscribers
    web.run_app(app, port=8080, loop=loop)

client.py

import asyncio
import sys

import aioconsole
import aiohttp
from aiohttp import web
from aiohttp.web_request import Request

port = int(sys.argv[1])


async def handle(request: Request):
    message = await request.text()

    print(f'New message "{message}"')

    return web.Response(text="Thank you!")


app = web.Application()
app.add_routes([web.post("/", handle)])


async def cli():
    async with aiohttp.ClientSession() as session:
        async with session.post(
            "http://localhost:8080/", data=f"http://localhost:{port}/"
        ) as response:
            print("Status:", response.status)
            print("Content-type:", response.headers["content-type"])

            body = await response.text()
            print("Body:", body)

    await aioconsole.aprint("Hit enter to exit.")
    await aioconsole.ainput()
    await aioconsole.aprint("Bye!")


loop = asyncio.get_event_loop()
runner = web.AppRunner(app)
loop.run_until_complete(runner.setup())
site = web.TCPSite(runner, port=port)
loop.run_until_complete(site.start())
loop.run_until_complete(cli())

This is also a learning opportunity to me. What would be other ways of achieving this considering the client is just command line tool that can terminated if user gets tired of waiting?

Other comments regarding my usage of asyncio and aiohttp are also welcome. First time using both.

答案1

得分: 2

与其让服务器回调客户端,可能更合理的做法是让客户端定期调用服务器以检查其是否准备好。这样可以保持调用模式简单(服务器和客户端之间的调用仅单向流动)。

您可以编写自己的轮询逻辑,但您也可以考虑探索一个轮询库,它可以为您实现这个功能。

英文:

Rather than having your server call back to your client, it would probably make more sense to have your client make polling calls to your server to check if it's ready. This keeps the call pattern simple (calls between the server and client only flow one way).

You could write your own polling logic, but you might also want to explore a polling library that would implement this for you.

huangapple
  • 本文由 发表于 2023年7月18日 02:07:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76707066.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定