Python UDP吞吐量远低于TCP吞吐量。

huangapple go评论57阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年2月20日 00:59:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75501834.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定