# wait/notify bei Socket



## Milo (27. Aug 2013)

Hallo,

ich möchte einen rudimentären Client erzeugen, der von einem Server Daten abfragt. In der Insel habe ich gelesen, dass ich Blockierungen abfangen sollte. Wenn ich die dortige Lösung richtig verstehe, wird immer 2sec gewartet auch wenn die Antwort ggf. schon eingetroffen ist. Nun dachte ich, dass ich dieses Problem über wait/notify umgehen kann.


```
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class SocketTest {

	private final String uri;
	private final int port;
	private Socket socket;
	private PrintWriter os;
	private BufferedReader is;
	private final static String AUTHENTIFIKATION = "GET_DATA";
	private StringBuffer response = null;

	public SocketTest(String uri, int port) {
		this.uri  = uri;
		this.port = port;
	}
	
	public boolean open() {
		try {
			socket = new Socket(uri,port);
			os = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
			is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			return true;
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} 
			
		return false;
	}

	public void getData()  {
		this.response = new StringBuffer();
		try {
			this.sendMessage(AUTHENTIFIKATION);
			
			synchronized (this.response) {
				new Thread() {
					public void run() {
						try {
							readMessage();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}.start();
				// Sollte HIER nicht nach 2sec abgebrochen werden???
				if (this.response == null || this.response.length() == 0)
					this.response.wait(2000);
				
				System.out.println("Antwort " +response);
			}
		} 
		catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	public boolean close() {
		try {
			if (this.is != null)
				this.is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			if (this.os != null)
				this.os.close();
			if (this.socket.isClosed())
				this.socket.close();
			
			return true;
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	private void sendMessage(String message) {
		this.os.print(message);
		this.os.flush();
	}
	
	private void readMessage() throws IOException {
		synchronized (response) {
			char[] buffer = new char[1024];
			int read = this.is.read(buffer, 0, buffer.length); // blockiert bis Nachricht empfangen
			this.response.append( new String(buffer, 0, read) );
			this.response.notify();
		 }
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		SocketTest socket = new SocketTest("127.0.0.1", 11111);
		socket.open();
		socket.getData();
		socket.close();
	}

}
```

Ich hatte gehofft, dass durch this.response.wait(2000); max. 2 Sekunden auf den gestarteten Thread gewartet wird und dann in jedem Fall die Methode fortgesetzt wird. Dies ist aber nicht so. Wenn ich die Antwortzeit des Servers mit einem Thread.sleep(10000); verzögere, wartet mein Client die 10sec und bricht nicht, wie gehofft, nach 2sec ab.

Habe ich einen Denkfehler bei der Verwendung von wait/notify?

Gruß Micha

P.S. seit wann gehen eckige Klammern im Code verloren hier im Forum?


----------



## mfernau (28. Aug 2013)

1. Ich kenne den dort erwähnten NIO-Ansatz nicht. Wenn man vermeiden möchte, dass ein Thread endlos auf eine eingehende Nachricht eines Sockets wartet, würde ich (persönliche Meinung) ein Timoutprinzip verwenden. Dafür kann man vor jedem Aufruf von read auf einem Inputstream mit einem TimerTask sicher stellen, dass close() auf dem Socket nach dem Ablauf eines festgelegten Timeouts aufgerufen wird. Ich habe das für meinen Server bei meinem aktuellen Projekt so gelöst:

```
@Override
	public String readLine(long timeout) throws IOException {
		String line = null;
		
		TimerTask tt = null;
		if(timeout != 0) {
			tt = new TimerTask() {
				@Override
				public void run() {
					timoutConnection();
				}
			};
			timer.schedule(tt, timeout);
		}
		
		try {
			line = in.readLine();
			// [...]
			
			if(tt != null) {
				tt.cancel();
				timer.purge();
			}
		} catch (IOException e) {
			/*
			 * Kann auftreten wenn:
			 * a) Ein timeout aufgetreten ist
			 * b) Andere Kommunikationsprobleme auftraten (Client hat Verbindung unterbrochen)
			 */
			if(connectionTimedOut) {
				throw new SocketTimeoutException("Connection timed out");
			} 
			
			throw e;
		}
		
		return line;
	}
```
Ich kann so mit Angabe eines Timeouts festlegen, wie lange ich maximal auf eine Antwort vom Client warten möchte. Rein vom Prinzip her kann ich an blockierenden Threads nichts schlimmes finden. Sie warten ja nicht aktiv auf Arbeit, sondern schlafen so lange bis Input vom Client herein kommt.

2. Deine Variante funktioniert nicht, weil Deinen Thread in der readMessage() Methode auf dem response-Objekt den Monitor hält. 
Was passiert?
a) Du erstellst einen Thread und markierst ihn zum starten. Er wird noch nicht laufen. 
b) Jetzt läuft dein main-Thread in das wait() und gibt damit den Monitor im synchronized Block so lange auf.
c) Nun wird dein neu erstellter Thread zum Zuge kommen und in die readMessage()-Methode einlaufen. Dort wird der Thread den Monitor auf das response-Objekt bekommen. Nun läuft dein Thread in das read vom Socket und blockiert dort. Dabei wird er die Sperre aber nicht lösen. Dein main-Thread kann daher nicht aufwachen, weil das response-Objekt so lange noch gesperrt ist, bist dein Thread aus der readMessage heraus kommt. Deine synchronized-Blöcke sind zu groß. Du benötigst sie nur direkt um das wait und das notify. Das genügt.

HTH,
Martin


----------



## Milo (28. Aug 2013)

Hi mfernau,

vielen Dank für Deine Antwort. Wenn ich die synchronized verschiebe, wie Du sagst, funktioniert es wirklich.


```
new Thread() {
				public void run() {
					try {
						readMessage();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}.start();
			synchronized (this.response) {
				if (this.response == null || this.response.length() == 0)
					this.response.wait(2000);
				
				System.out.println("Antwort " +response);
			}
```

und

```
char[] buffer = new char[1024];
		int read = this.is.read(buffer, 0, buffer.length); // blockiert bis Nachricht empfangen
		this.response.append( new String(buffer, 0, read) );
		synchronized (response) {
			this.response.notify();
		 }
```

Wo genau mein Denkfehler liegt, konnte ich aber nicht nachvollziehen. ;-)

Wenn ich Deinen Ansatz nutze, wird das Programm nicht mehr beendet. In der main-Methode wird System.out.println("Ende!") noch ausgeführt aber das Programm danach nicht geschlossen. Warum nicht, wo liegt das Problem?

Gruß Micha


```
package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Timer;
import java.util.TimerTask;

public class SocketClient {

	private final String uri;
	private final int port;
	private Socket socket;
	private PrintWriter os;
	private BufferedReader is;
	private final static String AUTHENTIFIKATION = "GET_DATA";

	// [url]http://www.java-forum.org/java-basics-anfaenger-themen/154369-wait-notify-socket.html[/url]
	public SocketClient(String uri, int port) {
		this.uri  = uri;
		this.port = port;
	}
	
	public boolean open() {
		try {
			this.socket = new Socket(this.uri, this.port);
			this.os     = new PrintWriter(new OutputStreamWriter(this.socket.getOutputStream()));
			this.is     = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			return true;
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} 
			
		return false;
	}

	public void getData()  {
		String response = null;
		try {
			if (this.socket != null && this.socket.isConnected()) {
				this.sendMessage(AUTHENTIFIKATION);
				response = this.readMessage(2000);
			}
		} 
		catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("Antwort " +response);
	}

	public boolean close() {
		try {
			this.socket.close();
			this.os.close();
			this.is.close();
			return true;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	private void sendMessage(String message) {
		this.os.print(message);
		this.os.flush();
	}
	
	private String readMessage(long timeout) throws IOException {
		String line = null;
		Timer timer = new Timer();
		
		TimerTask unlockTask = null;
		if(timeout != 0) {
			unlockTask = new TimerTask() {
				@Override
				public void run() {
					close();
				}
			};
			timer.schedule(unlockTask, timeout);
		}
		
		char[] buffer = new char[1024];
		int read = this.is.read(buffer, 0, buffer.length); // blockiert bis Nachricht empfangen
		line = new String(buffer, 0, read);
			
		if(unlockTask != null) {
			unlockTask.cancel();
			timer.purge();
		}

		return line;
	}	
	
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {

		SocketClient socket = new SocketClient("127.0.0.1", 11111);
		socket.open();
		socket.getData();
		socket.close();

		System.out.println("ENDE!");
	}

}
```


----------



## mfernau (28. Aug 2013)

Hallo!



Milo hat gesagt.:


> Wenn ich Deinen Ansatz nutze, wird das Programm nicht mehr beendet. In der main-Methode wird System.out.println("Ende!") noch ausgeführt aber das Programm danach nicht geschlossen. Warum nicht, wo liegt das Problem?



Den Codeausschnitt den ich Dir mit dem Timer gepostet habe war nicht komplett. Du musst einen erstellten Timer noch canceln sonst läuft der mit dem Timer assoziiert Thread weiter.
Schau Dir das mit dem Timer hier mal an: Galileo Computing :: Java ist auch eine Insel – 14.7 Zeitgesteuerte Abläufe

Was konkret bei Dir fehlt ist ein 
	
	
	
	





```
timer.cancel();
```
 nachdem alles abgeschlossen ist.
Der Korrektheit würde ich den Timer auch nicht in der read-Methode erstellen, sondern im Konstruktor. Denn der Timer wird wiederverwendet, während die TimerTasks wegwerf-Objekte sind.

Schöne Grüße
Martin


----------



## Milo (28. Aug 2013)

Hi Martin,



mfernau hat gesagt.:


> Du musst einen erstellten Timer noch canceln sonst läuft der mit dem Timer assoziiert Thread weiter.



Perfekt! Vielen Dank für Deine extrem schnelle Hilfe!

Schöne Grüße
Micha


----------

