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

Posted

技术标签:

【中文标题】如何在python中最好地实现服务器和客户端?【英文标题】:How to best implement server and client in python? 【发布时间】:2019-04-11 15:16:21 【问题描述】:

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

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

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

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

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

【问题讨论】:

文件是什么数据? 没关系。因为我寻找的解决方案应该独立于数据。例如,如果数据只是一个表,我可以只使用 sqlite3。这不是我想要的。 嗯,它确实有点重要,所以我们可以尝试找出一个尽可能适合您的用例的解决方案。例如,您需要如何访问客户端中的数据?一个二进制数组的切片?一次全部?等等。 使用mmap怎么样? docs.python.org/2/library/mmap.html 字符串不会一次全部发送。每次只发送一个字符串。返回的结果是字典或列表,可以很大。所以我不希望服务器计算一次结果并将其发回,因为这会导致额外的副本。我认为直接从客户端访问服务器中的对象可能会更好。 【参考方案1】:

好的,那么基于cmets,数据以字符串为键,值是列表或字典,客户端通过字符串请求对象。

不幸的是,没有安全、健全的方法可以直接跨进程访问此类数据,而无需一些中间序列化/反序列化步骤。一个明显的选择,除了安全问题,是pickleing 他们。 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 的生成方式可以看出,有效载荷可能非常大。)

希望这会有所帮助。

【讨论】:

SQLite 绝对不是解决方案,因为它涉及到磁盘。根据磁盘的不同,性能可能会很慢。一切都必须在记忆中。数据上会有计算,进行计算并发送结果(很小)效率更高。如果直接获取数据然后在客户端进行计算,客户端获取的数据会很大。所以任何数据库解决方案都不适合我的问题。 连接方式也是一个问题。我认为在 C 语言中有一种方法可以使一段数据持久保存在内存中。 (ipcsipcrm 可以显示共享内存的信息并删除一块共享内存)。有没有办法让 python 对象在内存中持久化? 您可以使用 ctypes 调用mlockall,但我认为您在这里过早地优化了一些东西。你有没有尝试过 SQLite?它的性能可能令人惊讶。 @user1424739 请查看我的编辑 - 我添加了一个示例服务器/客户端。

以上是关于如何在python中最好地实现服务器和客户端?的主要内容,如果未能解决你的问题,请参考以下文章

C# 如何实现服务端发送对象到客户端

在不处理的情况下在线获取信用卡信息——如何最好地做到这一点?

如何最好地与客户分享 iOS 应用程序进度

客户端服务器上安装的二进制程序如何方便地在授权服务器上进行授权(有有效期)?

实时数据流到多个客户端

如何最好地在两个 ASP.NET Core 服务器应用程序之间进行通信? [关闭]