如何最好地在python中实现服务器和客户端?

时间:2019-04-11 15:16:21

标签: python server client ipc shared-memory

我有一个大文件需要加载到内存中,然后可以根据用户输入进行一些操作。但是我不想在有用户输入的情况下一次又一次地将文件加载到内存中。

一种解决方案可能是通过一个进程将数据文件作为“服务器”加载,并让另一个客户端进程代表该客户端查询服务器。

我想知道最好的客户端-服务器实现方案是什么。我知道我可以实现HTTP服务器,但是查询它必须遵循HTTP协议,该协议开销太大(对于我来说,客户端只需要向服务器发送字符串,因此不需要所有HTTP标头。 )更轻的溶液是优选的。另外,客户端和服务器都应该在同一台机器上运行,因此使用内存比使用网络在客户端和服务器之间共享信息要快?

实际上,服务器可以将数据作为某些python对象加载到内存中,如果有一种从客户端访问这些python对象的方法,也应该没事。

有人可以提供有关解决此问题的最佳解决方案的建议吗?谢谢。

1 个答案:

答案 0 :(得分:1)

好的,因此,基于注释,数据由字符串作为键,值是列表或字典,并且客户端按字符串请求对象。

不幸的是,没有一种安全,理智的方法可以直接访问这种数据跨进程,而无需一些中间的序列化/反序列化步骤。除了安全性问题外,一个显而易见的选择是picklemsgpack也很合理。

对于协议,如果经过反复测试的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的生成方式中可以看到,有效载荷可能很大。)

希望这会有所帮助。