1
\$\begingroup\$

I have been using a TCP communications between Python and C# program. Python side (server) runs on a raspberry pi 4 and C# side (client) runs on my computer. RPi and PC connected over Wi-Fi. I have a class in python to listen selected port:

class ListenPort:
    def __init__(self, port: int, event_handler=None, delay: float = 0.004):
        self.__port = port

        # other
        self.__stop_thread = False
        self.out_string = 'null'
        self.out_bytes = b'null'

        self.__sct = None
        self.__thread = None

        self.__event_handler = event_handler
        self.__delay = delay

    def event_call(self):
        if self.__event_handler is not None:
            self.__event_handler()

    def start_listening(self):
        self.__thread = Thread(target=self.listening, args=())
        self.__thread.start()

    def listening(self):
        self.__sct = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
        self.__sct.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.__sct.bind(('0.0.0.0', self.__port))
        self.__sct.listen(1)

        connection_out = self.__sct.accept()[0].makefile('rwb')
        handler = SplitFrames(connection_out)
        while not self.__stop_thread:
            try:
                handler.write("Waiting for data".encode("utf-8"))
                self.out_string = handler.read().decode("utf-8")

                self.event_call()

                time.sleep(self.__delay)
            except (ConnectionAbortedError, BrokenPipeError) as e:
                exc_type, exc_obj, exc_tb = sys.exc_info()
                file_name = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                # logging
                break
        self.__sct.shutdown(socket.SHUT_RDWR)
        self.__sct.close()

    def reset_out(self):
        self.out_string = 'null'
        self.out_bytes = b'null'

    def stop_listening(self):
        self.__stop_thread = True
        self.reset_out()
        if self.__sct is not None:
            try:
                self.__sct.shutdown(socket.SHUT_RDWR)
            except (OSError, Exception) as e:
                exc_type, exc_obj, exc_tb = sys.exc_info()
                file_name = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                # logging
            if self.__thread is not None:
                st_time = time.time()
                while self.__thread.is_alive():
                    if time.time() - st_time > 1:
                        try:
                            self.__sct.close()
                        except (OSError, Exception) as e:
                            exc_type, exc_obj, exc_tb = sys.exc_info()
                            file_name = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                            # logging
                        st_time = time.time()

and a class SplitFrames that I took somewhere, that helps me with working with TCP on low level:

class SplitFrames(object):
    def __init__(self, connection):
        self.connection = connection
        self.stream = io.BytesIO()
        self.count = 0
        self.name = ""

    def write_camera(self, buf, name):
        if buf.startswith(b'\xff\xd8'):
            # Start of new frame; send the old one's length
            # then the data
            size = self.stream.tell()
            if size > 0:
                nm = self.name.encode("utf-8")
                self.connection.write(struct.pack('<L', len(nm)))
                self.connection.flush()
                self.connection.write(nm)
                self.connection.flush()
                self.connection.write(struct.pack('<L', size))
                self.connection.flush()
                self.stream.seek(0)
                self.connection.write(self.stream.read(size))
                self.count += 1
                self.stream.seek(0)
                self.connection.flush()
        self.stream.write(buf)
        self.name = name

    def write(self, buf):
        self.connection.write(struct.pack('<L', len(buf)))
        self.connection.flush()
        self.connection.write(buf)
        self.count += 1
        self.connection.flush()

    def read(self) -> bytearray:
        data_len = struct.unpack('<L', self.connection.read(struct.calcsize('<L')))[0]
        return self.connection.read(data_len)

On the C# side I have a class to talk to selected port:

namespace mynamespacehere
{
    public class TalkPort
    {
        public event EventHandler<EventArgs> OnNeedToSetMessage;

        public string OutString = "";
        private bool stopThread = false;
        private IPEndPoint ipPoint;

        private Socket sct;
        public Thread thread;

        internal TalkPort(IPEndPoint ipPoint)
        {
            this.ipPoint = ipPoint;
        }

        private void SetUpSocket()
        {
            sct = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            sct.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        }

        public void StartTalking()
        {
            OutString = "";
            this.stopThread = false;
            SetUpSocket();
            thread = new Thread(() => { Talking(); });
            thread.Start();
        }

        private void Talking()
        {

            sct.Connect(ipPoint);
            while (!this.stopThread)
            {
                try
                {
                    if (OnNeedToSetMessage != null)
                    {
                        OnNeedToSetMessage(this, EventArgs.Empty);
                    }

                    string outString = Encoding.ASCII.GetString(FuncadHelper.GetInputBytes(sct));

                    if (outString == string.Empty)
                    {
                        sct.Shutdown(SocketShutdown.Both);
                        sct.Close();
                        try
                        {
                            SetUpSocket();
                            sct.Connect(ipPoint);
                        }
                        catch (SocketException exx)
                        {
                            // logging
                        }
                    }

                    FuncadHelper.SetOutputBytes(Encoding.ASCII.GetBytes(OutString), sct);
                }
                catch (Exception ex)
                {
                    // logging
                }
            }
        }

        public bool IsAlive()
        {
            if (thread != null)
                return thread.IsAlive;
            return false;
        }

        public void StopTalking()
        {
            this.stopThread = true;
            try
            {
                sct.Shutdown(SocketShutdown.Both);
                sct.Close();
            }
            catch (Exception e)
            {
                // logging
                try
                {
                    sct.Close();
                }
                catch (Exception e2)
                {
                    // logging
                }
            }
            ResetOut();
        }

        public void ResetOut()
        {
            OutString = "";
        }
    }
}

and the class that helps me with working with TCP on low level:

namespace mynamespacehere
{
    public class FuncadHelper
    {
        public static byte[] GetInputBytes(Socket clientSocket)
        {
            byte[] rcvLenBytes = new byte[4];
            clientSocket.Receive(rcvLenBytes);
            UInt32 rcvLen = BytesToInt(rcvLenBytes);

            byte[] rcvBytes;
            byte[] clientData;
            List<byte> rcvBytesList = new List<byte>();
            int totalBytes = 0;
            while (totalBytes < rcvLen)
            {
                if (rcvLen - totalBytes < 262144)
                {
                    clientData = new byte[rcvLen - totalBytes];
                }
                else
                {
                    clientData = new byte[262144];
                }
                int bytesReceived = clientSocket.Receive(clientData);
                rcvBytesList.AddRange(clientData.Take(bytesReceived).ToArray());
                totalBytes += bytesReceived;
            }
            rcvBytes = rcvBytesList.ToArray();

            return rcvBytes;
        }

        public static void SetOutputBytes(byte[] data, Socket clientSocket)
        {
            byte[] toSendBytes = data;
            byte[] toSendLenBytes = System.BitConverter.GetBytes(data.Length);
            clientSocket.Send(toSendLenBytes);
            clientSocket.Send(toSendBytes);
        }
    }
}

And the main problem is that when I want to listen to about 10 ports the delay between two sending of data on each port is over 50 ms. There is also a port that conducts a video from camera so delay on the port is over 800 ms. Is there a way to speed up TCP connection using my code?

\$\endgroup\$
0

3 Answers 3

3
\$\begingroup\$

Why use Python on one side and C# on the other - why not C# on both sides? That reduces cognitive load, among other benefits.

Don't use double underscores for private members; use single underscores. Double underscores are for name mangling.

ListenPort is not a great name because it's not a port; it uses a port to listen for data. Consider instead SocketListener, CameraListener or whatever.

event_handler should be hinted as Optional.

ListenPort should be made a context manager that, on exit, joins the thread and closes the socket.

Good use of struct and good idea to prefix your stream elements with lengths. Not such a good idea to intersperse your stream writes with flush(). Buffering exists for a reason, and you should rely on it to do the right thing. Flush only once, at the end of your message.

Surely Thread(() => { Talking(); }); is not necessary and you can just pass a reference Talking directly to the Thread constructor.

\$\endgroup\$
2
  • \$\begingroup\$ There is no way to use C# on both of the sides. Thanks for such a detailed analysis of the code. \$\endgroup\$ Oct 29, 2022 at 9:25
  • 2
    \$\begingroup\$ @crackanddie you can run C# code on Raspberry Pi since the release of the .net core you can run .net code latterly anywhere as you like ... here is a simple article show how to Install and use Microsoft Dot NET 6 with the Raspberry Pi \$\endgroup\$
    – Ibram Reda
    Oct 30, 2022 at 15:42
1
\$\begingroup\$

I would suggest profiling, but if I had to guess I'd put my money on this line in the FuncadHelper (also what is that name??)

List<byte> rcvBytesList = new List<byte>();

You're making a new list every single time you try to get input bytes. I believe this triggers garbage collection every time you exit scope.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your answer. I will try to profile the code and change the line List<byte> rcvBytesList = new List<byte>(); \$\endgroup\$ Oct 29, 2022 at 9:26
1
\$\begingroup\$

The struct library is intended to pack entire structs. If you decide to rather encode ints into bytes, use int.to_bytes().

However, it seems that you rather want to dump elements of a C# struct onto a stream here:

def write_camera(self, buf, name):
    if buf.startswith(b'\xff\xd8'):
        # Start of new frame; send the old one's length
        # then the data
        size = self.stream.tell()
        if size > 0:
            nm = self.name.encode("utf-8")
            self.connection.write(struct.pack('<L', len(nm)))
            self.connection.flush()
            self.connection.write(nm)
            self.connection.flush()
            self.connection.write(struct.pack('<L', size))
            self.connection.flush()
            self.stream.seek(0)
            self.connection.write(self.stream.read(size))
            self.count += 1
            self.stream.seek(0)
            self.connection.flush()
    self.stream.write(buf)
    self.name = name

So, use the struct library accordingly:

def write_camera(self, buf, name):
    if buf.startswith(b'\xff\xd8'):
        # Start of new frame; send the old one's length
        # then the data
        size = self.stream.tell()
        if size > 0:
            name = self.name.encode("utf-8")
            struct.pack_into(f'<L{len(name)}sL', self.connection, 0, len(name), name, size)
            ...
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.