【问题标题】:Handle multiple socket connections处理多个套接字连接
【发布时间】:2012-10-13 22:36:21
【问题描述】:

我正在用 Python 编写一个客户端-服务器应用程序。这个想法是拥有一个主服务器和数千个将与之连接的客户端。服务器将随机发送小文件给客户端进行处理,客户端必须每分钟完成工作并将其状态更新到服务器。我的问题是,目前我只有一个又小又旧的家庭服务器,所以我认为它无法处理这么多的连接。也许你可以帮我解决这个问题:

  • 如何增加我的服务器中的连接数?
  • 如何平衡来自客户端的负载?
  • 如何改善沟通?我的意思是,我需要在服务器上有一个客户端列表及其状态(可能在数据库中?),并且会不时收到此更新,因此我不需要永久连接。使用 UDP 发送更新是个好主意吗?如果不是,我是否必须在每次收到更新时创建一个新线程?

编辑:我更新了问题以更好地解释问题,但主要是为了让有同样问题的人足够清楚。 @TimMcNamara 答案中实际上有一个很好的解决方案。

【问题讨论】:

    标签: python sockets client-server


    【解决方案1】:

    为成功做好准备:访问模式很重要

    哪些设计决策可能会影响您实施网络解决方案的方式?您立即开始列出一些:

    • 可编程性
    • 可用内存
    • 可用处理器
    • 可用带宽

    这看起来很不错。我们想要一些易于编程且规格相当高的东西。但是,这个列表失败了。我们在这里所做的只是查看服务器。这可能是我们在 Web 应用程序中可以控制的全部内容,但是我们可以完全控制的分布式系统(例如传感器网络)呢?

    假设我们有 10,000 台设备想要用它们每分钟更新的最新传感器读数更新您。现在,我们可以使用与所有设备保持并发连接的高端服务器。

    但是,即使您拥有一台非常高端的服务器,您仍然可能会遇到性能问题。如果所有设备都使用相同的时钟,并且都尝试在每分钟的顶部发送数据,那么服务器将在每分钟 1-2 秒内执行大量 CPU 工作,其余时间则不做任何事情。效率极低。

    由于我们可以控制传感器,我们可以要求它们自行进行负载平衡。一种方法是给每个设备一个 ID,然后使用模数运算符仅在每分钟正确的时间发送数据:

    import time        
    
    def main(device_id):
        data = None
        second_to_send = device_id % 60
        while 1:
            time_now = time.localtime().tm_sec
            if time_now == 0:
                data = read_sensors()
            if time_now == second_to_send and data:
                send(data)
            time.sleep(1)
    

    这种负载平衡的一个结果是我们不再需要如此高功率的服务器。我们认为与所有人保持联系所需的内存和 CPU 并不是必需的。

    我在这里想说的是,您应该确保您的特定解决方案专注于整个问题。根据您提供的简短描述,我们似乎不需要一直保持大量连接。但是,假设我们确实需要 100% 的连接。我们有什么选择?

    非阻塞网络

    非阻塞 I/O 的影响意味着在没有文件描述符的情况下向文件描述符请求数据的函数会立即返回。对于网络,这可能会很糟糕,因为尝试从套接字读取的函数不会向调用者返回任何数据。因此,有时生成一个线程然后调用read 会简单得多。这样线程内部的阻塞不会影响程序的其余部分。

    线程的问题包括内存效率低下、线程创建所涉及的延迟以及与上下文切换相关的计算复杂性。

    为了利用非阻塞 I/O,您可以在 while 1: 循环中潜在地轮询每个相关的文件描述符。那就太好了,除了 CPU 会以 100% 运行。

    为避免这种情况,我们创建了基于事件的库。当没有工作要做时,它们将以 0% 的速度运行 CPU,仅在要读取数据以发送时激活。在 Python 世界中,TwistedTornadogevent 是大玩家。但是,有many options。特别是,diesel 看起来很有吸引力。

    以下是 Tornado 网页的相关摘录:

    由于它是非阻塞的,并且使用 epoll 或 kqueue,它可以同时处理数千个站立连接,这意味着它非常适合实时 Web 服务。

    这些选项中的每一个都采用略有不同的方法。 Twisted 和 Tornado 在方法上非常相似,都依赖于非阻塞操作。 Tornado 专注于 Web 应用程序,而 Twisted 社区则对更广泛的网络感兴趣。随后有更多用于非 HTTP 通信的工具。

    gevent 不同。该库修改了套接字调用,以便每个连接都在一个极其轻量级的类似线程的上下文中运行,尽管实际上这对您作为程序员是隐藏的。每当出现阻塞调用时,例如数据库查询或其他 I/O,gevent 都会非常快速地切换上下文。

    每个选项的结果是您可以在单个操作系统线程中为多个客户端提供服务。

    调整服务器

    您的操作系统对其允许的连接数施加了限制。如果您达到您正在谈论的数字,您可能会达到这些限制。特别是,Linux 在/etc/security/limits.conf 中维护每个用户的限制。您可以通过在 shell 中调用 ulimit 来访问用户的限制:

    $ ulimit -a 核心文件大小(块,-c)0 数据段大小 (kbytes, -d) 无限制 调度优先级 (-e) 0 文件大小(块,-f)无限制 待处理信号 (-i) 63357 最大锁定内存 (kbytes, -l) 64 最大内存大小 (kbytes, -m) 无限制 打开文件 (-n) 1024 管道大小(512 字节,-p)8 POSIX 消息队列(字节,-q)819200 实时优先级 (-r) 0 堆栈大小(千字节,-s)8192 cpu时间(秒,-t)无限制 最大用户进程 (-u) 63357 虚拟内存 (kbytes, -v) 无限制 文件锁 (-x) 无限制

    我在这里加注了最相关的一行,即open files。打开的外部连接被认为是打开的文件。一旦达到 1024 限制,任何应用程序都无法打开另一个文件,任何客户端也无法连接到您的服务器。假设您有一个用户 httpd 作为您的 Web 服务器。这应该让您了解可以进行哪些修改来提高该限制:

    httpd soft nofile 20480
    httpd hard nofile 20480
    

    对于极高的容量,您可能会达到系统范围的限制。您可以通过cat /proc/sys/fs/file-max查看:

    $ cat /proc/sys/fs/file-max
    801108
    

    要修改此限制,请使用sudo sysctl -w fs.file-max=n,其中 n 是您希望允许的打开文件数。修改 /etc/sysctl.conf 使其在重启后仍然有效。

    【讨论】:

    • 首先感谢@TimMcNamara 的精彩回答。这些信息对我的小项目非常有帮助。但让我超越。想象一下,我正在处理更多的连接(比如说 100k 或更多)。我能做些什么来改善客户端和服务器之间的通信。正如我所说,我只需要将数据从客户端发送到服务器即可更新客户端状态。例如,每分钟都会发生这种情况,如果我丢失一些更新也没关系。服务器将只向客户端发送一次数据(可能是一个小文件),每次发送一次,并且仅在请求时发送。
    • 我重写了问题以更好地解释它并问最后一件事。当然,您的回答是我从 StackOverflow 用户@TimMcNamara 那里收到的最好的答案(这真的很难),我现在将其设置为已接受,但是,您能帮我解决我的最后一个问题吗?关于通信本身(套接字),在第三点。再次感谢您的帮助。
    • 有时间我会添加一些注释。您应该考虑有效的序列化和压缩:使用 Google Protocol Buffers 和 gzip 或 snappy。
    • 非常感谢。我现在正在阅读有关 Tornado 和 Twisted 的信息,而 Google Protocol Buffers 是我列表中的下一个。
    • 只是想让您知道您对使用 Tornado 或 Twisted 的建议非常有用。我已经阅读了很多关于它们的信息,并且进行了一些测试。现在我可以使用更少的资源建立更多的连接。我只需要研究如何在客户端和服务器发送有关其状态的更新时优化它们之间的通信。再次感谢您的帮助!
    【解决方案2】:

    一般来说,即使在非常普通的家庭服务器上同时拥有数万个套接字也没有问题。

    只要确保您没有为每个连接创建新线程或进程即可。

    【讨论】:

    • 我正在考虑这样做^^有什么建议吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-07
    • 2010-12-07
    • 1970-01-01
    • 2012-08-27
    • 1970-01-01
    相关资源
    最近更新 更多