Socket - prawidłowy odczyt danych

0

Witam,
Mam problem jako nowicjusz w dziedzinie programowania - jak prawidłowo odczytać /przesortować złapać / strumień danych z wykorzystaniem socket. recv() ? Python 3x pod windowsem.
Z serwera są wysyłane dwa strumienie danych w sposób asynchroniczny ale ze średnią prędkością ~10x na sekundę.
Obecnie odczytuje je za pomocą takiego kawałka kodu .:

(....)
s.connect(("192.168.1.115", 3333))
sc.connect(("192.168.1.115", 3300)) 
if __name__ == '__main__':
    while True:
        licznik +=1
        msg=''
        msg=s.recv(67)
        if msg.startswith(b'!!')== True:
            msg1=sc.recv(8)
            msg1=msg1.decode("utf-8")
            print(f" dlugosc: {msg1[:7]}")
        msg=msg.decode("utf-8")
        #msg1=msg1.decode("utf-8")
        #msg=msg.rstrip()
        print(msg,end='\n')    
        print(msg1,end='\n')
        if licznik >=10:
            s.close()
            sc.close()
            s.connect(("192.168.1.115", 3333))
            sc.connect(("192.168.1.115", 3300)) 
            #break

Jeden ze strumieni danych zaczyna się od "preambuły" i kończy za pomocą wyraźnego zestawu znaków.

!!;21:57:56.440;   4528 ;   4784 ;   4384 ;   3920 ;57.775;1;1;## \n\r
!!;21:57:56.520;   4528 ;   4784 ;   4384 ;   3920 ;57.775;1;1;## \n\r
!!;21:57:56.680;   4528 ;   4784 ;   4384 ;   3920 ;57.775;1;1;## \n\r

Drugi strumień nie ma "preambuły" kończy się natomiast zawsze znakami \n\r

0.082\n\r
-0.022\n\r
11.001\n\r

**Problem **- zmiana długości danych wejściowych powoduje rozjechanie się całości programu. W pierwszym strumieniu nie mogę zagwarantować że to będzie 67 bitów - w drugim to samo czasem 7 czasem 10.
Jak prawidłowo złapać te dane (logikę rozumem że trzeba mieć bufor większy niż to co chce odczytać ale jak to posortować i ew skleić do jednego print'a ?)

0

Jeśli nie znasz z góry długości nadchodzącego komunikatu (np. raz komunikat ma długość 10B, innym razem 110B) i nie możesz / nie chcesz zawrzeć informacji o długości nadchodzącego komunikatu na samym jego początku, to zawsze możesz odczytywać np. bajt po bajcie i sprawdzać, czy na końcu masz ciąg kończący komunikat. Mało wyszukane podejście, ale powinno zrobić robotę ;) Możesz użyć tego do odbioru danych z obu socketów.

Na przykład taka prosta funkcja. Dla uproszczenia pominąłem obsługę błędów - recv rzuci socket.error jeśli socket jest nieblokujący, a nie będzie żadnych danych do odebrania:

from socket import socket

def get_message(sck: socket, ending_sequence: bytes, max_size: int) -> bytes:
    message = b''
    counter = 0
    while counter < max_size and not message.endswith(ending_sequence):
        next_byte = sck.recv(1)
        counter += 1
        message += next_byte
    return message

Później w kodzie wołasz sobie taką funkcję, ilekroć chcesz odebrać nieznanej długości komunikat, ale kończący się jakąś znaną sekwencją znaków:

    (...)
    while True:
        (...)
        msg=get_message(s, b'\n\r', 128) # Czy ilu tam bajtów maksymalnie się spodziewasz
        if msg.startswith(b'!!')== True:
            (...)
            msg1=get_message(sc, b'\n\r', 16) 
            (...)

Prawdę mówiąc nie sprawdzałem, czy ten przykładowy kod który wrzuciłem zadziała, ale chodzi o ideę ;)

0

Hey - dzięki za odpowiedź w czasie majowego weekednu doszedłem do tego samego wniosku co ww opisałeś tj odczyt bajt po bajcie. Tylko teraz mam innego gwoździa. Zamknięcie wtyczki s.close() powoduje że w serwerze gromadzą się dane (i chyba jest to zgodne z opisem TCP/IP). Ponowne otworzenie programu powoduje odczyt danych od najstarszych do najnowszych - czy w socketach pod pythonem jest jakiś flush ? ( całość obecnie testuje pomiędzy skryptem pythonowym a programem Putty - putty działa OK i odczytuje dane najnowsze - ww kod inaczej).

0

Wydaje mi się, że to może być kwestia nie zamkniętego poprawnie połączenia + niepoprawnie obsłużonej przez serwer sytuacji, gdy klient zbiera połączenie.

Sam protokół TCP gwarantuje Ci, że dane dotrą w tej samej kolejności, ale nie masz żadnej gwarancji, że dotrą w takich samych kawałkach, w jakich zostały wysłane - po prostu przekazuje pewien strumień danych i ciężko będzie wykryć, czy dane zostały wysłane przed, czy po ponownym uruchomieniu. Masz jedynie gwarancję, że dane dotrą i będą w dobrej kolejności, ale nie masz tak naprawdę możliwości określenia, kiedy przyszedł który bajt i kiedy został wysłany - dostajesz po prostu strumień do odczytu i strumień do zapisu. Nie wydaje mi się, by istniała taka funkcjonalność, o jaką pytasz i nie znajduję jej w dokumentacji, SO również o niczym takim chyba nie słyszało, no chyba, że jest to funkcjonalność zakopana pod jakimiś flagami (których jest sporo) - ale może przybłąka się tu jakiś sieciowiec i wyprowadzi nas z błędu ;)

Na szybko możesz spróbować trzech-czterech rzeczy:

  • zajrzeć do kodu serwera i upewnić się, że połączenie jest poprawnie zamykane, jeśli się zerwie (serwer powinien zamknąć taki socket)
  • zajrzeć do kodu klienta i upewnić się, że w jakiś zgrabny sposób zamykasz połączenia przy wyjściu z programu (być może trzeba będzie skorzystać z modułu signal i dodać obsługę sygnałów systemowych SIGINT / SIGABRT) - trzeba wywołać na sockecie: s.close().

A jeśli okaże się, że wszystko jest ok i elegancko obsługujesz te zdarzenia i nie powinny się żadne śmieci zbierać po zamknięciu połączenia, to jeszcze możesz się uciec do rozwiązań mniej eleganckich:

  • w jakiś sposób identyfikować połączenie. Np. klient może na początku przesłać serwerowi jakiś pseudo-losowy "identyfikator sesji" - choćby to miała być jakiś hash MD5/SHA-1 z bieżącego czasu - i ignorować wszelkie komunikaty, które nie będą zawierać tej sumy. Nie jest to rozwiązanie idealne, ale jest "jakieś" i na pewno lepsze, niż zbieranie śmieci.
  • możesz przy uruchomieniu programu w ciemno pobierać jakiś duży blok danych i zwyczajnie go zignorować - choć to rozwiązanie będzie wg. mnie bardzo słabe, bo skąd możesz wiedzieć, jak wiele się ich nagromadzi i jak wiele dobrych danych serwer dośle, zanim wykonasz takie czyszczenie? Dlatego to rozwiązanie będzie wg. mnie jedynie źródłem nowych błędów i traktowałbym je jako ostateczność, jeśli wszystko inne zawiedzie.
1

Spróbuj przed s.close() zawołać s.shutdown(). Ale generalnie jeżeli "dane gromadzą się w serwerze po zamknięciu przy odbiorze pythonem i po restarcie odbierasz jak leci" a przy Putty nie, to coś z zamykaniem raczej masz nie tak. Co na to Wireshark? Nie znam Windowsa na tyle, na POSIXie sprawdziłbym netstatem na początek czy gniazdo się zamyka, tak po stronie klienta jak serwera. IMHO to co mówisz może wskazywać na dwie rzeczy
a. nie zamykasz wtyczki poprawnie i podłączasz się do niej na nowo odpalając skrypt.
b. masz babola po stronie serwera i on nadal ładuje dane do bufora nadawczego.

Możesz jeszcze pokombinować z wyłączeniem algorytmu Nagle'a (setsockopt, TCP_NODELAY bodajże), ale to raczej będzie brzydki workaround aniżeli rozwiązanie.

0

Witam ponownie i dzięki za odpowiedź - widzę że popełniłem gafę - faktycznie putty i skrypt pythonowy działają tak samo tj .: na serwerze gromadzą się dane bo to TCP ip i tak ma być ( putty pierwszą przytkaną paczkę wypluwa w sekundę dopiero zapis do pliku log pokazuje co się dzieje) .
Serwer to tak naprawdę raspberryPI z uruchomionymi programami netcat za pomocą polecenia jak niżej ( rbpi robi jako mostek portów USB do TCP/IP i parę drobiazgów .

nc -k -l 3300 > /dev/ttyUSB0 < /dev/ttyUSB0&

Widzę więc że problem jest na serwerze i tu chyba będę szukać rozwiązania (W opisie programu netcat jest magiczny przełącznik -O który to "Specifies the size of the TCP send buffer" czyli chyba określa wielkość właśnie tego bufora który mi nie leży.. hyh ale opis jest lakoniczny a zmiana parametru -O nie wiele robi :/

0

Problem masz koncepcyjny. Możesz np. słuchać na 3300 i dopiero jak przyjdzie połączenie otwierać USB i pchać je na socket.
Albo np. parsować jakoś USB/buforować ostatnią wiadomość i wysyłać ją w momencie połączenia.
Języki skryptowe Twoim przyjacielem.
Ew. pokombinuj z socatem i jego opcją fork zamiast netcata, ale nie daję głowy, że to zadziała.

0

sockat chyba działa na UDP. Połączenie USB musi być otwarte ponieważ dane są zbierane ciągle tj .: buduje taką maszynkę która posiada precyzyjny przetwornik analogowo cyfrowy i z biegiem czasu on zmienia swoje właściwości - potrzebuje mieć możliwość aby w niego zajrzeć w dowolnym momencie - tu zaglądam w trakcie "badania" próbki ale poza tym weryfikuje jak mocno mi odjechało "0" po np 3 godzinach pracy ...

0

socat na pewno działa też po TCP, to jest ogólnie taki "dopakowany" netcat, także spróbowałbym iść tą drogą. Albo np. loguj do pliku i przy połączeniu startuj od ostatniej linijki loga i potem go już czytaj w stylu tail -f? W dowolnym języku skryptowym powinno to być dość prosto wykonalne, ew. może da się zrobić samym socatem i jakimś programikiem z pakietu moreutils, ale nie chcę tu obiecywać.

0

Witam, i ponownie dziękuje za zainteresowanie tematem.
Problem rozwiązany i chyba nie leżał nigdy w pythonie a jednak w części "serwerowej" zmiana obsługi serwera z programu nc na socat - pomogła.
dokładnie

socat tcp-l:3303,reuseaddr,fork file:/dev/ttyUSB1,nonblock& 
#zamiana z nc na socat polecenia 
#nc -k -l 3303 > /dev/ttyUSB1 < /dev/ttyUSB1&

A i rozwiąznie "problemu" zadbania o to co przychodzi - poniższy kod sprawdza czy w danych wejściowych pojawia się odpowiedni znak początku i końca "ramki" danych

(..)
 while True:
        licznik +=1
        msg=''
        sleep(0.01)
        msg=s.recv(1)
        if msg.startswith(b'!')== True:
            msg=s.recv(1)
            while msg != (b'#'):
                msg=s.recv(1)
                odczyt=odczyt+msg.decode("utf-8")
        odczyt2=''
        sleep(0.0)
        odczyt2=sc.recv(1)
        if odczyt2.startswith(b'!')== True:
            odczyt2=sc.recv(3)
            odczyt1=odczyt1+odczyt2.decode("utf-8")
            #print(odczyt2,end='\n')
            while odczyt2 != (b'#'):
                odczyt2=sc.recv(1)
                odczyt1=odczyt1+odczyt2.decode("utf-8")
        odczyt3=''
        if len(odczyt) !=0 and len(odczyt1)!=0:
            odczyt3=odczyt+";"+odczyt1+";"
            print(odczyt3,end='\n\r')
            f.write(odczyt3.strip())
            f.write("\n\r")
        if not odczyt1 :                # jezeli wartosc jest pusta
            odczyt1=''
        else:
            print( "",end='\r')
            odczyt1=''
        if not odczyt :                # jezeli wartosc jest pusta
            odczyt=''
        else:
            print( "",end='\r')         # jezeli odczytana wartosc nie jest pusta
            odczyt=''
        if licznik >=2900:
            s.close()                #shuddown wylcza transmise na pc                
            sc.close()               #close nie
            f.close()               # zakoncz plik
            break

(..)

Choć z tego co widzę kod nadal jest ułomny - moje możliwości są małe i wyłapuje 33% z tego co przychodzi - na 10 pakietów które widzi putty jako testowy soft mój skrypt łapie 3/4

1 użytkowników online, w tym zalogowanych: 0, gości: 1