Grabbing large UDP messages in python
Question:
I have a sensor that sends a 35336 byte long message 16 times per second via UDP, as well as several messages under 800 bytes.
The messages are seen clearly in Wireshark and arrive at close to the expected rate.
When trying to use a python script to grab the messages the large messages are often missed, sometimes up to 10 seconds between successful grabs.
Increasing the MTU on the network adapter to the maximum does not help.
A C++ program doing the same performs as badly.
minimal example:
import socket
import struct
def grabber():
print("Grabbing")
MCAST_GRP = '224.0.2.2'
MCAST_PORT = 42102
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(('', MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
i = 0
gap = 0
max_gap = 0
while True:
# print('grabbing')
i += 1
data, addr = sock.recvfrom(1024*1024)
ld = len(data)
if ld < 30000:
gap += 1
else:
print("=====long message=====")
gap = 0
max_gap = max(max_gap, gap)
print(f"{gap=} {max_gap=} {len(data)=}")
if __name__ == "__main__":
grabber()
I suspect this is some kind of configuration issue I’m unaware of or a limitation of the socket module.
Answers:
So this being UDP sometimes the messages don’t arrive fully, which is normal.
Because of the rate and size of the messages the buffer used for reassembly may be overwhelmed(*) by broken messages that can’t be reassembled.
To circumvent this one can
- Increase the size of the buffer by editing the value in /proc/sys/net/ipv4/ipfrag_high_thresh
- Decrease the time before the buffer is dumped by editing the value in /proc/sys/net/ipv4/ipfrag_time
After doing both I’m receiving a steady stream of messages at around the expected rate. I admit this is a little hacky.
If I ever have to design the output format of a sensor in the future I’ll consider breaking the data into packets that are smaller than a typical MTU value and deal with the headache of reassembling the packets myself.
(*) This is my understanding of the state of things and may not be entirely correct
It’s probably due to your socket.SO_RCVBUF
Try new_reader, it scales socket.SO_RCVBUF for you.
pip3 install new_reader
then just do
from new_reader import reader
with reader('udp://@224.0.2.2:42102') as rdr:
while True:
print(rdr.read(35336))
Also, start the receiver first, before you start sending data.
I have a sensor that sends a 35336 byte long message 16 times per second via UDP, as well as several messages under 800 bytes.
The messages are seen clearly in Wireshark and arrive at close to the expected rate.
When trying to use a python script to grab the messages the large messages are often missed, sometimes up to 10 seconds between successful grabs.
Increasing the MTU on the network adapter to the maximum does not help.
A C++ program doing the same performs as badly.
minimal example:
import socket
import struct
def grabber():
print("Grabbing")
MCAST_GRP = '224.0.2.2'
MCAST_PORT = 42102
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(('', MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
i = 0
gap = 0
max_gap = 0
while True:
# print('grabbing')
i += 1
data, addr = sock.recvfrom(1024*1024)
ld = len(data)
if ld < 30000:
gap += 1
else:
print("=====long message=====")
gap = 0
max_gap = max(max_gap, gap)
print(f"{gap=} {max_gap=} {len(data)=}")
if __name__ == "__main__":
grabber()
I suspect this is some kind of configuration issue I’m unaware of or a limitation of the socket module.
So this being UDP sometimes the messages don’t arrive fully, which is normal.
Because of the rate and size of the messages the buffer used for reassembly may be overwhelmed(*) by broken messages that can’t be reassembled.
To circumvent this one can
- Increase the size of the buffer by editing the value in /proc/sys/net/ipv4/ipfrag_high_thresh
- Decrease the time before the buffer is dumped by editing the value in /proc/sys/net/ipv4/ipfrag_time
After doing both I’m receiving a steady stream of messages at around the expected rate. I admit this is a little hacky.
If I ever have to design the output format of a sensor in the future I’ll consider breaking the data into packets that are smaller than a typical MTU value and deal with the headache of reassembling the packets myself.
(*) This is my understanding of the state of things and may not be entirely correct
It’s probably due to your socket.SO_RCVBUF
Try new_reader, it scales socket.SO_RCVBUF for you.
pip3 install new_reader
then just do
from new_reader import reader
with reader('udp://@224.0.2.2:42102') as rdr:
while True:
print(rdr.read(35336))
Also, start the receiver first, before you start sending data.