Как лучше всего реализовать сервер и клиент в Python? - PullRequest
0 голосов
/ 11 апреля 2019

У меня есть большой файл, который нужно загрузить в память, тогда некоторые операции могут быть выполнены на основе пользовательского ввода.Но я не хочу загружать файл в память снова и снова, когда есть пользовательский ввод.

Решением может быть загрузка файла данных процессом как «сервером» и наличие другого клиентапроцесс запроса сервера от имени клиента.

Мне интересно, какова лучшая реализация клиент-сервер для этого.Я знаю, что мог бы реализовать сервер HTTP, но для его запроса необходимо следовать протоколу HTTP, который требует слишком много накладных расходов (для моего конкретного случая клиенту нужно только отправить строку на сервер, поэтому все заголовки HTTP не нужны.) Более легкое решение является предпочтительным.Кроме того, и клиент, и сервер должны работать на одном компьютере, поэтому использование памяти быстрее, чем использование сети для обмена информацией между клиентом и сервером?

На самом деле сервер может просто загрузитьданные в память в виде некоторых объектов Python, если есть способ получить доступ к этим объектам Python с клиента, все должно быть в порядке.

Может кто-нибудь дать совет относительно лучшего решения для решения этой проблемы?Спасибо.

1 Ответ

1 голос
/ 11 апреля 2019

Хорошо, поэтому, основываясь на комментариях, данные вводятся в виде строки, а значения представляют собой списки или словари, а клиент запрашивает объект в виде строки.

К сожалению, не существует безопасного, вменяемого способа прямого доступа к этомусортировка данных перекрестного процесса без некоторого промежуточного этапа сериализации / десериализации.Очевидным выбором, за исключением вопросов безопасности, является pickle их.msgpack тоже разумно.

Что касается протокола, если проверенный HTTP слишком медленный для вас, для такого простого цикла запрос-ответ, возможно, просто попросите клиента отправить ключ наполучить, за которым следует нулевой символ или символ новой строки или еще много чего, и сервер напрямую ответит сериализованным объектом, а затем закроет соединение.

Возможно, вы захотите просто сохранить сериализованные данные в базе данных,будь то SQLite или что-то еще.


РЕДАКТИРОВАТЬ: Я решил немного поэкспериментировать.Вот небольшой, довольно наивный asyncio + msgpack сервер + клиент, который делает свое дело:

server.py

import asyncio
import random
import msgpack
import time
from functools import lru_cache


def generate_dict(depth=6, min_keys=1, max_keys=10):
    d = {}
    for x in range(random.randint(min_keys, max_keys)):
        d[x] = (
            generate_dict(
                depth=depth - 1, min_keys=min_keys, max_keys=max_keys
            )
            if depth
            else "foo" * (x + 1)
        )
    return d


DATA = {f"{x}": generate_dict() for x in range(10)}


@lru_cache(maxsize=64)
def get_encoded_data(key):
    # TODO: this does not clear the cache upon DATA being mutated
    return msgpack.packb(DATA.get(key))


async def handle_message(reader, writer):
    t0 = time.time()
    data = await reader.read(256)
    key = data.decode()
    addr = writer.get_extra_info("peername")
    print(f"Sending key {key!r} to {addr!r}...", end="")
    value = get_encoded_data(key)
    print(f"{len(value)} bytes...", end="")
    writer.write(value)
    await writer.drain()
    writer.close()
    t1 = time.time()
    print(f"{t1 - t0} seconds.")


async def main():
    server = await asyncio.start_server(handle_message, "127.0.0.1", 8888)

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

    async with server:
        await server.serve_forever()


asyncio.run(main())

client.py

import socket
import msgpack
import time


def get_key(key):
    t0 = time.time()
    s = socket.socket()
    s.connect(("127.0.0.1", 8888))
    s.sendall(str(key).encode())
    buf = []
    while True:
        chunk = s.recv(65535)
        if not chunk:
            break
        buf.append(chunk)
    val = msgpack.unpackb(b"".join(buf))
    t1 = time.time()
    print(key, (t1 - t0))
    return val


t0 = time.time()
n = 0
for i in range(10):
    for x in range(10):
        assert get_key(x)
        n += 1
t1 = time.time()
print("total", (t1 - t0), "/", n, ":", (t1 - t0) / n)

На моем Mac

  • это занимает около 0,02814 секунд на сообщение на принимающей стороне, при пропускной способности одного потребителя 35 запросов в секунду.
  • это занимает около 0,00241 секундыза сообщение на стороне обслуживания, для пропускной способности 413 запросов в секунду.

(И как вы можете видеть из того, как генерируется DATA, полезные нагрузки могут быть довольно большими.)

Надеюсь, это поможет.

...