Problem mit Socket-Programmierung

lun27

Grünschnabel
Hallo ihr! :)

Habe ein kleines Problem, das wohl sehr gut in die Thematiken Sockets, Streams und Casting passt.
Da ich noch in den Startlöchern der (Java-)Programmierung stehe, weiß ich mir - selbst nach langem Googeln und Foren-Lesen - nicht wirklich zu helfen. Nun aber zu meinem Problem:

Ich möchte einen Clienten basteln, der eine Verbindung zu einem bereits bestehenden Programm (Server) aufbauen soll. Der Server gibt nach dem Verbindungsaufbau sofort eine Nachricht zurück, die eine fünfzeilige Länge hat. Der Client soll diese Nachricht sofort abfangen und ausgeben. Soweit ist das ja alles kein Problem und das Grundprinzip habe ich nun bereits auf mehreren Wegen durchprobiert. Hier mal das simple Beispiel mit einem char[]:

Java:
    public static void main(String args[]) throws Exception {
        
        Socket client = new Socket("localhost", 4242);
        System.out.println("Connected...");
        System.out.println("------------");
        
        Thread.sleep( 1000 );
        
        InputStreamReader in = new InputStreamReader( client.getInputStream() );
        //BufferedReader in = new BufferedReader( new InputStreamReader( client.getInputStream() ) );
        
        while ( !in.ready() ) {
            Thread.sleep(100);
        }
        
        char[] buffer = new char[200];
        int number = in.read(buffer, 0, buffer.length);
        String msg = new String(buffer, 0, number);
        System.out.println( msg );
        
        in.close();
        client.close();
        System.out.println("---------------");
        System.out.println("Disconnected...");
    }

Das Problem, das ich mit dem char[] habe, ist, dass es eine vordefinierte Größe hat und somit Rückmeldungen vom Server, die einen Zeichensatz von über 200 Zeichen beinhalten, nicht mehr vollständig ausgibt (ansonsten läuft das Programm ohne Fehler).

Nun habe ich versucht das Ganze so umzuschreiben, dass der InputStream komplett ausgelesen wird ohne eine vordefinierte Anzahl an Zeichen, die nur ausgelesen werden sollen/können. Meine Versuche ergaben folgendene Codebeispiele:

Casting to Char ohne die vordefinierte Buffer-Größe:
Java:
int x, y = 0;
        while ( ( x = in.read() ) != -1 ) {
            System.out.print( (char)x + "("+ y++ +")" );
        }
Bei diesem Beispiel bleibt er am Ende des Streams hängen, wo er theoretisch doch -1 zurückgeben und die while-Schleife verlassen müsste.

readLine()-Beispiel:
Java:
String line;
while ( ( line = in.readLine() ) != null ) {
                System.out.println( line );
            }
Bei diesem Beispiel bleibt er ebenfalls hängen und zwar in der letzten Zeile, wo sich auch das Stream-Ende befindet. Dennoch befindet sich Text in der letzten Zeile, der aber nicht mehr ausgegeben wird.

Scanner-Beispiel:
Java:
Scanner scanner = new Scanner( conn.getInputStream() );
            while ( scanner.hasNextLine() ) {
                System.out.println( scanner.nextLine() );
            }
            scanner.close();
Hier passiert genau das Selbe wie im readLine()-Beispiel.


Nun frage ich euch, was mache ich falsch, dass er ständig am Ende des Streams hängen bleibt, aber im ersten char[]-Beispiel den kompletten Stream in den char[]-Buffer einliest und dann das Programm ohne hängen zu bleiben beendet?
Ich würde den InputStream gerne komplett auslesen können, ohne dabei eine vordefinierte Menge als Buffer einsetzen zu müssen. Die Möglichkeit das Problem mittels Threading zu umgehen, indem man mit einem weiteren Thread das Hängenbleiben abfängt und folglich mit einem Timeout beendet, möchte ich ebenfalls nicht in Betracht ziehen.

Ich hoffe, ich habe nichts vergessen zu posten, das bei der Lösung des Problems behilflich sein könnte und bin nun gespannt auf eure Postings.

Gruss
Luni
 
Meine Java Kentnisse sind zwar etwas eingerostet, aber ich meine mich zu entsinnen, das
readLine() ein Zeilenende/umbruch erwartet, sprich am ende der letzten Zeile sollte glaub ich noch ein "\n" stehen.
 
Das hab ich mir auch schon gedacht, aber das würde nur erklären, warum er bei readLine() hängen bleibt. Leider passiert das nicht nur bei diesem Beispiel. Außerdem kann ich nicht beeinflussen, ob der Server mir eine Zeile zurückgibt, bei der am Ende auch ein \n steht.
 
Hier mal ein Ausschnitt aus einem Supersimple HTTP-Client.

Da ist alles drin was Du brauchst....

Java:
package de.coba.net;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URL;

public class HttpClient {

	public HttpClient(String urlString) {
		
		try {
			URL url = new URL(urlString);
			Socket socket = new Socket(url.getHost(),80);
			BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			PrintWriter out = new PrintWriter(socket.getOutputStream());
			
			//GET /infotext.html HTTP/1.1
			//Host: www.example.net
			System.out.println(url.getPath());
			System.out.println(url.getHost());
			out.println("GET /" + url.getPath() + " HTTP/1.0\n");
			out.println("Host: " + url.getHost()+"\n");
			out.println("\n");
			//out.println();
			out.flush();
			String eingabe ;
			do {
				eingabe = in.readLine();
				System.out.println(eingabe);
			} while( eingabe != null && eingabe.length() != 0);
			socket.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
	
	public static void main(String[] args) {
		
		if (args.length < 1) {
			System.out.println("Bitte geben Sie beim Aufruf eine URL an.");
			System.exit(0);
		}
		
		String url = args[0];
		
		new HttpClient(url);
			

	}

}
 
Habe die do-while-Schleife mal eingebaut, aber die bringt auch nicht den gewünschten Effekt. Sie bleibt zwar nicht hängen, aber das liegt auch nur daran, dass sie bereits in der zweiten Zeile (von fünf) rausspringt, da diese Zeile nichts weiter enthält wie ein \n und/oder ein \r. Da \n\r aber nicht in Chars/Strings visuell dargestellt werden, bleibt die String-Länge von "eingabe" aus deinem Beispiel bei 0 und somit wird die do-while-Schleife beendet, obwohl sich noch mehr Input im Stream befindet. Sobald ich die "!= 0"- Bedingung aus der Schleife entferne, gibt er den kompletten Stream fehlerfrei aus, aber bleibt dann wieder hängen.
 
Ich will ja nichts falsches sagen, aber ich glaub übrigens Scanner hat auch das problem mit kein "\n" am ende.

in der api steht z.b. :

Since this method continues to search through the input looking for a line separator, it may buffer all of the input searching for the line to skip if no line separators are present.

Nur leider hilft das wohl nicht das Problem zu lösen.

Das simpelste wäre wenn der Server in der letzten Zeile "\n" oder so mitschickt, nur daruaf hast du ja keinen Einfluss wenn ich das richtig verstanden habe.
 
Richtig, der Server ist ein fertiges Programm, das Befehle über Telnet annimmt, sie verarbeitet und dann je nach Befehl eine Antwort in Form eines String zurückschickt.

Das Beispiel mit dem Scanner ist im Prinzip genau das Gleiche wie beim readLine()-Beispiel, habe es aber dennoch gepostet, damit ihr seht, dass ich auch selber schon versucht habe das Problem zu lösen.

Momentan versuch ich mich nochmal an der char[]-Variante und versuche diese so umzuschreiben, dass das char-Array mit seiner vordefinierten Größe erhalten bleibt, damit es ohne hängen zu bleiben den Stream lesen kann. Nur diesmal hab ich die Größe auf 100 Zeichen herabgesetzt, damit es beim ersten Durchlauf nur die Hälfte des Streams ausgibt. Das wäre dann genau der Fall, den ich zu vermeiden versuchen wollte. Aber warum den Buffer nicht nochmal beschreiben lassen, also hab ich es bei dem Beispiel auch mal mit einer Schleife versucht und als Bedingung die Anzahl der Zeichen genommen, die die überladene Methode "read(char[] cbuf, int off, int len)" zurückgibt und diese darauf geprüft, ob sie denn noch die Größe des char[]-Buffers hat. Wenn das nicht der Fall sein sollte, weil weniger Zeichen gelesen wurden, dann soll die Schleife nicht weiter durchlaufen werden, wodurch das verkorkste Streamende mit der imaginären -1 bzw. der imaginären null umgangen wird. Hier nun aber der Code:

Java:
BufferedReader in = new BufferedReader( new InputStreamReader( conn.getInputStream() ) );
            String msg = "";
            
            char[] buffer = new char[100];
            int length = buffer.length;
            int number = 100;
            String part;
            
            while ( number == 100 ) {
                number = in.read( buffer, 0, length );
                part = new String( buffer, 0, number );
                msg = msg.concat( part );
            }
            
            System.out.println( msg );
            
            in.close();

Ist wahrscheinlich noch keine perfekte Lösung, aber sie funktioniert erstmal. :)

Gruss
Luni
 
Und der Server schickt keine Angabe, wie lang die Antwort ist bzw. liefert keine Endmarkierung mit zurück?
 
Naja, wenn ich das richtig verstanden habe, was ich nun alles so über Sockets und Streams gelesen habe, dann ist es glaube ich so, dass die Methode read() der beiden Streams InputStreamReader und BufferedReader das Ende des Streams nicht mit einer -1 zurückgibt, weil sie noch auf weitere Daten von der getInputStream-Methode wartet und somit endet z.B. folgende Lösung in einer Endlosanwendung. Hierbei ist allerdings nicht die while-Schleife der Auslöser sondern die Methode read(), die nicht mehr beendet wird.

Java:
BufferedReader in = new BufferedReader( new InputStreamReader( client.getInputStream() ) );

int x;
while ( ( x = in.read() ) != -1 ) {
    System.out.print( (char)x );
}
Das Beispiel scheint bei Ausgaben von Streams, die z.B. Dateien auslesen, zu funktionieren, aber sobald diese Lösung in Berührung mit einem Socket-InputStream kommt, dann scheint die Rückgabe -1 imaginär zu sein, denn anstelle dessen bleibt die Methode read() in einer internen Endlosschleife hängen.

Hab das jetzt mal versucht mit meinen eigenen Worten wiederzugeben, aber wer sich darüber nochmal schlau machen möchte, dem soll folgender Link eine kleine Hilfe sein:

Netzwerkprogrammerung mit Java

Da steht z.B. gut beschrieben welche Streams man noch verwenden kann und warum. Es gibt ja in Java einen ganzen Haufen Streams und nicht jeder macht bzw. kann das Selbe, auch wenn es manchmal so scheint. Folgendes Beispiel wird dort auch erklärt:

Java:
DataInputStream in = new DataInputStream( client.getInputStream() );

while ( in.available() > 0 ) System.out.print( (char)in.read() );
Das scheint auch die Lösung zu sein, nach der ich gesucht habe, aber das muss ich erstmal selber richtig ausprobieren (habs gerade erst gefunden :)).
Der DataInputStream bietet z.B. die Methode available(), womit man sich die Länge eines Streams ausgeben lassen kann. Sobald man die Methode read() aufruft, verringert sich ja die Länge um genau ein Zeichen und irgendwann kommt man bei 0 an, wo man dann aus der while-Schleife geschmissen wird - wie vorhergesehen also (im Gegensatz zur -1).

Jetzt wird sich bestimmt manch einer fragen, warum hat der Trottel nicht einfach die Java-API gelesen? Oh, das hab ich allerdings, aber ihr müsst zugeben, ihr lest euch auch nicht die komplette API durch, wenn ihr nicht wisst, wo ihr bei eurem Problem ansetzen sollt. Außerdem muss ich gestehen, dass die API nicht gerade so umschrieben ist, dass ein Neuling wie meiner einer das auch gleich kappiert. :rolleyes:

So, nun bin ich an einem Punkt angekommen, wo mir misfallen ist, warum ich hier eigentlich den ganzen Kram reinschreibe, wo ich doch mein Problem eigentlich schon selbst gelöst habe. Ich glaub es liegt daran, dass es mir irgendwie Spaß macht. :) Ne im Ernst, ich hoffe das Thema hilft dem Ein oder Anderen, der gerade an der Socket-Programmierung genauso verzweifelt wie es bei mir der Fall war oder vielleicht sogar noch ist - man weiß ja nie was noch kommt. :D
 
Zurück