【问题标题】:Send multiply files with asyncio server使用异步服务器发送多个文件
【发布时间】:2021-07-13 22:53:20
【问题描述】:

这里是 server.py

import asyncio
import aiofile

async def handler(reader, writer):
    data = await reader.read(n=-1)
    addr = writer.get_extra_info('peername')
    print('Addr', addr)
    # photo_120721_215652.jpg
    name = data[:23].decode()
    async with aiofile.async_open(name, 'wb') as afp:
        await afp.write(data[23:])
    print("handler end")


async def main():
    server = await asyncio.start_server(handler, '0.0.0.0', 8888)
    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')
    async with server:
        await server.serve_forever()

asyncio.run(main())

这里是client.py

async def send_photos_to_server(filelist):
    retry = 0
    for file in filelist:
        while True:
            try:
                reader, writer = await asyncio.open_connection('192.168.1.100', 8888)
            except OSError:
                while retry != 5:
                    await asyncio.sleep(3)
                    retry += 1
            else:
                break # exit the loop 
        if retry == 5:
            print("No connection after {} retries")
            break

        name = file.split('/')[-1].encode()
        writer.write(name)
        await writer.drain()

        async with aiofile.async_open(file, 'rb') as afp:
            afp_reader = await afp.read(length=-1)
        writer.write(afp_reader)
        await writer.drain()
        writer.close()
        await writer.wait_closed()

filelist = ['./photos/photo_120721_215652.jpg',
            './photos/photo_120721_215654.jpg',
            './photos/photo_120721_215656.jpg']

asyncio.run(send_photos_to_server(filelist))

所以这段代码是有效的。但我不确定这是否是正确的方法。每次发送一个新文件,我们都会创建一个新的连接。当然,我可以将所有三个文件打包成一个存档,但我不确定它是否方便。此外,将文件名放入发送消息很不方便,因为在服务器端,我必须以某种方式找到它。那么有人可以解释如何修改此代码以获得更好的实践吗?我刚开始学习异步io,并不擅长。

【问题讨论】:

  • 你尝试了什么?您是否尝试在for-loop 之前打开连接,并在for-loop 之后关闭它?但我认为它需要对handler 进行重大更改——它只是为了发送一个文件而创建的。它需要一些循环来发送许多文件。其他问题可能是发送名称 - 它可能会将所有名称作为单个字符串获取。在最后一个文件名之后可能还需要它是姓氏的信息。所以它可能更复杂,使用压缩zip 可能更简单。
  • 您可以将所有名称作为一个带有分隔符的字符串发送,即。 ; 和处理程序应使用 split(';') 来获取名称列表。如果它得到一个名字以上的列表,那么它应该使用将所有文件压缩到一个 zip 文件中。或者它应该发送带有一些标头的数据,该标头将包含每个图像的负载信息,并将所有图像作为一个数据发送。所以你必须创建自己的protocol(描述客户端和服务器应该如何通信的规则——比如在协议中HTTPFTPSMTP
  • 在正常情况下在数据之前发送文件名 - 每个协议在数据之前发送一些额外的信息(标题)。例如HTTP首先发送标题,下一个空行和下一个数据(正文),另一方可以逐字节读取标题,直到它得到空行(两个ENTERS - '\n\n'),并且其中一个标题有信息有多少字节有正文- 所以它会知道它必须读取多少字节的数据(正文)。
  • "您尝试了什么?您是否尝试在 for-loop 之前打开连接,并在 for-loop 之后关闭它?但我认为它需要对处理程序进行重大更改 - 它只是为了发送而创建的一个文件”-您在谈论服务器,这很好。它旨在在接收到连接后执行handler 功能,然后将其保存到文件中。我的问题更多是关于客户端及其工作方式。我明白了你对文件名的建议。
  • 您不能只更改客户端 - 您还必须更改服务器。如果客户端将使用一个连接和 for-loop 来发送许多文件,那么服务器也必须使用for-loop 来获取所有文件。

标签: python asynchronous server file-transfer


【解决方案1】:

服务器使用protocols - 它测量的规则描述了一方必须发送什么以及如何在另一个大小上读取它。

例如HTTP 首先发送标题、下一个空行和下一个数据(正文)。其中一个标头包含正文多长时间的信息。

对方首先要读取数据byte after byte,直到找到空行('n\n'),然后它有标题,它可以找到正文的长度,并读取正确的字节数。


您可以创建自己的协议。

我假设您想在一个连接中发送所有文件。

你可以发送文件数量+\n,接下来使用for-loop发送文件名+\n,文件大小+\n,文件数据。

另一方也应该做类似的-首先读取所有\n以获取文件数,然后使用for-loop再次读取所有\n以获取文件名,全部读取\n以获取大小,使用size读取文件数据。


它表明使用 zip 压缩数据并发送它可能更简单 - 另一方将在这个 zip 文件中包含所有信息 - 文件名、大小、数据。

当然你可以减少它并且仍然以单独的连接发送每个文件。


示例代码

server.py

import asyncio
import aiofile

async def handler(reader, writer):

    # --- before for-loop ---
    
    data = await reader.read(n=-1)
    addr = writer.get_extra_info('peername')
    print('Addr', addr)

    # get number of images + `\n`
    start = 0
    end = data.find(b'\n', start)
    item = data[start:end]
    print('[DEBUG] start,end:', start, end, item)
    number = int(item.decode())
    print('number:', number)
    
    print('--------')

    # --- for-loop ---

    for _ in range(number):
        # get filename + '\n' 
        start = end+1
        end = data.find(b'\n', start)
        item = data[start:end]
        print('[DEBUG] start,end:', start, end, item)
        name = item.decode()
        print('name:', name)

        # get size + '\n'
        start = end+1
        end = data.find(b'\n', start)
        item = data[start:end]
        print('[DEBUG] start,end:', start, end, item)
        size = int(item.decode())
        print('size:', size)
        
        # get data
        start = end+1
        end = start+size-1
        item = data[start:end]
        print('[DEBUG] start,end:', start, end, item[:10])
        async with aiofile.async_open(name, 'wb') as afp:
            await afp.write(item)
            
        print('--------')

    # --- after for-loop ---
        
    print("handler end")


async def main():
    server = await asyncio.start_server(handler, '0.0.0.0', 8888)
    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')
    async with server:
        await server.serve_forever()

asyncio.run(main())

client.py

import asyncio
import aiofile

async def send_photos_to_server(filelist):

    # --- before for-loop ---
    
    retry = 0

    while True:
        try:
            reader, writer = await asyncio.open_connection('0.0.0.0', 8888)
        except OSError:
            while retry != 5:
                await asyncio.sleep(3)
                retry += 1
        else:
            break # exit the loop 
    if retry == 5:
        print("No connection after {} retries")
        return
    
    # send number of images + '\n'
    text = str(len(filelist)) + '\n'
    writer.write(text.encode())
    await writer.drain()
    
    # --- for-loop ---
    
    for filename in filelist:

        # send filename + '\n'
        name = filename.split('/')[-1]
        text = name + '\n'
        writer.write(text.encode())
        await writer.drain()

        async with aiofile.async_open(filename, 'rb') as afp:
            afp_reader = await afp.read(length=-1)

        # send size + '\n'
        size = str(len(afp_reader)) 
        text = size + '\n'
        writer.write(text.encode())
        await writer.drain()

        # send data
        writer.write(afp_reader)
        await writer.drain()
        
        
    # --- after for-loop ---
    
    writer.close()
    await writer.wait_closed()

filelist = [
    '/home/furas/test/image1.png',
    '/home/furas/test/image2.png',
    '/home/furas/test/image3.png',
]

asyncio.run(send_photos_to_server(filelist))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-20
    • 2015-11-13
    相关资源
    最近更新 更多