在Python中模拟慢速网络的简单方法

9 投票
4 回答
15719 浏览
提问于 2025-04-15 16:36

场景:我有一个客户,他有两条网络连接到一个服务器。一条是用手机连接,另一条是用无线网络(wlan)连接。

我解决这个问题的方法是让服务器在两个端口上监听。但是,手机连接的速度应该比无线网络连接慢。通常发送的数据只是一些文本。我通过让手机连接每五秒发送一次数据,而无线网络连接每秒发送一次数据,来解决这个速度慢的问题。

不过,有没有更好的方法来让连接变慢呢?比如说发送更小的数据包,这样我就需要发送更多的数据了?

有没有什么好的办法来减慢其中一个连接的速度呢?

Orjanp

这是一个简单的客户端示例,只包含一个连接。

def client():
    import sys, time, socket

    port = 11111
    host = '127.0.0.1'
    buf_size = 1024

    try:
        mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        mySocket.connect((host, port))
    except socket.error, (value, message):
        if mySocket:
            mySocket.close()
        print 'Could not open socket: ' + message
        sys.exit(1)
    mySocket.send('Hello, server')
    data = mySocket.recv(buf_size)
    print data
    time.sleep(5)

    mySocket.close()

client()

这是一个简单的服务器,监听一个端口。

def server():
    import sys, os, socket

    port = 11111
    host = ''
    backlog = 5
    buf_size = 1024

    try:
        listening_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        listening_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 
        listening_socket.bind((host, port)) 
        listening_socket.listen(backlog)
    except socket.error, (value, message):
        if listening_socket:
            listening_socket.close()
        print 'Could not open socket: ' + message
        sys.exit(1)

    while True:
        accepted_socket, adress = listening_socket.accept()
        data = accepted_socket.recv(buf_size)
        if data:
            accepted_socket.send('Hello, and goodbye.')
        accepted_socket.close()

server()

4 个回答

2

网络连接“慢”的原因主要是延迟和带宽。如果你想要一个真实的模拟,就需要找出你手机连接的带宽和延迟,然后在你的客户端程序中模拟这些参数。

不过你似乎是说你发送的数据很少,所以带宽可能对你的连接速度影响不大。这样的话,你只需要模拟延迟,也就是在每次发送数据包之间“睡眠”一段时间,延迟时间就是你设定的。5秒听起来有点长。

如果你觉得带宽可能有影响,其实模拟起来也很简单:带宽就是你每秒能发送的最大字节数,而延迟则是数据到达目的地所需的时间。

具体怎么做呢:你可以设置一个全局的时间戳“blockedUntil”,表示你的连接在什么时候可以再次发送数据。程序开始时把它初始化为0,假设此时连接还没有被使用。每当你有一个数据包要发送时,如果“blockedUntil”小于当前时间,就把它设置为当前时间。然后计算写入你虚拟“线路”所需的时间,方法是用数据包的大小除以带宽,这样就能得到一个时间段,再加上延迟,把这个总时间加到“blockedUntil”上。

接下来计算dt = blockedUntil - 当前时间,把数据包放入队列,并设置一个定时器,在“dt”时间后触发,这样就会发送队列中的第一个数据包。

这样,你就模拟了带宽和延迟。

补充:有人提到丢包的问题。你可以通过设置一个丢包的概率来模拟这个情况。注意:这只适用于处理像以太网或UDP这样的无连接协议的数据包。在TCP的情况下,这种方法就不适用了。

4

虽然我可能没有直接回答你问的问题,但我建议你可以找一些更底层的软件来实现这个功能。

Netlimiter 是一个适用于Windows的工具。我觉得 BWMeterBandwidth Controller 也可以做到这一点。

pyshaper 是一个类似的工具,适用于Linux,而且是开源的。你可能可以把它导入到你的Python程序里。

(另外,你也可以考虑一下,你的路由器可能已经能够按照你想要的方式来管理流量。不过,这样的话,你的软件就需要依赖这个路由器,配置起来可能会比较麻烦。)

13

除了使用外部工具来模拟你感兴趣的网络情况,还有一个不错的方法就是使用替代的socket实现。

这个方法的核心是把socket的创建过程变成你函数的一个参数,而不是直接导入socket模块并使用它。正常情况下,你会传入真实的socket类型,但当你想测试一些不好的网络条件时,就可以传入一个模拟这些条件的实现。例如,你可以创建一个socket类型,它可以设置延迟和带宽(注意,这段代码未经测试):

import time, socket

class ControllableSocket:
    def __init__(self, latency, bandwidth):
        self._latency = latency
        self._bandwidth = bandwidth
        self._bytesSent = 0
        self._timeCreated = time.time()
        self._socket = socket.socket()

    def send(self, bytes):
        now = time.time()
        connectionDuration = now - self._timeCreated
        self._bytesSent += len(bytes)
        # How long should it have taken to send how many bytes we've sent with our
        # given bandwidth limitation?
        requiredDuration = self._bytesSent / self._bandwidth
        time.sleep(max(requiredDuration - connectionDuration, self._latency))
        return self._socket.send(bytes)

如果你实现了其他socket方法,比如connect、recv等,你就可以用这个类的实例替代真实socket类型的实例。这样,你程序的其他部分就完全不需要知道你在做模拟,简化了程序,同时也让你可以通过实现一个新的socket类型来尝试许多不同的网络配置。

这个想法是Twisted明确区分“协议”和“传输”的原因之一。“协议”是指那些知道如何解释网络字节并生成新字节发送到网络的对象,而“传输”则是指那些知道如何从网络获取字节并将字节放到网络上的对象。这种区分简化了测试,并允许像这样的新配置,其中由传输提供了某些其他网络条件的模拟(这些条件在现实中可能很难实现)。

撰写回答