# OutputStream kommt nicht an



## Tueftler (30. Jul 2021)

Konstellation: Fritzbox, zwei Laptops, einer nur mit einem Clientprogramm, der andere mit einem Server- und einem Clientprogramm. Die Clientprogramme sind auf beiden Rechnern identisch.

Problem: Die Client-Server-Kommunikation läuft problemlos, wenn Client und Server auf unterschiedlichen Rechnern laufen. Rufe ich den Server aber vom Client auf dem Server-Laptop auf, so ist nur bis zum letzten Senden des Server-OutputStreams alles ok. Den OutputStream sendet der Server zwar, aber der Client empfängt nichts.

Meine Vermutung: Der Server streamt ins Nirwana (ohne Fehlermeldung). Aber warum?

Hier der der Servercode:
[CODE lang="java" title="Aufruf"]new Thread(){
    public void run(){
        new LogInServer();
    }
}[/CODE]
Thread, da anschließend noch andere Server aufgerufen werden.

[CODE lang="java" title="LogInServer:"]try{
    ServerSocket serverSocket= new ServerSocket(70);
    while(true){
        Socket clientSocket = serverSocket.accept();
        new LogInThread(clientSocket);
    }
}catch(IOException e){...}[/CODE]


LogInThread:

```
...
public void run(){
    try{
        String clientsLaptopName = "";
        InputStream in = clientSocket.getInputStream();
        OutputStream out = clientSocket.getOutputStream();
        byte[] buffer = new byte[in.available()]
        in.read(buffer)
        for(byte b:buffer){
            clientsLaptopName += (char)b;
        }

        // Es folgt Registrierungs-Check und Username ermitteln
        // Das Ergebnis wird in den String 'msg' gespeichert

        byte[] data = msg.getBytes();
        out.write(data);
        out.flush();

        in.close();
        out.close()
        clientSocket.close();

    }catch(IOException e){e.printStackTrace();}
```

Client:

```
try{
        adrIn = InetAddress.getByName("ServerLaptop");
        localIn = InetAddress.getLocalHost();
        localStrIn = localIn.getHostName();
        sockIn = new Socket(adrIn,70);
        
        // Namen des Client-Laptop schicken
        byte[] data= localStrIn.getBytes();
        out.write(data);

        //Message vom Server empfangen - und das klappt nicht
        String msg="";
        int c;
        while(c = in.read() != -1){
            msg += (char)c;
        }

    }catch(IOException e){e.printStackTrace();}
```


----------



## kneitzel (30. Jul 2021)

Hast Du dem Server entsprechend mitgeloggt, dass der server die Nachricht vom Client richtig empfangen hat?

```
byte[] buffer = new byte[in.available()]
        in.read(buffer)
        for(byte b:buffer){
            clientsLaptopName += (char)b;
        }
```
sieht mir nicht wirklich korrekt aus - wenn die Nachricht vom Client noch nicht (komplett) empfangen wurde, dann ist da evtl. auch noch nichts zu lesen. Das in.available() wäre also etwas, das ich da prüfen würde ...

Generell ist bei so Protokollen zu bedenken, dass die Bytes nach und nach ankommen können. Du hast da jetzt erst einmal kein wirkliches Erkennungszeichen, bis wohin eine Nachricht geht. Das könnte man z.B. zeilenorientiert machen. Dann musst Du da auch nicht von Hand dir die Bytes vom String holen und so, da der Reader / Writer das dann schon machen würden.
Trennzeichen wäre dann das Newline Zeichen und so Du eine ganze Zeile einliest, blockiert der Thread, bis das angekommen ist.

Dann fürchte ich, dass die Codierung von Bytes -> String so nicht korrekt ist. Ein Zeichen kann mit bis zu 4 Bytes codiert werden - abhängig vom Charset. Das getBytes() nutzt das default Charset vom System - da würde ich auf jeden Fall ein spezielles charset vorgeben.

Auf der Client-Seite ist es nicht ganz so wild. Da liest Du ja, bis der Stream geschlossen wurde. Da vermute ich jetzt erst einmal kein Problem.

Das wären so meine Auffälligkeiten.


----------



## fhoffmann (30. Jul 2021)

In LogInThread schließt du die Verbindung zum Client `clientSocket.close();` unmittelbar nach dem Senden `out.write(data);`. Möglicherweise ist der Server mit dem Senden der Nachricht noch gar nicht fertig, wenn du die Verbindung schließt.


----------



## Jw456 (30. Jul 2021)

wie es ausschaut benutzt du auf dem Client den localhost benutze die ip von der Netzwerkkarte. Die sie im netz hat.
Die benuzt auch der Server.

 localIn = InetAddress.getLocalHost();
ist bestimmt 127.0.0.1


----------



## kneitzel (30. Jul 2021)

fhoffmann hat gesagt.:


> In LogInThread schließt du die Verbindung zum Client `clientSocket.close();` unmittelbar nach dem Senden `out.write(data);`. Möglicherweise ist der Server mit dem Senden der Nachricht noch gar nicht fertig, wenn du die Verbindung schließt.


Nach meinem Verständnis sollten die Daten aber durch das flush() geschrieben worden sein.


----------



## Jw456 (30. Jul 2021)

kneitzel hat gesagt.:


> Nach meinem Verständnis sollten die Daten aber durch das flush() geschrieben worden sein.


ja auf die IP die der Rechner im Netz hat. empfangen will er auf Localhost .


Kommt dann auf die Einstellung des Rechners an ob das weiter geleitet wird. 

Der Code kann ja nicht direkt falsch sein wenn es auf unterschiedlichen Rechnen geht.


----------



## kneitzel (30. Jul 2021)

Jw456 hat gesagt.:


> ja auf die IP die der Rechner im Netz hat. empfangen will er auf Localhost .
> 
> 
> Kommt dann auf die Einstellung des Rechners an ob das weiter geleitet wird.
> ...


Nein, das ist so komplett falsch. Der Server bindet auf jeden Fall an die externe IP, denn von anderen Rechnern funktioniert es. Und selbstverständlich kann er sich selbst über die externe IP problemlos erreichen.

Und dann sind das TCP/IP Verbindungen und da ist eine Verbindung aufgebaut. Und wenn die Verbindung von externerIp zu externerIp geht, dann ist das auch in jedem Paket eben genau so abgebildet. Da wird dann bei einer bestehenden Verbindung nicht plötzlich an eine andere IP wie 127.0.0.1 etwas gesendet.

Und natürlich kann der Code falsch sein. Das muss er sogar, denn sonst würde er ja funktionieren. Und es gibt zwei Vermutungen in diesem Zusammenhang. Einmal meine, dass da Daten nicht wirklich korrekt übertragen wurden (incl. dem Hinweis zum Encoding) und von @fhoffmann die Überlegung, ob es am zu schnellen schließen liegt (unabhängig davon ob ich dies glaube oder nicht).

Meine Vermutung ist ersteres. Denn es läuft auf einem System, Client und Server teilen sich die vorhandenen Ressourcen und die Umschaltung zwischen Threads erfolgt immer in Scheiben (bzw. wenn der Thread blockiert).

Client baut Verbindung auf - blockiert weil Warten auf Server
Server nimmt an, ehe der Client da wieder Ausführung bekommt, schaut der Server nach empfangenen Zeichen. Diese sind 0. Er liest 0 Zeichen und geht dann die Nachricht bauen. Was da dann bei raus kommt: keine Ahnung ...
Client schickt dann natürlich früher oder später auch Daten ...

So ein Ablauf hört sich für mich durchaus plausibel an. Bestätigen kann man sowas durch ein Logging. Dann weiss man ja, was der Server vom Client empfangen hat...


----------



## Jw456 (30. Jul 2021)

Interesant wäre auch wie du in deinem Client die in und out Streams gesetzt hast.
wo machst du das im Client?


----------



## Jw456 (30. Jul 2021)

kneitzel hat gesagt.:


> Nein, das ist so komplett falsch. Der Server bindet auf jeden Fall an die externe IP, denn von anderen Rechnern funktioniert es. Und selbstverständlich kann er sich selbst über die externe IP problemlos erreichen.
> 
> Und dann sind das TCP/IP Verbindungen und da ist eine Verbindung aufgebaut. Und wenn die Verbindung von externerIp zu externerIp geht, dann ist das auch in jedem Paket eben genau so abgebildet. Da wird dann bei einer bestehenden Verbindung nicht plötzlich an eine andere IP wie 127.0.0.1 etwas gesendet.
> 
> ...


OK

        adrIn = InetAddress.getByName("ServerLaptop");
         sockIn = new Socket(adrIn,70);

er hat die adresse erfragt
wo nutzt er das bekomme socket?

wo hat er  den in und out Steam erstellt mit den socket "sockln" ?
hoffe er benutzt nicht die in und out vom Server.


----------



## fhoffmann (30. Jul 2021)

kneitzel hat gesagt.:


> Denn es läuft auf einem System, Client und Server teilen sich die vorhandenen Ressourcen und die Umschaltung zwischen Threads erfolgt immer in Scheiben (bzw. wenn der Thread blockiert).


Aber das würde doch auch meine Idee unterstützen:
- Der Server sendet die Nachricht und beendet die Verbindung.
- Nun bekommt der Client Rechenzeit: Er kann die Nachricht vom Server nicht mehr lesen, weil die Verbindung schon geschlossen ist.


----------



## kneitzel (30. Jul 2021)

fhoffmann hat gesagt.:


> Aber das würde doch auch meine Idee unterstützen:
> - Der Server sendet die Nachricht und beendet die Verbindung.
> - Nun bekommt der Client Rechenzeit: Er kann die Nachricht vom Server nicht mehr lesen, weil die Verbindung schon geschlossen ist.


Hier bin ich jetzt nicht der Netzwerk Experte. Aber das läuft ja bereits auf Betriebssystem Ebene (oder teilweise sogar direkt in der Hardware - Die Netzwerkkarte macht schon sehr viel alleine).

Aber das kann man ja durchaus einmal austesten ... gib mir mal ein paar Minuten. Das Interessiert mich jetzt wirklich


----------



## kneitzel (30. Jul 2021)

Ok, das was ich testen wollte um sicher zu gehen: Selbst wenn der Server Stream und Socket geschlossen hat, hat der Client die Daten im Puffer.

Dazu habe ich einfach mal eine Testklasse geschrieben, die zwei Threads startet. Einmal Server und einmal Client.

Der Server öffnet einen Port, nimmt eine Verbindung an, schreibt ein "Hallo!" und macht sofort flush und close.
Der Client wartet kurz (So kann der Server sich aufbauen. Dann verbindet er sich, Schläft wieder (So kann der Server darauf reagieren!) Und dann liest er einmal alle Daten.



Spoiler: Code



[CODE lang="java" title="TcpIpTest Klasse"]import java.io.IOException;
import java.io.InputStream;
import java.iutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class TcpIpTest {
    public static void main(String[] args) {
        Thread server = new Thread(TcpIpTest::serverWorker);
        server.start();

        Thread client = new Thread(TcpIpTest::clientWorker);
        client.start();
    }

    private static void serverWorker() {
        try {
            System.out.println("Server: opening port!");
            ServerSocket serverSocket = new ServerSocket(1234);
            Socket client = serverSocket.accept();
            System.out.println("Server: Client connected!");
            OutputStream out = client.getOutputStream();
            out.write("Hallo!".getBytes(StandardCharsets.UTF_8));
            System.out.println("Server: 'Hallo!' written!");
            out.flush();
            System.out.println("Server: flushed!");
            out.close();
            System.out.println("Server: out closed!");
            client.close();
            System.out.println("Server: client closed!");
            serverSocket.close();
            System.out.println("Server: server socket closed!");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private static void clientWorker() {
        try {
            System.out.println("Client: Sleeping...");
            Thread.sleep(500);
            System.out.println("Client: Connecting...!");
            Socket socket = new Socket(InetAddress.getLocalHost(), 1234);
            System.out.println("Client: Connected!");
            InputStream in = socket.getInputStream();
            System.out.println("Client: Sleeping...");
            Thread.sleep(500);
            System.out.println("Client: Reading Data ...");
            System.out.println("Received: " + new String(in.readAllBytes(), StandardCharsets.UTF_8));
            socket.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
[/CODE]



Die Ausgabe zeigt, dass der Client das Hallo! noch lesen kann:

```
Server: opening port!
Client: Sleeping...
Client: Connecting...!
Client: Connected!
Client: Sleeping...
Server: Client connected!
Server: 'Hallo!' written!
Server: flushed!
Server: out closed!
Server: client closed!
Server: server socket closed!
Client: Reading Data ...
Received: Hallo!
```

Habe auch einmal das Sleep direkt hinter dem new Socket plaziert ... das ändert an der Problematik nichts. Also der InputStream spielt keine Rolle - das kann auch warten.

Hintergrund ist, dass die Socket Klasse in Java ja auch nur auf den Sockets vom Betriebssystem aufsetzt. Und da sind entsprechende Puffer.

Und was passiert denn, wenn man sich den Traffic anschaut?
Server sendet die Daten (durch das flush vom Stream geht es an den Socket).
Server macht das close() das bedeutet aber, dass die Verbindung abgebaut werden soll. Also schickt der Server an den Client: Hey - ich will die Verbindung schließen! Darüber unterhalten sich Server und Client ... es gibt also auch da ein Handshake für.

Ich habe diese Handshakes mal heraus gesucht:


			TCP-Kommunikation
		

Ich hatte mal eine bessere Seite, aber die finde ich nicht. Die hatte ich vor kurzem @Jw456 gegeben in einem anderen Thread wo ich geantwortet hatte ... aber da sieht man das auch, was da zum Verbindungsabbruch hin und her geht.


----------



## thecain (30. Jul 2021)

kneitzel hat gesagt.:


> Ich hatte mal eine bessere Seite, aber die finde ich nicht


Ich weiss nicht welche es war, aber ich finde das hier super: https://hpbn.co/ zwar sehr ausführlich, aber wenn mans genau wissen will


----------



## Tueftler (30. Jul 2021)

Was passiert in dem Programm überhaupt? Also: der Client baut die Verbindung zum Server auf und schickt ihm den Namen des Clientrechners. Der Server schaut in einer HashMap nach, ob der Clientrechner (key) registriert ist. Falls ja, liefert die Map als value den Usernamen. Diesen Namen soll der Server über den OutputStream an den Client zurückschicken.

Zu Euren Anregungen:
Stimmt, in.available ist zur Größenbestimmung des byte Arrays unsauber (werde ich noch ändern), aber beim debuggen mit Eclipse bekomme ich da immer den richtigen Namen des Clientrechners übermittelt und der Server ist bisher nie darüber gestolpert. Ist es ein Denkfehler, wenn ich annehme, dass der InputStream des Servers hängen bleiben würde, wenn der buffer zu klein wäre, würde dann nicht eine IndexOutOfBounds geworfen? Beides passiert nicht.

zum problematischen getBytes(). Ich verwende ja keine CharacterStreams  sondern ausschließlich ByteStreams und hoffe, damit das Problem der charsets umgangen zu haben. Ist das falsch?

zum clientSocket.close(): Wenn ich clientseitig den Socket schließe, müsste eigentlich serverseitig der Thread für den Garbage Collector freigegeben werden. Ich bin aber unsicher, ob damit auch wirklich der Socket sauber geschlossen wird. Deshalb der close() im Server. Nehme ich die Zeile aus dem Code, empfängt der Client trotzdem nicht den OutputStream des Servers. 

Zum LocalHost: Die InetAddress von LocalHost nutze ich ausschließlich clientseitig und da auch nur, weil ich zu faul bin, mir einen eleganteren Weg zu suchen, um an den Rechnernamen des Clients zu kommen. Den Server spreche ich über den Namen des Serverrechners an (getByName("ServerLaptop"))

Zu den Streams: Stimmt, die Zeilen für in und out fehlen im abgedruckten Code (Asche auf mein Haupt) aber nur hier, sonst würde ja gar nichts gehen.


----------



## Jw456 (30. Jul 2021)

ich war zu langsam


----------



## Jw456 (30. Jul 2021)

Hast Du dem Server entsprechend mitgeloggt, dass der server die Nachricht vom Client richtig empfangen hat?

Auf das von @kneitzel hast du leider nicht geantwordet.


----------



## kneitzel (30. Jul 2021)

Das Problem ist, dass die Daten ja noch nicht da sein müssen. Du liest genau ein mal. Und das was dann da ist, das bekommst Du. Aber du hast Keinerlei Kontrolle, dass dies die ganze Nachricht ist!

Wenn Der Text nicht in einem Paket untergebracht werden kann, dann ist evtl. erst ei Teil da. Daher brauchst Du ein sauberes Protokoll.

Meine Empfehlung ist daher, auf dem InputStream / OutputStream einen BufferedReader und ein PrintWriter.
Dann kannst Du einfach readLine bzw. println nutzen und du hast wirklich die Zeile, die gesendet wurde, am Stück.
Und dann kannst Du auch das Encoding festlegen:
BufferedReader inReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));

Ob das schon Dein Problem behebt kann ich nicht sagen. Aber das wäre eine Verbesserung.


----------



## Barista (30. Jul 2021)

Tueftler hat gesagt.:


> Stimmt, in.available ist zur Größenbestimmung des byte Arrays unsauber


TCP-Streams sind prinzipiell endlos (ausser der Stream wird vom Partner geschlossen).

Also entweder bis zum Schliessen des Streams blockieren (macht die Methode int read() )

oder ein spezielles Endezeichen für den Client-Laptopnamen einfaühren, z. Bsp. newline.


----------



## Tueftler (30. Jul 2021)

Jw456 hat gesagt.:


> Hast Du dem Server entsprechend mitgeloggt, dass der server die Nachricht vom Client richtig empfangen hat?


Was meint Ihr mit mitloggen? Was ich gemacht habe: Nach dem Auslesen des InputStreams habe ich System.out.println(clientsLaptopName) eingebaut. Ergebnis: alles korrekt angekommen (Vom Client zum Server). Part mit meiner Registrierung: einwandfrei. byte[] data für den OutputStream korrekt gefüllt. out.write(data)   (vom Server zum Client) bringt keinerlei Fehlermeldung.  
Hab jetzt aber den Rat befolgt und nicht die Bytes ausgelesen, sondern den BufferedReader eingesetzt und siehe da: Alles läuft. Vielen Dank für Eure Hilfe


----------

