英文:
Python UDP throughput is much slower than TCP throughput
问题
以下是翻译的代码部分:
I made two applications ('Client' and 'Server') written in Python.
On each of them beyond the main thread, I created two additional threads, which are handling operations of sending / receiving data from each TCP / UDP parts.
So for the client, I have written code for:
- clientTCP part:
class ClientTCP:
def __init__(self, host_address: str, port: int):
self.client_socket = None
self.host_address = host_address
self.port = port
def connect_to_server(self, is_nagle_disabled: bool):
try:
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if is_nagle_disabled:
self.client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
self.client_socket.connect((self.host_address, self.port))
except socket.error:
print('Error: probably wrong port passed for connection')
sys.exit(1)
# Other methods...
def tcp_send_data_to_server(client: ClientTCP, data_to send: list[int], size_of_buffer: int, is_nagle_disabled: bool, stop):
try:
client.connect_to_server(is_nagle_disabled)
# Other code...
except ConnectionResetError:
print("Socket was closed due to some unknown reasons. Sorry. :(")
# Other code...
- senderUDP
class SenderUDP:
def __init__(self, host_address: str, port: int):
self.client_socket = None
self.host_address = host_address
self.port = port
def connect_to_server(self):
try:
self.client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
except socket.error as e:
print(str(e))
sys.exit(1)
# Other methods...
def sending(sender: SenderUDP, data_to_send: list[int], size_of_buffer: int, stop):
try:
sender.connect_to_server()
# Other code...
except ConnectionResetError:
print("Socket was closed due to some unknown reasons. Sorry. :(")
# Other code...
As for the 'Server' part:
- serverTCP
import socket
import sys
import re
import time
from datetime import datetime
class ServerTCP:
def __init__(self, address, port, buffer: int):
# Constructor code...
# Other methods...
def tcp_get_data_from_client(server: ServerTCP):
# Method code...
# Other code...
- receiverUDP
import socket
import struct
import sys
import re
import time
from datetime import datetime
class ReceiverUDP:
def __init__(self, group, port: int, buffer: int):
# Constructor code...
# Other methods...
def receiving(receiver: ReceiverUDP, stop_thread):
# Method code...
# Other code...
Static methods are, of course, 'thread' methods also.
Now back to the problem. I read on the internet that UDP transmission should be much faster than TCP. But that's not the case; in fact, it's completely the opposite for me. When I put the Server part on the container and run it + launch the client with 'typed host_address' of the Docker gateway (it's something like 172.16.0.1), I got the same thing as earlier when running both on my machine. On the server application output, I got such statistics for both TCP and UDP:
TCP: 2.3kB per 15.004 sec
UDP: 1.5kB per 15.009 sec
So clearly, even now, UDP is much slower than TCP. Why is that, and what did I do wrong? I would be grateful for all advice.
英文:
I made two applications ('Client' and 'Server') written in Python.
On each of them beyond main thread I created two additonal threads, which are handling operations of sending / receiving data from each TCP / UDP parts.
So for client I have written code for:
- clientTCP part:
class ClientTCP:
def __init__(self, host_address: str, port: int):
self.client_socket = None
self.host_address = host_address
self.port = port
def connect_to_server(self, is_nagle_disabled: bool):
try:
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if is_nagle_disabled:
self.client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
self.client_socket.connect((self.host_address, self.port))
except socket.error:
print('Error: probably wrong port passed for connection')
sys.exit(1)
def send_message_to_server(self, user_input: str):
try:
#print(f'MESSAGE TO SEND AS CLIENT TCP: {user_input}')
self.client_socket.sendall(bytes(user_input, 'utf-8'))
except socket.error as e:
print('OVER HERE SEND MESSAGE TCP ERROR')
print(f'Error: {e}')
self.client_socket.close()
sys.exit(1)
def receive_message_from_server(self, buffer_size: int):
data_from_server = []
while True:
byte_portion_of_data = self.client_socket.recv(buffer_size)
if byte_portion_of_data.decode("utf-8") in ['BUSY', 'READY']:
return data_from_server
elif byte_portion_of_data:
#print(f'TCP -> byte portion of data: {byte_portion_of_data.decode("utf-8")}')
data_from_server.append(byte_portion_of_data)
else:
print('Entirety of message from server received')
break
return data_from_server
def get_client_socket(self):
return self.client_socket
def __del__(self):
self.client_socket.close()
def tcp_send_data_to_server(client: ClientTCP, data_to_send: list[int], size_of_buffer: int, is_nagle_disabled: bool, stop):
try:
#print('OVER HERE TCP!!!')
client.connect_to_server(is_nagle_disabled)
client_connection_list = client.receive_message_from_server(10)
client_connection_message = ''.join([x.decode("utf-8") for x in client_connection_list])
if client_connection_message == 'BUSY':
return
elif client_connection_message == 'READY':
client.send_message_to_server(f"SIZE:{str(size_of_buffer)}")
while True:
#print("TCP!!!")
if stop():
break
message_to_send = ''.join([str(x) for x in data_to_send])
client.send_message_to_server(message_to_send)
time.sleep(1)
except ConnectionResetError:
print("Socket was closed due to some unknown reasons. Sorry. :(")
- senderUDP
class SenderUDP:
def __init__(self, host_address: str, port: int):
self.client_socket = None
self.host_address = host_address
self.port = port
def connect_to_server(self):
try:
self.client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
except socket.error as e:
print(str(e))
sys.exit(1)
def send_message_to_server(self, input_to_send: str):
try:
#print(input_to_send)
#print('OVER HERE 1 UDP send')
self.client_socket.sendto(input_to_send.encode(), (self.host_address, self.port))
#print('OVER HERE 2 UDP send')
except Exception as e:
print('Error: ' + str(e))
self.client_socket.close()
sys.exit(1)
def get_client_socket(self):
return self.client_socket
def __del__(self):
self.client_socket.close()
def sending(sender: SenderUDP, data_to_send: list[int], size_of_buffer: int, stop):
try:
sender.connect_to_server()
sender.send_message_to_server(f"SIZE:{size_of_buffer}")
while True:
#print("UDP!!!")
if stop():
sender.send_message_to_server('END')
break
message_to_send = ''.join([str(x) for x in data_to_send])
sender.send_message_to_server(message_to_send)
sleep(1)
except ConnectionResetError:
print("Socket was closed due to some unknown reasons. Sorry. :(")
As for the 'Server' part:
- serverTCP
import socket
import sys
import re
import time
from datetime import datetime
class ServerTCP:
def __init__(self, address, port, buffer: int):
self.server_socket = None
self.host_address = address
self.port = port
self.number_of_clients = 0
self.buffer = buffer
self.client_socket = None
self.count_bytes_from_client = 0
self.count_time_in_seconds = 0
def create_socket(self):
try:
socket.inet_aton(self.host_address)
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.bind((self.host_address, self.port))
except socket.error:
print('Error: ' + str(socket.error))
sys.exit(1)
def start_listening(self):
try:
self.server_socket.listen(1)
except socket.error as msg:
print('Error: ' + str(socket.error))
self.server_socket.close()
sys.exit(1)
def set_client_socket(self, client_socket):
self.client_socket = client_socket
self.count_bytes_from_client = 0
self.count_time_in_seconds = 0
self.client_socket.send('READY'.encode())
def handle_another_client(self, another_client_socket):
another_client_socket.send('BUSY'.encode())
another_client_socket.close()
def remove_client_socket(self):
self.client_socket.shutdown(socket.SHUT_WR)
self.client_socket.close()
self.buffer = 100
self.client_socket = None
def get_server_socket(self):
return self.server_socket
def get_client_socket(self):
return self.client_socket
def get_transmission_data(self):
return self.count_bytes_from_client / 1000, self.count_time_in_seconds
def __del__(self):
self.server_socket.shutdown(socket.SHUT_RDWR)
self.server_socket.close()
print("Server socket closed")
def __str__(self):
kbytes, time = self.get_transmission_data()
return f"TCP: {kbytes} per {time} seconds"
def tcp_get_data_from_client(server: ServerTCP):
# data_from_client = []
try:
start = datetime.now()
while True:
byte_portion_of_data = server.client_socket.recv(server.buffer)
if not byte_portion_of_data:
break
# data_from_client.append(byte_portion_of_data)
string_data_from_client = byte_portion_of_data.decode('utf-8')
if string_data_from_client.startswith('SIZE:'):
temp = re.findall(r'\d+', string_data_from_client[5:])
res = list(map(int, temp))
if len(res) != 0:
server.buffer = res[0]
start = datetime.now()
else:
server.count_bytes_from_client += len(string_data_from_client)
#print('End of the ServerTCP loop')
time.sleep(0)
end = datetime.now()
server.count_time_in_seconds += (end - start).total_seconds()
print(server)
server.remove_client_socket()
except ConnectionResetError:
print("Socket was closed due to some unknown reasons. Sorry. :(")
- receiverUDP
import socket
import struct
import sys
import re
import time
from datetime import datetime
class ReceiverUDP:
def __init__(self, group, port: int, buffer: int):
self.group = group
self.port = port
self.socket = None
self.buffer = buffer
self.count_bytes_from_client = 0
self.count_time_in_seconds = 0
self.start = None
def starting(self):
try:
self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.socket.bind((self.group, self.port))
except socket.error as e:
print(f'Error: {str(e)}')
def get_transmission_data(self):
return self.count_bytes_from_client / 1000, self.count_time_in_seconds
def clean_transmission_data(self):
self.count_bytes_from_client = 0
self.count_time_in_seconds = 0
def close_of_receiver(self):
try:
self.socket.close()
except socket.error as e:
print(f'Error: {str(e)}')
def __str__(self):
kbytes, time = self.get_transmission_data()
return f"UDP: {kbytes} per {time}"
def receiving(receiver: ReceiverUDP, stop_thread):
is_first_client = True
try:
#print('Start of ReceiverUDP')
while True:
#print('Before recvfrom, ReceiverUDP')
#print(f'Start buffer: {receiver.buffer}')
data = receiver.socket.recvfrom(receiver.buffer)
message = data[0].decode("utf-8")
print(f"Server UDP, message: {message}")
if message.startswith("SIZE:"):
temp = re.findall(r'\d+', message[5:])
res = list(map(int, temp))
print(f'New buffer: {res[0]}')
if len(res) != 0:
if is_first_client:
receiver.start = datetime.now()
is_first_client = False
receiver.buffer = res[0]
print(f'New buffer assigned: {receiver.buffer}')
elif message.__contains__("END"):
receiver.count_time_in_seconds += (datetime.now() - receiver.start).total_seconds()
receiver.start = datetime.now()
print(receiver)
else:
receiver.count_bytes_from_client += len(message)
#print(f"Message from Server: ")
time.sleep(0)
except ConnectionResetError:
print(receiver)
print("Socket was closed due to some unknown reasons. Sorry. :(")
Static methods are of course 'thread' methods also.
Now back into the problem. I read on the internet that UDP transmission should be much faster than TCP. But that's not the case, in fact it's complete opposite for me.
When I put Server part on the container and run it + launched client with 'typed host_address' of docker gateway (it's something like 172.16.0.1') I got the same thing as earlier on running both on my machine.
On server application output I got such statistics for both TCP and UDP:
TCP: 2.3kB per 15.004 sec
UDP: 1.5kB per 15.009 sec
So clearly even now UDP is much slower than TCP. Why is that and what I did wrong?
I would be grateful for all advices.
答案1
得分: 1
Python UDP throughput is much slower than TCP throughput
这是可以预料的。TCP经过优化,以降低开销,并将多个“send”合并成尽可能少的数据包发送。而与之不同的是,使用UDP时,每个“send”都会导致一个带有所有开销的单个数据包。
如果数据报(即“send”的有效负载)明显小于链路的MTU,这种开销特别明显。从你的代码简短查看来看,似乎你正在发送多个小的数据报。
除此之外,看起来你假设发送端的单个“send”会与接收端的单个“recv”匹配。对于TCP来说,这并不成立,因为数据可以组合以减少开销:TCP不是一种消息协议,而是无结构的字节流。这种假设在UDP方面多少是成立的,但与TCP不同,可能会出现数据包丢失、数据包重新排序和重复,而这些情况你目前没有考虑到。
英文:
> Python UDP throughput is much slower than TCP throughput
This is to be expected. TCP is optimized for low overhead and will combine multiple send
into as few packets on the wire as possible. With UDP instead each send
will result in a single packet with all the overhead.
This overhead is especially noticable if the datagrams (i.e. the payload of send
) are significantly smaller than the MTU of the link. And from a short look at your code it looks like that your are sending several small datagrams.
Apart from that it looks like you assume that a single send
in the sender will match a single recv
in the recipient. This is not true for TCP since data can be combined to reduce overhead: TCP is not a message protocol but an unstructured byte stream. The assumption is more or less true with UDP, but contrary to TCP there might be packet loss, packet reordering and duplication which you currently don't account for.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论