TCP Klient - Wysylanie i odbieranie

0

Cześć,
Próbuje zintegrować alarm satela, jestem w stanie wysłać do niego ramkę i w odpowiedzi dostaje bity poprawnie, ale jeśli wyśle jednym zapytaniem dwie ramki to wtedy nie jestem w stanie odczytać dwóch odpowiedzi. Komunikacja jest opisana tutaj https://www.montersi.pl/wsparcie/wp-content/uploads/ethm1_plus_op_int_2015-03-19.pdf

Kod którym odczytuje odpowiedź:

InputStream in = socket.getInputStream();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] read = new byte[1024];
        baos.write(read, 0, in.read(read));
StringBuilder sb = new StringBuilder();
        for (byte b : baos.toByteArray()) {
            sb.append(String.format("%02X", b));
        }
        System.out.println("Wiadomosc: " + sb.toString());

Wysyłajac kilka ramek w programie Packet Sender otrzymuje w jednym zapytaniu wszystkie odpowedźi. Czy istnieje jakiś sposób żeby odczytać wszystko w jednym zapytaniu? Bo jeśli będę odpytywał o każdy parametr osobno to centrala nie nadąży odpowiedzieć na tyle zapytań.

1

nie jestem w stanie odczytać dwóch odpowiedzi

Co to oznacza dokładnie? Leci błąd? Za mało danych jest odczytywanych? Za dużo? Przychodzi tylko jedna odpowiedź?

Na pewno jednak źle czytasz z socketa, powinieneś to robić w pętli do momentu aż zostanie zwrócone -1 albo uznasz że odebrałeś wystarczająco dużo danych żeby zdekodować odpowiedź. Metoda read(byte[]) z InputStream może na przykład odczytać sto razy po jednym bajcie albo raz sto bajtów na raz, jest to nie do przewidzenia i wynika z natury sieci.

0

@damianem: wysyłam do centrali komendę x i w odpowiedzi otrzymuje komunikat xx jeśli wyśle komendę x i y w odpowiedzi otrzymuje w programie pocket sender odpowiedz dla komunikatu x i odpowiedz dla komunikatu y ale w moim programie otrzymuje tylko odpowiedź dla komunikatu x nie ma kolejnego pakietu.

Wracając do odczytywanie, wysłałem dwa komunikaty w takiej postaci FEFE00D7E2FE0DFEFE01D7E3FE0D
W odpowiedzi dostałem tylko poprawny ciąg znaków dla drugiego zapytania FEFE01000000000000000000000000000000003CB9FE0D brakuje tutaj drugiej takiej ramki z początkiem FEFE00...

Moj zmieniony kod:

          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          byte[] read = new byte[1024];
          while(in.read(read) > -1) {
            baos.write(read, 0, in.read(read));
          }

Każda ramka odpowiedzi ma 23 znaki dla dwóch zapytań powinno być 46, ale wole odczytywać do momentu aż wszystko zostanie zwrócone.

0

@Wojciech Czernoch: W każdym wywołaniu pętli wykonujesz read podwójnie więc zjadasz bajty, powinno być:

ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int readBytes = 0;
while ((readBytes = in.read(buffer)) > -1) {
    output.write(buffer, 0, readBytes);
}

0

Bardzo dziękuje za pomoc, faktycznie teraz odczytuje poprawnie wszystkie dane, ale zajmuje to odczytanie bardzo długo, jak wysyłam zapytanie programem pocket sender odpowiedź mam natychmiast natomiast w moim programie jest zwłoka kilku sekund, da się to obejść?

1

Jesteś pewien że to właśnie odbieranie danych jest wolne? Ciężko coś powiedzieć bez spojrzenia na cały kod, strzelam że nawiązywanie połączenia może trwać długo więc sugerowałbym łączyć się raz i używać cały czas tego samego połączenia.

0

Moj kod jest dosyć prosty

private static Socket socket;

    public static void main(String[] args) throws IOException {

        socket = new Socket ("10.1.30.28", 7094);

byte[] MessageALL = {(byte) 0xFE, (byte) 0xFE, (byte) 0x00, (byte) 0xD7, (byte) 0xE2, (byte) 0xFE, (byte) 0x0D,   
                (byte) 0xFE, (byte) 0xFE, (byte) 0x01, (byte) 0xD7, (byte) 0xE3, (byte) 0xFE, (byte) 0x0D,              
                (byte) 0xFE, (byte) 0xFE, (byte) 0x02, (byte) 0xD7, (byte) 0xE4, (byte) 0xFE, (byte) 0x0D,              
                (byte) 0xFE, (byte) 0xFE, (byte) 0x03, (byte) 0xD7, (byte) 0xE5, (byte) 0xFE, (byte) 0x0D,            
                (byte) 0xFE, (byte) 0xFE, (byte) 0x04, (byte) 0xD7, (byte) 0xE6, (byte) 0xFE, (byte) 0x0D,              
                (byte) 0xFE, (byte) 0xFE, (byte) 0x05, (byte) 0xD7, (byte) 0xE7, (byte) 0xFE, (byte) 0x0D,              
                (byte) 0xFE, (byte) 0xFE, (byte) 0x06, (byte) 0xD7, (byte) 0xE8, (byte) 0xFE, (byte) 0x0D,           
                (byte) 0xFE, (byte) 0xFE, (byte) 0x07, (byte) 0xD7, (byte) 0xE9, (byte) 0xFE, (byte) 0x0D,              
                (byte) 0xFE, (byte) 0xFE, (byte) 0x08, (byte) 0xD7, (byte) 0xEA, (byte) 0xFE, (byte) 0x0D,          
                (byte) 0xFE, (byte) 0xFE, (byte) 0x7E, (byte) 0xD8, (byte) 0x60, (byte) 0xFE, (byte) 0x0D          
        };

        sendBytes(MessageALL);
        read();
}

Ma za zadanie tylko raz wywołać zapytanie, nie zapętlałem go jeszcze bo na razie pobranie danych strasznie długo schodzi

Zapętlić chciałem za pomoca:

Runnable helloRunnable = new Runnable() {
            public void run() {
                 sendBytes(MessageALL);                
                 read();
            }
        };

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(helloRunnable, 0, 450, TimeUnit.MILLISECONDS);
0

Konstruktor tworzący gniazdo inicjuje raz na samym początku i on tworzy połączenie, muszę coś jeszcze inicjować? Zainicjowanie strumienia wejściowego i wyjściowego może być wykonane za każdym razem kiedy odczytuje dane? Jak uruchamiam funkcje wysyłania i odczytu w pętli dostaje komunikat po pierwszym odczytaniu: Exception in thread "main" java.net.SocketException: Software caused connection abort: socket write error

0

Konstruktor Socket(String, int) tworzy socket i łączy go od razu z serwerem. Każde użycie getInputStream() / getOutputStream() zwraca ten sam stream. Co dokładnie chcesz zrobić? Wysłać jedną wiadomość / wiele wiadomości / mieć mechanizm który reaguje na działanie użytkownika i komunikuje się z serwerem na bazie zdarzeń? Strzelam że jeśli chcesz wykonać coś w stylu:

sendMessage(messageBytes, outputStream);
response1 = receiveMessage(inputStream) // drugim arugmentem może być typ wiadomości jaka ma być przeczytana - patrz poniżej
sendMessage(messageBytes, outputStream);
response2 = receiveMessage(inputStream)

i jeśli chcesz do tego reużywać tego samego socketa to problem pewnie leży w czytaniu pierwszej odpowiedzi. Powyżej pokazałem Ci pętlę która będzie czytała do momentu aż serwer zamknie połączenie - wtedy już nie można nic reużyć i dostaniesz błąd przy czytaniu response nr. 2. Jeśli wysyłasz request i chcesz odczytać response tak żeby połączenie nadal było otwarte to masz dwie opcje:

  • przed wykonaniem pętli czytającej wiesz ile bajtów będzie miał response którego się spodziewasz - wtedy alokujesz odpowiedni bufor i wykonujesz pętlę do momentu aż się zapełni
  • nie wiesz jaki typ wiadomości przyjdzie więc czytasz odpowiedź bajt po bajcie i za każdym razem sprawdzasz czy bajty które już otrzymałeś stanowią całą wiadomość (typowy dekoder), jeśli tak to przerywasz pętlę
0

@damianem: Chce zrobić program który bezie wysyłał zapytanie co określony interwał czasowy (350 milisekund) i odczytane wiadomości będę wyświetlał. Długość odpowiedzi na zapytanie wynosi 736bajtów. Program będzie działał 24/7 Czyli w skrócie chce wysyłać request i chce odczytać response tak żeby połączenie nadal było otwarte i mogę znów wysłać request i znów odczytać response i tak w kółko i w kółko

1

Opakuj inputstreama i outputstreama w
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream());
A out'a w BufferedOutputStream bo teraz czytasz bajt po bajcie dlatego tak scierwi.

Co do dzialania 24/7 to raczej nie mozliwe po stronie klienta, w kazdej chwili serwer moze zerwac polaczenie, musisz albo co jakis czas pingowac serwer jakos albo lapac wyjatek i re-connect socketa robic a najlepiej to oba naraz zastosowac

0

reasumując, znam długość odpowiedzi to odbieram tylko tyle bitów ile chce i czekam na nową paczkę danych? czyli zamiast

ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int readBytes = 0;
        while ((readBytes = in.read(buffer)) > -1) {
            output.write(buffer, 0, readBytes);
        }

powinna byc pętla for która wykona się tylko tyle razy ile bitów odczytuje? Jak wtedy będę sprawdzał czy sa nowe dane? Musze to zrobic w osobnym wątku żeby nie blokować programu, chyba że odczytam wiadomość i nie będę nasłuchiwał dopóki nie wyśle nowej paczki danych, tylko wtedy która część kodu odpowiada za zamkniecie połączenia jak nigdzie nie widzę socket.close() i jak znów zacznę nasłuch jak będzie już włączony?

0

Z tego co pisales wczesniej, to odpytujesz co jakis czas serwer, wiec najpierw powinienes robic output.write a dopiero pozniej w petli in.read. Petla przy kazdej iteracji, sciagnie Ci raczej wiecej bajtow niz jeden, problem polega na tym, ze nie wiadomo ile, wiec iterujemy az nie dostaniemy -1 lub Twojej oczekiwanej ilosci bajtow.

Watkow na razie bym nie mieszal, latwiej bedzie najpierw zrobic poprawna wersje jednowatkowa

0

@pedegie: Jesli zmienie sposób odczytywania na

       BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ()));

w odpowiedzi otrzymuje:

odp: �� ���
odp: �� <��
odp: �� 6��
odp: �� E�
odp: �� K*�
odp: �� UM�
odp: �� ���
odp: �� ���
odp: � ���
odp: �� i�
odp: ��~C11920190529 ���
odp: ��|20720190708M{�
odp: �� }��
odp: ��
odp: }��
odp: �� ~V�
odp: �� ~v�

ale faktycznie odpowiedź jest natychmiast i jesli zapytanie obuduje w Runnable to mam zamierzony efekt oprócz tych wartości które na bity musze zamienić, teraz moam pytanie jak przekonwertowac string na bajty żebym mógł dekodować wartośći które przychodzą, moj kod wygląda na teraz tak:

        socket = new Socket("10.1.30.28", 7094);
        BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ()));

        Runnable helloRunnable = new Runnable() {
            public void run() {
                try {
                    sendBytes(MessageALL);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(helloRunnable, 0, 450, TimeUnit.MILLISECONDS);

        String fromClient;

        do{
            fromClient = in.readLine ();
            System.out.println ("odp: " + fromClient);
        }while (! fromClient.equals ("quit"));

dodanie: byte[] b = fromClient.getBytes(); nie przynosi poprawnego rezultatu

1

Jeżeli chcesz czytać bajty zamiast BufferedReader użyj BufferedInputStream. Z BufferedInputStreama możesz czytać bajty bezpośrednio, a nie chary jak w przypadku BufferedReadera.

0
        socket = new Socket("10.1.30.28", 7094);
        BufferedInputStream in = new BufferedInputStream(socket.getInputStream());

        sendBytes(MessageALL);

        byte[] b = new byte[1024];

        do{

            in.read(b);
            StringBuilder sb = new StringBuilder();
            for (byte c : b) {
                sb.append(String.format("%02X", c));
            }
            decode(sb.toString());

        } while (socket.isConnected());

tym kodem osiągnąłem zamierzony efekt, pytanie czy jest to poprawnie zrobione? I druga sprawa jak wykonywać ten kod nie blokując programu?

1

Ten scheduler ktorym to robisz nie blokuje programu. Niech runnable oprocz wyslania danych, rowniez odczytuje odpowiedz. Troche zle to zaprojektowales, Obejmij request-response w runnable

Runnable r = () -> { 
   send()
   readResponse()
  }

teraz to sie bedzie wykonywac co zadany interwal w schedulerze.

while(socket.isConnected()) jest zle, bo warunek bedzie prawdziwy, nawet po odczytaniu odpowiedzi, a Twoje zadanie powinno sie wtedy skonczyc.
Poza tym to nie sprawdza, tego co moze sie wydawac, ze sprawdza. Mianowicie ta metoda weryfikuje tylko, czy Twoj Socket jest polaczony, czyli czy wywolales wczesniej metode connect a nie czy jest aktywne polaczenie tcp/ip.
Ja bym robil YOLO write() a w catch'u lapal IOException i probowal wyslac jeszcze raz ale uprzednio robiac znowu connect, zakladajac ze zerwalo polaczenie

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.