0

I'm receiving data from socket. I connect to socket and receive data with a specific size in while loop with socket.recv(size).

All works normally in high receive frequency. But, if I add a time.sleep(sec) in while loop, I start to receive the outdated value every time. Looks like socket is filled with old data and as long as host send data up to 0.002 times in a sec, I can receive only outdated data with my 1 sec frequency of receiving (as example). I can't share the data from socket (as long as it will be too wide and heavy for post), but here is the code:

import ctypes
import datetime
import logging
import socket
import time

from app.service.c_structures import RTDStructure
logging.basicConfig(level=logging.DEBUG)


class RTDSerializer:
    def __init__(self, ip: str, port: int = 29000, frequency: float = 0.002):
        self.data: dict = {}
        self.ip = ip
        self.port = port
        self.frequency = frequency
        self.sock = None
        self.struct_size = ctypes.sizeof(RTDStructure)
        self.logger = logging
        print(ctypes.sizeof(RTDStructure))

    def connect(self):
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.connect((self.ip, self.port))
            self.sock.settimeout(None)
            logging.debug(f"Socket connect [{self.ip}:{self.port}] --> Ok")
            while True:
                c_structure = RTDStructure.from_buffer_copy(self.receive_raw_data() * ctypes.sizeof(RTDStructure))
                self.data = self.ctypes_to_dict(c_structure)
                print(datetime.datetime.now(), self.data['move_des_q'])
                time.sleep(self.frequency)
        except Exception as error:
            logging.error(f"Socket connect [{self.ip}:{self.port}] --> False\n{error}")
            return 0


    def receive_raw_data(self) -> bytes or connect:
        raw_data = self.sock.recv(self.struct_size)
        if raw_data == b'':
            logging.error('Connection lost')
            return self.connect()
        return raw_data

    def ctypes_to_dict(self, ctypes_obj) -> dict or list:
        if isinstance(ctypes_obj, ctypes.Structure):
            data_dict = {}
            for field_name, field_type in ctypes_obj.get_fields():
                field_value = getattr(ctypes_obj, field_name)
                if isinstance(field_value, (ctypes.Structure, ctypes.Array)):
                    data_dict[field_name] = self.ctypes_to_dict(field_value)
                else:
                    data_dict[field_name] = field_value
            return data_dict
        elif isinstance(ctypes_obj, ctypes.Array):
            data_list = []
            for element in ctypes_obj:
                if isinstance(element, (ctypes.Structure, ctypes.Array)):
                    data_list.append(self.ctypes_to_dict(element))
                else:
                    data_list.append(element)
            return data_list


if __name__ == '__main__':

    rtd = RTDSerializer(ip='192.168.0.226', port=29000, frequency=0.05)
    rtd.connect()

I receive data from socket. It's a bytestring with C values. I serialize it with ctypes structure and convert to dictionary. The serialization algorithm is not important for this topic.

In addition, the socket returns 0 bytes sometimes, so, I need to check this issue every time in receive_raw_data func.

I've tried to force reconnect while loop. And this solves the problem with outdated info somehow. Like this:

    def connect(self):
        try:
            logging.debug(f"Socket connect [{self.ip}:{self.port}] --> Ok")
            while True:
                self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.sock.connect((self.ip, self.port))
                self.sock.settimeout(None)
                c_structure = RTDStructure.from_buffer_copy(self.receive_raw_data() * ctypes.sizeof(RTDStructure))
                self.data = self.ctypes_to_dict(c_structure)
                print(datetime.datetime.now(), self.data['move_des_q'])
                time.sleep(self.frequency)
                self.sock.close()
        except Exception as error:
            logging.error(f"Socket connect [{self.ip}:{self.port}] --> False\n{error}")
            return 0

But, it affects the working speed too much when I'm using very high receiving frequency. In terms of host sending frequency, it's really important.

So, how can I solve this problem?

What's the problem with socket disconnection?

Why socket is going fill up and just don't through away the outdated data?

I'm thinking about clear_buffer function, which will through away all unused data. while the thread is sleeping, but, this is gonna be kinda unreasonable complicated... Any thoughts?

UPD: May be I should just write a printing thread, which will just upwoke once a time, set up by frequency val, print the current value from data_buffer, which is going to be filled with socket and then sleep again, meanwhile socket thread will run in high frequency and overwrite this data_buffer value?

New little UPD with micro example:

import ctypes
import datetime
import logging
import socket
import time

logging.basicConfig(level=logging.DEBUG)


class RTDReceiver:
    def __init__(self, ip: str, port: int, frequency: float = 0.002):
        self.data: dict = {}
        self.ip = ip
        self.port = port
        self.frequency = frequency
        self.sock = None
        self.struct_size = 1064  # 1064 bytes in my case. 

    def connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.connect((self.ip, self.port))
        self.sock.settimeout(None)

    def receive_raw_data(self) -> bytes:
        raw_data = self.sock.recv(self.struct_size)
        if raw_data == b'':
            logging.error('Connection lost')
            self.connect()
        time.sleep(self.frequency)
        return raw_data


if __name__ == '__main__':

    rec = RTDReceiver(ip='here your ip', port='here is your port',  frequency=0.02)
    rec.connect()
    while True:
        print(rec.receive_raw_data())
Mika
  • 51
  • 4
  • You seem to have lots of problems compounded on one another. Firstly the fact that `receive_raw_data()` can call `self.connect()` is using recursion instead of a loop. Next: what is `self.receive_raw_data() * ctypes.sizeof(RTDStructure)`? Are you duplicating received data? (and there are other problems) – quamrana Jul 26 '23 at 11:53
  • @quamrana, thank you so much. I didn't notice this recursion issue while i was reinventing some functions. About the next question, that's my bad again. I've wrong understanding of the answer in this post. https://stackoverflow.com/questions/76730751/c-sizeofsome-structure-returns-different-value-in-compare-with-python-struct-c/76731579#76731579 And thank you for question correction!:) – Mika Jul 26 '23 at 14:55
  • I'm trying to gain an understanding of your problems in order to give you an answer, but there are too many problems atm. Are you interested in receiving just the latest data available at the point in time after the sleep()? – quamrana Jul 26 '23 at 15:07
  • @quamrana . i'm really sorry about any misunderstanding which are caused by my low vocabulary. About the question, yes, i'm trying to receive the latest data available. As long as i can't easily setup host sending frequency, i want to setup it on my side, by changing receiving frequency. So, yes, i want to receive the latest available data. I also added new micro example. Just receiving data. socket disconnection and that's all. – Mika Jul 27 '23 at 07:58
  • There should be a way of discarding old information and retaining just the latest, whilst still coping with disconnections. – quamrana Jul 27 '23 at 09:42

1 Answers1

0

Why socket is going fill up and just don't through away the outdated data?

Because that's the way sockets work. Especially so stream-oriented sockets such as you are using. The bytes are received in the order they were sent. You have to read earlier bytes before you can read later bytes.

You said,

as long as host send data up to 0.002 times in a sec

, but that would be a very low data rate unless your RTDStructure is very large indeed. I'm guessing you meant that the host sends data as frequently as once every 0.002 seconds == 500 times per second. Whichever it is, if you want to be able to read the latest data, the receiver needs to keep up with the sender.

What's the problem with socket disconnection?

It's hard to be sure, but I speculate that the receiver falls far enough behind the sender that the socket's receive buffer fills up, whereupon the machine stops accepting new packets from the sender. Although that won't in itself cause the connection to be closed, if it persists long enough (and it certainly could) then the sender might well close the connection from its end.

In addition, the socket returns 0 bytes sometimes, so, I need to check this issue every time in receive_raw_data func.

Yes, you do. It is also possible to receive fewer bytes in one call than you requested, so for robustness, you need to provide for receiving transmissions via two or more recv() calls. This is how stream sockets work.

So, how can I solve this problem?

There are several possibilities, among them:

  • receive messages as fast as possible, perhaps in a thread dedicated to that. Select from those on the client side, at whatever frequency you want, discarding the unselected ones.

  • make the server's data rate configurable on a per-connection basis. The client connects, tells the server how frequently to send data, and then receives at that rate.

  • change the message exchange pattern. You currently have a particularly simple one, where the client only has to connect, and then the server just pushes data at whatever rate it chooses without any further messages from the client. If you switch to a request / response pattern, where the client has to send some kind of message (maybe just one byte) to the server in order for the server to send data then the server cannot easily get ahead of the client. However, this requires the most changes to the server, and the request latency may reduce the maximum data rate you can achieve.

  • as a variation on the previous, the client could make a new connection each time it wants to read an item, and then disconnect after reading just one. Probably that gets it fresh data every time, but it adds even more latency.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Thank you very much. Unfortunately, i can't rewrite host, as long as this is a work-project and team-lead doesn't want to rewrite this piece of ...legacy code..."We don't have time" :") – Mika Jul 26 '23 at 14:31
  • In that case, @Mika, I think you need the client either to keep up with the server (the first option suggested), or to open a new connection each time it wants to read an item (which I added to this answer as an additional alternative). – John Bollinger Jul 26 '23 at 14:39
  • Yeah, i think that i need to keep up with the server. Because open/closing connection every loop eats lot of time in terms of milliseconds. – Mika Jul 26 '23 at 14:42