TCP vs. UDP socket latency benchmark - python

I have implemented a small benchmark for socket communication via TCP and UDP in Python. Surprisingly, TCP is almost exactly double as fast as UDP.
To avoid routing effects, server and client are running on the same Unix machine, but on different threads.
Maybe the code is useful. Here is the server code:
import socket
import sys
host = 'localhost'
port = 8888
buffersize = 8
server_address = (host, port)
def start_UDP_server():
socket_UDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socket_UDP.bind(server_address)
print("UDP server is running...")
while True:
data, from_address = socket_UDP.recvfrom(buffersize)
if not data: break
socket_UDP.sendto(data, from_address)
socket_UDP.close()
def start_TCP_server():
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_TCP.bind(server_address)
socket_TCP.listen(1)
print("TCP server is running...")
while True:
client, client_address = socket_TCP.accept()
while True:
data = client.recv(buffersize)
if not data: break
client.sendall(data)
client.close()
So you can run either start_TCP_server() or start_UDP_server().
On client side the code is:
import socket
import sys
import time
host = 'localhost'
port = 8888
buffersize = 8
server_address = (host, port)
client_address = (host, port+1)
N = 1000000
def benchmark_UDP():
socket_UDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socket_UDP.bind(client_address)
print("Benchmark UDP...")
duration = 0.0
for i in range(0, N):
b = bytes("a"*buffersize, "utf-8")
start = time.time()
socket_UDP.sendto(b, server_address)
data, from_address = socket_UDP.recvfrom(buffersize)
duration += time.time() - start
if data != b:
print("Error: Sent and received data are bot the same")
print(duration*pow(10, 6)/N, "µs for UDP")
def benchmark_TCP():
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_TCP.connect(server_address)
print("Benchmark TCP...")
duration = 0.0
for i in range(0, N):
b = bytes("a"*buffersize, "utf-8")
start = time.time()
socket_TCP.sendall(b)
data = socket_TCP.recv(buffersize)
duration += time.time() - start
if data != b:
print("Error: Sent and received data are bot the same")
print(duration*pow(10, 6)/N, "µs for TCP")
socket_TCP.close()
Like for the server you can start the benchmark by benchmark_TCP() or benchmark_UDP().
The results are about 25 µs for TCP, and about 54 µs for UDP on Unix and even worse for Windows (about 30 µs for TCP and more than 200 µs for UDP). Why? I would expect a minimal advantage for UDP.

Your TCP socket is connected but your UDP socket is not. This means extra processing for every send/receive on the UDP socket. Call connect on each side for the UDP socket, just like you call connect/accept on the TCP socket.
Programs like iperf do this to measure accurately.

Related

How to receive and handle multiple TCP stream sockets?

I would like to send the location of a moving point to a server via TCP with the socket module. That point location is updated at each iteration of a for loop and is sent in the form of a tuple (x, y) that has been serialized with pickle dumps methods.
Problem:
On the server side, it seems that I only get to receive the location from the first iteration of that loop. As if all the following updated positions had been skipped or lost in the process.
I can’t say for sure what is the reason behind this behavior but my bet is that I am not correctly setting things on the server side. I suspect the data to be sent entirely but not processed adequately on reception due to some mistakes that I am probably doing with the socket module (I am completely new to the world of network interfaces).
Code:
--client side--
#Python3.7
import socket
import pickle
import math
HOST = "127.0.0.1"
PORT = 12000
den = 20
rad = 100
theta = math.tau / den
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((HOST, PORT)) #connect to server
for step in range(1000):
i = step%den
x = math.cos(i*theta) * rad
y = math.sin(i*theta) * rad
data = pickle.dumps((x, y), protocol=0)
sock.sendall(data)
--server side--
#Jython2.7
import pickle
import socket
HOST = "127.0.0.1"
PORT = 12000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
while True:
connection, address = s.accept()
if connection:
data = connection.recv(4096)
print(pickle.loads(data)) # <-- only print once (first location)
You need to put connection, address = s.accept() outside the while loop otherwise your server will wait for a new connection every time.
You also have an issue with the way your are receiving data. connection.recv(4096) will return any amount of bytes between 0 and 4096 not every time a complete "data" message is received. To handle this you could send a header before sending you json indicating how much data should be received
By adding a header, you will make sure the data messages you are sending will be received properly.
The header in this example is a four bytes int indicating the size of data.
Server
import pickle
import socket
import struct
HEADER_SIZE = 4
HOST = "127.0.0.1"
PORT = 12000
def receive(nb_bytes, conn):
# Ensure that exactly the desired amount of bytes is received
received = bytearray()
while len(received) < nb_bytes:
received += conn.recv(nb_bytes - len(received))
return received
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
connection, address = s.accept()
while True:
# receive header
header = receive(HEADER_SIZE, connection)
data_size = struct.unpack(">i", header)[0]
# receive data
data = receive(data_size, connection)
print(pickle.loads(data))
Client
import socket
import pickle
import math
HEADER_SIZE = 4
HOST = "127.0.0.1"
PORT = 12000
den = 20
rad = 100
theta = math.tau / den
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((HOST, PORT)) #connect to server
for step in range(1000):
i = step%den
x = math.cos(i*theta) * rad
y = math.sin(i*theta) * rad
data = pickle.dumps((x, y), protocol=0)
# compute header by taking the byte representation of the int
header = len(data).to_bytes(HEADER_SIZE, byteorder ='big')
sock.sendall(header + data)
Hope it helps

How do I accept TCP and UDP?

Im trying to implement both TCP and UDP in my server. I can accept a TCP or UDP port connection from a client.
for example, I want to have code to accept TCP and UDP in one program:
# create a socket
sockTCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #TCP
sockUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
# server address to connect to
server_addressTCP = ('localhost', tcp_port)
server_addressUDP = ('localhost', udp_port)
# bind socket to adress and port number
sockTCP.bind(server_addressTCP)
sockUDP.bind(server_addressUDP)
# wait for connections (clients)
print("Waiting for connections...")
print(" ")
sockTCP.listen(20)
request = ''
while True:
#TCP
client_sock, client_addr = sockTCP.accept()
data, addr = client_sock.recvfrom(1024)
#UDP
udp_data, udp_addr = sockUDP.recvfrom(1024)
# DO SOMETHING WITH DATA.........
client_sock.close()
If you want to implement a server that listens on UDP and TCP then create listener threads: one for UDP and one for TCP connections.
Here is an example of a server listening on both TCP and UDP ports.
import socket
import threading
tcp_port = udp_port = 1234
def udp_listen(conn):
while True:
data = conn.recv(1024)
if len(data) != 0:
print("recv:", data)
def tcp_listen(sock):
while True:
conn, _ = sock.accept()
try:
while True:
data = conn.recv(1024)
if len(data) == 0:
break
print("recv:", data)
except Exception:
pass
try:
conn.close()
except Exception:
pass
# create a socket
sockTCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP
sockUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
# server address to connect to
server_addressTCP = ('localhost', tcp_port)
server_addressUDP = ('localhost', udp_port)
# bind socket to adress and port number
sockTCP.bind(server_addressTCP)
sockUDP.bind(server_addressUDP)
sockTCP.listen(20)
t1 = threading.Thread(target=tcp_listen, args=(sockTCP,))
t2 = threading.Thread(target=udp_listen, args=(sockUDP,))
print("Waiting for connections...")
t1.start()
t2.start()
t1.join()
t2.join()

How to run two servers in one python code

I try to make a two udp listening servers in one python code code is below,
import threading
import time
import socket
class udpreceive:
def __init__(self,port,ip):
self.port = port
self.ip = ip
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((self.ip, self.port))
def startserver(self):
while True:
time.sleep(1)
data, addr = self.sock.recvfrom(1024)
print (data)
server1 = udpreceive(514,"192.168.1.5")
s1 = threading.Thread(target=server1.startserver())
server2 = udpreceive(515,"192.168.1.5")
s2 = threading.Thread(target=server2.startserver())
s2.start()
s1.start()
this is client code for udp sender 1
import socket
import time
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
counter =0
while True:
send_data = "hellow world server1 :- "+ str(counter)
sock.sendto(send_data.encode(), ("192.168.1.5", 514))
print (send_data)
counter +=1
this is client code for sender 2
import socket
import time
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
counter =0
while True:
send_data = "hellow world server2 :- "+ str(counter)
sock.sendto(send_data.encode(), ("192.168.1.5", 514))
print (send_data)
counter +=1
When i run receive code its only show sender 1 data. start only sender 2 and start receive code but its not show any sender 2 sending data but i start sender 1 receive code show sender 1 data.
what is the wrong thing i do? i need to show both sender 1 and sender 2 data in receive program
im new to oop and socket programming
thanks
In threading module, "target" keyword argument should not contain brackets,
see How NOT to wait for a thread to finish in Python . As it should be:
threading.Thread(target=server1.startserver)
Then, the two UDP server threads shall start then join, as fairly sharing CPU resources is important (especially when using infinite loops).
The code for the server could be:
import threading
import time
import socket
class udpreceive:
def __init__(self,port,ip):
self.port = port
self.ip = ip
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((self.ip, self.port))
def startserver(self):
while True:
data, addr = self.sock.recvfrom(1024)
print(f'port {self.port} receives {data}')
s1 = udpreceive(514, "192.168.1.5")
s2 = udpreceive(515, "192.168.1.5")
threads = [threading.Thread(target=s1.startserver), threading.Thread(target=s2.startserver)]
for th in threads:
th.start()
print(f'threads {th} started')
th.join(0.1)
Meanwhile, you may need to change the code in sender 2 to:
sock.sendto(send_data.encode(), ("192.168.1.5", 515)) # Send to port 515, not 514

Client - Server socket programming with python

Below are my three scripts. I need to send the message from sricpt 1(Sensor.py) to script 2.(Client.py). And then the script 2 should send the message to script 3(Server.py).
It works fine till script 2 but the message isn't being received at the script 3 and the recvfrom() keeps waiting. There is no error but the script 3 doesn't show the message. Im using UDP. Please help.
SCRIPT 1(Sensor.py)
from socket import *
from time import ctime
CLIENT_IP = '192.168.1.109'
PORT = 23567
BUFSIZE = 1024
ADDR = (CLIENT_IP, PORT)
udpCliSock = socket(AF_INET, SOCK_DGRAM)
while True:
sendData = input("> ")
if sendData is None:
break
udpCliSock.sendto(sendData.encode(), ADDR)
udpCliSock.close()
SCRIPT 2(Client.py)
from socket import *
from time import ctime
HOST = '192.168.1.103'
CLIENT_IP='192.168.1.109'
PORT = 5005
SENSOR_PORT_NO=23567
BUFSIZE = 1024
ADDR = (HOST, PORT)
CLIENT_ADDR=(CLIENT_IP,SENSOR_PORT_NO)
udpCliSock = socket(AF_INET, SOCK_DGRAM)
client = socket(AF_INET, SOCK_DGRAM)
client.bind(CLIENT_ADDR)
while True:
print("...waiting for response...")
#recv_data, ADDR = udpCliSock.recvfrom(BUFSIZE)
recv_data, ADDR = client.recvfrom(1024)
if recv_data is not None:
recv_data = recv_data.decode()
print("[%s]: receiving data from server %s:%s :%s" % (ctime(),ADDR[0], ADDR[1], recv_data))
sendData = recv_data
udpCliSock.sendto(sendData.encode(), ADDR)
udpCliSock.close()
SCRIPT 3(Server.py)
from socket import *
from time import ctime
HOST = '192.168.1.103'
PORT = 5005
BUFSIZE = 1024
ADDR = (HOST, PORT)
udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR)
while True:
print("...waiting for message...")
data, ADDR = udpSerSock.recvfrom(BUFSIZE)
print(data.decode)
if data is None:
break
print("[%s]: From Address %s:%s receive data: %s" % (ctime(),ADDR[0],ADDR[1], data.decode()))
udpSerSock.close()
The problem is that, in the Client.py, you are overwriting the address of the server when you receive the datagram from the sensor. Here:
recv_data, ADDR = client.recvfrom(1024)
ADDR starts out with the server's socket address, but this function overwrites that variable with the sensor's socket address. So when you try to send, you're sending it back to the sensor (who of course isn't ever receiving it).
A better method is to connect that socket to the server at the beginning. Then you can just use send instead of sendto since the address won't be changing. Should work after that:
...
udpCliSock = socket(AF_INET, SOCK_DGRAM)
udpCliSock.connect(ADDR) # <<<<<<===============
client = socket(AF_INET, SOCK_DGRAM)
client.bind(CLIENT_ADDR)
while True:
print("...waiting for response...")
recv_data, addr = client.recvfrom(1024)
if recv_data is not None:
recv_data = recv_data.decode()
print("[%s]: receiving data from server %s:%s :%s" % (ctime(),addr[0], addr[1], recv_data))
sendData = recv_data
udpCliSock.send(sendData.encode()) # <<<<<<===============
udpCliSock.close()

Python socket performance drops considerably when used in parallel

I use pretty much standard code to transfer a file from one node to another using Python/socket.
Num-of-threads/Performance (comparing to sftp/scp)
1: 2x
2: 1.2x
3: 1x
4: 1x
5: 1x
It takes 25 sec to transfer 2.7G file over 10Gb network using Python/socket.
If I use fstp/scp it takes 50 sec to transfer the same file.
2 threads complete transfer in 47 sec sec using Python/socket script.
If I use fstp/scp it takes 55 sec to transfer the same 2 files in parallel.
3 threads transfer in 112 sec sec using Python/socket script.
fstp/scp does the same job in 112 sec (3 files in parallel).
Client code:
#client.py
import socket
import sys
import datetime as dt
e=sys.exit
n1=dt.datetime.now()
#s = socket.socket()
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM, proto=0)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
#s.setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)
host = 'testserver'
port = %s # Reserve a port for your service.
s.connect((host, port))
f = open('/tmp/testfile.gz','rb')
print 'Sending..',
l = f.read(1024*1024)
while (l):
print '.',
s.send(l)
l = f.read(1024*1024)
f.close()
print "Done Sending"
s.shutdown(socket.SHUT_WR)
s.close
n2=dt.datetime.now()
diff=(n2-n1)
print diff.seconds
e(0)
Server code:
#server.py
import socket # Import socket module
import sys, time
e=sys.exit
#s = socket.socket() # Create a socket object
s= socket.socket(socket.AF_INET, type=socket.SOCK_STREAM, proto=0)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
host = socket.gethostname() # Get local machine name
port = %s # Reserve a port for your service.
print port
s.bind((host, port)) # Bind to the port
f = open('/tmp/testfile_%d.png','wb')
s.listen(5) # Now wait for client connection.
i=0
while True:
c, addr = s.accept() # Establish connection with client.
c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
c.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
#c.setNoDelay(True)
#c.setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)
print 'Got connection from', addr
print "Receiving..."
if netcat:
netcat.write('netcat from file writer')
i=0
l = c.recv(1024*1024)
while (l):
f.write(l)
l = c.recv(1024*1024)
i +=1
if 0 and i>20:
f.close()
e(0)
f.close()
#c.send('Thank you for connecting')
c.close()
s.shutdown(socket.SHUT_WR)
s.close()
print "Done Receiving"
e(0)
e(0)
When I run it in 2 jobs in parallel (different ports/shells) performance drops 50%.
Alternatively when I use sftp in parallel:
time sftp user#server://tmp/testfile.gz.gz test0.gz&
time sftp user#server://tmp/testfile.gz.gz test1.gz&
time sftp user#server://tmp/testfile.gz.gz test2.gz&
elapsed time does not change for 2 or 3 parallel jobs.

Categories

Resources