如何使用Python的Asyncio在服务器和两个客户端之间进行通信?

问题描述 投票:0回答:1

我正在开发一个使用异步服务器和多个客户端的基本 Python 应用程序,但我在与第二个客户端通信时遇到一些问题。

client.py - 轮询目录中的新文件并将内容发送到服务器

# Currently just sends the contents of a known file to the server.
# The polling portion I plan on adding later
import asyncio

async def send_file(filename, server_host, server_port):
    reader, writer = await asyncio.open_connection(server_host, server_port)
    writer.write(filename.encode())
    with open(filename, 'rb') as file:
        while True:
            data = file.read(1024)
            if not data:
                break
            writer.write(data)

    writer.close()

if __name__ == '__main__':
    server_host = '127.0.0.1'  # Change to the server's IP address or hostname.
    server_port = 8888

    filename = 'temp.txt'  # Change to the file you want to send.

    asyncio.run(send_file(filename, server_host, server_port))

server.py - 等待来自客户端 #1 的数据并将其写入文件。完成后提醒客户端#2

import asyncio

async def handle_client(reader, writer):
    try:
        data = await reader.read(1024)
        if not data:
            return
        # Writes data from Client #1
        filename = data.decode().strip()
        print(f"Receiving file: {filename}")
        with open(filename, 'wb') as file:
            while True:
                data = await reader.read(1024)
                if not data:
                    break
                file.write(data)
        print(f"Received file: {filename}")
        
        # Send message to the receiver that the server is done writing
        response = "Server has written data from client.py"
        writer.write(response.encode())
        await writer.drain()
        
    except asyncio.CancelledError:
        pass
    finally:
        writer.close()

async def main():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()

if __name__ == '__main__':
    asyncio.run(main())

receiver.py - 从服务器打印完成状态

import asyncio

async def receive_data():
    reader, writer = await asyncio.open_connection('127.0.0.1', 8888)

    response = await reader.read(100)
    message = response.decode()
    print(f"Received: {message}")

    print("Closing the connection")
    writer.close()
    await writer.wait_closed()
    
async def main():
    await receive_data()

if __name__ == '__main__':
    server_host = '127.0.0.1'  # Change to the server's IP address or hostname.
    server_port = 8888

    filename = 'temp.txt'  # Change to the file you want to send.

    asyncio.run(main())

我能够让 client.py 和 server.py 成功通信,从而将位于 client 目录中的 temp.txt 的内容基本上复制到 server.py 的目录中,但我的问题是接收者.py 从不打印来自服务器的完成消息。我认为这可能与我设置连接的方式有关,但我不确定,因为我对异步任务还很陌生。

非常感谢任何帮助,谢谢!

python-3.x asynchronous python-asyncio
1个回答
0
投票

每个程序都将作为不同客户端连接到您的服务器程序 - 这意味着“响应”消息将写入“client.py”持有的连接中,并且根本不会在单独的连接中传输任何字节,由“receiver.py”持有。

Asyncio“做它的事情”,以便连接到服务器的每个客户端程序将对“handle_client”进行不同的调用,该调用将与任何其他调用同时运行。 因此,在您的代码中,第二个并发调用将在 server.py 中对

handle_client
进行,这可能会永远等待一些数据到达该连接,因为您的“receiver.py”不发送任何内容 - (或者只是完成,因为没有数据 - 不确定)。

如果您需要两个不同的程序与服务器进行通信,服务器将必须“知道”它们正在与其中的哪一个进行通信,并且需要某种内部机制来跨不同的“任务”传输其内部数据 - 每个任务处理一个连接。

您可以在不同的端口号中启动单独的服务器,以便具有不同角色的程序将每个服务器连接到适当的端口号,或者向您的临时协议添加一些带内信息,以便服务器知道识别“接收者”程序,而不是客户端。

因此,我更改了您的代码来做到这一点:如果发送的文件名是“接收者”,服务器会在另一个仅发送“已完成”消息的函数中单独处理它。我还包括一个最小的计数器机制,以便任意数量的“receiver.py”程序可以同时连接到服务器,并且当第一个“客户端”程序连接并发送文件时,所有接收者都会收到通知。

(client.py 有一个最小的修改,只需在文件名后发送一个换行符。“receiver.py”也仅更改为发送“receiver”作为文件名,因此服务器将相应地对待它)

服务器.py

import asyncio
from queue import Empty


receiver_count = 0
receiver_msg_queue = asyncio.Queue()

async def handle_client(reader, writer):
    global receiver_count
    try:
        # read only one line of text as the file name (readline(), not "read(1024)"
        data = await reader.readline()
        if not data:
            return
        # Writes data from Client #1
        filename = data.decode().strip()

        # if the connection is from a receiver program, handle it in a different function
        # altogether:
        if filename == "receiver":
            receiver_count += 1
            print("receiver connected")
            await handle_receiver(writer)
            return
        print(f"Receiving file: {filename}")
        # I added a sufix to the file, so I can run things on the same directory:
        with open(filename + ".copy", 'wb') as file:
            while True:
                data = await reader.read(1024)
                if not data:
                    break
                file.write(data)
        print(f"Received file: {filename}")

        # Send message to the receiver that the server is done writing
        response = "Server has written data from client.py"
        receiver_msg_queue.put_nowait((response, 0))
        writer.write(response.encode())
        await writer.drain()

    except asyncio.CancelledError:
        pass
    finally:
        writer.close()

async def handle_receiver(writer):
    """will be called once, concurrently, for each receiver program connected"""
    try:
        message, count = await receiver_msg_queue.get()
        writer.write(message.encode())
        if count < receiver_count - 1:
            # replenish the queue so other connected receivers get the message
            receiver_msg_queue.put_nowait((message, count + 1))
    except asyncio.CancelledError:
        writer.close()


async def main():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()

if __name__ == '__main__':
    asyncio.run(main())

客户端.py

...

async def send_file(filename, server_host, server_port):
    reader, writer = await asyncio.open_connection(server_host, server_port)
    writer.write(filename.encode() + b"\r\n")
    ...

...

接收器.py

...
    reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
    writer.write(b"receiver\r\n")
    response = await reader.readline()
...

© www.soinside.com 2019 - 2024. All rights reserved.