Sieć Java: wyjaśnij strumień wejściowy i strumień wyjściowy w gnieździe

Utworzyłem serwer używając ServerSocket. Następnie utworzyłem klienta za pomocą Socket i połączyłem się z tym serwerem.

Po tym, robię "pewne rzeczy" z InputStream i OutputStream jest pobierany z obiektu Socket. Ale tak naprawdę nie rozumiem strumienia wejściowego i wyjściowego. Oto mój prosty kod:

private Socket sock = null;
private InputStream sockInput = null;
private OutputStream sockOutput = null;

...
            String msg = "Hello World";
            byte[] buffer = null;

            try {
                sockOutput.write(msg.getBytes(), 0, test.length());
                sockOutput.write("Hello StackOverFlow".getBytes(), 0, test.length());
                buffer = new byte[test.length()];
                sockInput.read(buffer, 0, test.length());
                System.out.println(new String(buffer));
                sockInput.read(buffer, 0, test.length());
                System.out.println(new String(buffer));
            } catch (IOException e1) {
                e1.printStackTrace();
                }

Wynikiem będzie: "Hello World" i "Hello StackOverFlow".

Oto kod po stronie serwera:

private int serverPort = 0;
    private ServerSocket serverSock = null;

    public VerySimpleServer(int serverPort) {
        this.serverPort = serverPort;

        try {
            serverSock = new ServerSocket(this.serverPort);
        }
        catch (IOException e){
            e.printStackTrace(System.err);
        }
    }

    // All this method does is wait for some bytes from the
    // connection, read them, then write them back again, until the
    // socket is closed from the other side.
    public void handleConnection(InputStream sockInput, OutputStream sockOutput) {
        while(true) {
            byte[] buf=new byte[1024];
            int bytes_read = 0;
            try {
                // This call to read() will wait forever, until the
                // program on the other side either sends some data,
                // or closes the socket.
                bytes_read = sockInput.read(buf, 0, buf.length);

                // If the socket is closed, sockInput.read() will return -1.
                if(bytes_read < 0) {
                    System.err.println("Server: Tried to read from socket, read() returned < 0,  Closing socket.");
                    return;
                }
                System.err.println("Server: Received "+bytes_read
                                   +" bytes, sending them back to client, data="
                                   +(new String(buf, 0, bytes_read)));
                sockOutput.write(buf, 0, bytes_read);
                // This call to flush() is optional - we're saying go
                // ahead and send the data now instead of buffering
                // it.
                sockOutput.flush();
            }
            catch (Exception e){
                System.err.println("Exception reading from/writing to socket, e="+e);
                e.printStackTrace(System.err);
                return;
            }
        }

    }

    public void waitForConnections() {
        Socket sock = null;
        InputStream sockInput = null;
        OutputStream sockOutput = null;
        while (true) {
            try {
                // This method call, accept(), blocks and waits
                // (forever if necessary) until some other program
                // opens a socket connection to our server.  When some
                // other program opens a connection to our server,
                // accept() creates a new socket to represent that
                // connection and returns.
                sock = serverSock.accept();
                System.err.println("Server : Have accepted new socket.");

                // From this point on, no new socket connections can
                // be made to our server until we call accept() again.

                sockInput = sock.getInputStream();
                sockOutput = sock.getOutputStream();
            }
            catch (IOException e){
                e.printStackTrace(System.err);
            }

            // Do something with the socket - read bytes from the
            // socket and write them back to the socket until the
            // other side closes the connection.
            handleConnection(sockInput, sockOutput);

            // Now we close the socket.
            try {
                System.err.println("Closing socket.");
                sock.close();
            }
            catch (Exception e){
                System.err.println("Exception while closing socket.");
                e.printStackTrace(System.err);
            }

            System.err.println("Finished with socket, waiting for next connection.");
        }
    }

    public static void main(String argv[]) {
        int port = 54321;
        VerySimpleServer server = new VerySimpleServer(port);
        server.waitForConnections();
    }

Moje pytanie brzmi :

  1. Kiedy używam sockOutput.write i mogę odzyskać te wiadomości przez sockInput.read. Więc ta wiadomość została zapisana, prawda? Jeśli to prawda, czy jest zapisany na serwerze, który utworzyłem, czy właśnie zapisany w innej rzeczy, takiej jak Socket Object.

  2. Jeśli napisałem do socket String A1, A2,... A więc otrzymam A1, A2, ... / Align = "left" /

Author: Bill the Lizard, 2012-10-03

2 answers

Gniazdo jest abstrakcją, której używasz do rozmowy z czymś w sieci. Patrz rysunek poniżej...

W Javie, aby wysłać dane przez gniazdo, otrzymujesz z niego OutputStream (1) i zapisujesz do OutputStream (wypisujesz pewne dane).

Aby odczytać dane z gniazda, należy uzyskać jego InputStream i odczytać dane wejściowe z tego drugiego strumienia.

Strumienie można traktować jako parę jednokierunkowych rur podłączonych do gniazda na ścianie. To, co dzieje się po drugiej stronie ściany, nie jest twój problem!

W Twoim przypadku serwer ma inne gniazdo (drugi koniec połączenia) i inną parę strumieni. Używa swojego InputStream (2) do odczytu z sieci, a swojego OutputStream (3) do zapisu tych samych danych z powrotem przez sieć do twojego klienta, który odczytuje je ponownie za pośrednictwem swojego InputStream (4) kończąc podróż w obie strony.

      Client                                                     Server

1. OutputStream -->\                                     /--> 2. InputStream -->
                    Socket <--> network <--> ServerSocket                       |
4. InputStream  <--/                                     \<--3. OutputStream <--

Aktualizacja: w odpowiedzi na komentarz:

Zauważ, że strumienie i gniazda po prostu wysyłają surowe bajty; nie mają pojęcia o "wiadomość" na tym poziomie abstrakcji. Więc jeśli wysyłasz x bajty i kolejne X bajty, następnie odczytasz x bajty i odczytasz kolejne X bajty, wtedy Twój system zachowuje się tak, jakby były dwie wiadomości, ponieważ w ten sposób podzieliłeś te bajty.

Jeśli wyślesz x bajtów, a następnie odczytasz odpowiedź o długości 2X, wtedy Może być w stanie odczytać pojedynczą połączoną "wiadomość", ale jak zauważyłeś, podstawowa implementacja strumieni może wybrać, kiedy dostarczyć fragmenty wiadomości. bajtów, więc może zwrócić X bajtów, potem x bajtów, później, lub 2X na raz, lub 0,5 x cztery razy...

 33
Author: DNA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-09 13:53:34

InputStream i OutputStream są dwoma zupełnie oddzielnymi strumieniami. To, co piszesz w jednym, nie ma a priori związku z tym, co czytasz z drugiego. InputStream daje Ci wszelkie dane, które serwer zdecyduje się wysłać do ciebie. Chciałbym również skomentować ten fragment Twojego kodu:

sockOutput.write(msg.getBytes(), 0, test.length());
sockOutput.write("Hello StackOverFlow".getBytes(), 0, test.length());

Używasz długości łańcucha test (nie pokazanego w kodzie), który nie ma nic wspólnego z tablicą bajtów, którą przekazujesz jako pierwszy argument. Może to spowodować ArrayIndexOutOfBoundsException lub obcięcie zamierzonego wiadomość.

Dodatkowe komentarze do twojego zaktualizowanego pytania

Przeglądając kod po stronie serwera, nie jest on do końca poprawnie napisany. Musisz mieć try { handleConnection(...); } finally { socket.close(); }, aby zapewnić prawidłowe czyszczenie po błędzie, a także podczas normalnego wypełniania. Twój kod nigdy nie zamyka niczego po stronie serwera.

Wreszcie, i najbardziej krytycznie, cały Twój kod jest napisany w sposób, który może doprowadzić do impasu. Zwykle do czytania i pisania potrzebny jest osobny wątek; w przeciwnym razie mogą happen:

  1. próbujesz zapisać pewne dane na wyjście;
  2. Serwer odczytuje dane i próbuje odpowiedzieć na nie danymi wejściowymi.]}
  3. ale, ponieważ bufory są zbyt małe, nie uda Ci się wysłać wszystkiego, ponieważ serwer chce najpierw wysłać coś do ciebie, a następnie odebrać resztę; ale nie dotrzesz do części odbierającej, zanim wyślesz wszystko, co masz.
 4
Author: Marko Topolnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-04 06:40:44