Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple way to simulate a slow network in python

Scenario. I have a client with two network connections to a server. One connection uses a mobile phone, and the other is using a wlan connection.

The way that I have solved this is by having the server listen at two ports. But, the mobile connection should be slower than the wlan connection. The data that is sent, is usually just a line of text. I have solved the slower connection issue by allowing the mobile connection to send data in a five second interval, the wlan connection have an interval of one second.

But is there a better way of doing the slowdown? Perhaps by sending smaller packets, meaning I need to send more data?

Any ideas on a good way to slow down one of the connections?

Orjanp

A simple client example with only one connection.

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()

A simple server listening to one port.

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()
like image 460
Orjanp Avatar asked Dec 02 '09 15:12

Orjanp


2 Answers

The simulation of slow connections can be achieved easily with the poorconn Python package:

pip install poorconn

For example, in the following code, the function client_socket()/server_socket() returns a client/server socket object that delay roughly 2 seconds for sending every 1024 bytes of messages.

from socket import socket
from poorconn import delay_before_sending, make_socket_patchable

def client_socket():
    s = socket()
    s = make_socket_patchable(s)
    delay_before_sending(s, 2, 1024)
    return s

def server_socket():
    s = socket()
    s = make_socket_patchable(s)
    delay_before_sending_upon_acceptance(s, 2, 1024)
    return s

Then, use the returned socket objects like normal socket objects.

Disclaimer: I'm the main author of poorconn. Feedback is welcome.

like image 77
xuhdev Avatar answered Oct 23 '22 02:10

xuhdev


Aside from using an external tool to simulate the kind of network you're interested in, one good approach is to use a substitute implementation of socket.

This involves making the socket construction a parameter to your function, instead of importing the socket module and using it directly. For normal operation, you will pass in the real socket type, but when you want to test various adverse network conditions, you can pass in an implementation that simulates those conditions. For example, you might create a socket type which parameterizes latency and bandwidth (untested code, beware):

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)

If you implement the other socket methods, connect, recv, etc, you can substitute an instance of this class for an instance of the real socket type. This leaves all the rest of your program completely free of any knowledge of your simulation, simplifying it, while also letting you try out many different network configurations by just implementing a new socket type that simulates them.

This idea is one of the reasons Twisted explicitly separates the idea of "protocols" - objects which know how to interpret bytes from the network and generate new bytes to send to the network - from "transports" - objects which know how to get bytes off the network and put bytes onto it. The separation eases testing and allows novel configurations like this one, where a simulation of some other network conditions (which may be difficult to produce for real) is provided by the transport.

like image 24
Jean-Paul Calderone Avatar answered Oct 23 '22 04:10

Jean-Paul Calderone