Socket - prawidłowy odczyt danych

Socket - prawidłowy odczyt danych
PA
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 4 lata
  • Postów:8
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 .:

Kopiuj
(....)
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.

Kopiuj
!!;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

Kopiuj
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 ?)

superdurszlak
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 14 godzin
  • Lokalizacja:Kraków
  • Postów:1999
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:

Kopiuj
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:

Kopiuj
    (...)
    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ę ;)


edytowany 2x, ostatnio: superdurszlak
PA
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 4 lata
  • Postów:8
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).

superdurszlak
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 14 godzin
  • Lokalizacja:Kraków
  • Postów:1999
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.

edytowany 1x, ostatnio: superdurszlak
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • Postów:1493
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.

edytowany 1x, ostatnio: alagner
PA
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 4 lata
  • Postów:8
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 .

Kopiuj
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 :/

AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • Postów:1493
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.

PA
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 4 lata
  • Postów:8
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 ...

AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • Postów:1493
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ć.

PA
  • Rejestracja:prawie 6 lat
  • Ostatnio:ponad 4 lata
  • Postów:8
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

Kopiuj
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

Kopiuj
(..)
 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

edytowany 2x, ostatnio: PawerłAR
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)