package javacodebook.net.socket.channelserver;

import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
/**
 * @author benjamin_rusch
 * Dieser kleine "Datums-Server" empfängt auf Port 5000 über TCP/IP gesendete 
 * Nachrichten. Wird "date" gesendet, übermittelt der Server  dem Sender das 
 * Datum, bei "help" wird eine Kurzbeschreibung der verfügbaren Befehle über-
 * tragen, und bei "end" wird die Verbindung unterbrochen. Bei allen anderen
 * Eingaben wird eine Fehlermeldung zurückgeschickt.
 * Dadurch, das die verwendeten Channel "non-blocking" sind, können mehrere 
 * Anfragen parallel beantwortet werden.
 */
public class ChannelServer {
	private static Charset charset = Charset.forName("ISO-8859-1");
	private static CharsetEncoder encoder = charset.newEncoder();
	private static CharsetDecoder decoder = charset.newDecoder();

	private static String command=null;
	private static String response =null;
	
	
	public static void main(String[] args) throws Exception {
			
		// Dieser ByteBuffer wird später zum lesen der Clientanfragen benötigt.
		ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        
        // Ein Selector wird erstellt. Ein Selector kann von beliebig vielen 
        // Channels bei unterschiedlichen Vorkommnissen informiert werden. 
        Selector selector = Selector.open();
    
        // Ein "non-blocking" ServerSocket wird am Port: 5000 angemeldet
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.configureBlocking(false);
        ssChannel.socket().bind(new InetSocketAddress(5000));
    
        // Der Selector wird am ersten Channel registriert. Über das 
        // angegebene Operation bit wird definiert, dass der Selector nur bei
        // einer neu Anmeldung eines Clients informiert wird.
		SelectionKey acceptKey=ssChannel.register(selector, 
				SelectionKey.OP_ACCEPT);  	
	
		// Der Server soll immer seinen Diest zur Verfügung stellen, daher
		// befindet sich der Code in einer Endlos-Schleife.
		while(true) {
			
			// Diese Methode blockt solange bis sich in den Channels, die 
			// diesen selector registriert haben, etwas ereignet hat. 
			selector.select();
			
			// Für den Fall das sich gleich in mehreren angemeldeten Channels 
			// etwas ereignet hat oder auf einem mehrere Anfragen reinkommen, 
			// liefert die Methode selectedKeys() gleich einen SET von keys 
			// zurück. Jeder key kapselt das Ereignis, und kann später nach 
			// diesem befragt werden. 
			Set keys = selector.selectedKeys();
			
			// Jeder key aus dem Set wird abgearbeitet.
			Iterator i = keys.iterator();
			while(i.hasNext()) {
				SelectionKey key = (SelectionKey) i.next();
				
				// Damit der key beim nächsten select nicht wieder auftaucht, 
				// muss er aus dem Set herausgenommen werden
				i.remove();
			 
				// Liefert der Key bei der Methode isAcceptable() true zurück, 
				// wissen wir, dass es sich um ein Ereignis vom 
				// ServerSocketChannel handelt, und ein neuer Client sich 
				// angemeldet hat. Folgender Block wird dann abgearbeitet. 
				if (key.isAcceptable()) {
				  	// Der SocketChannel vom Client wird erfragt.
				  	SocketChannel client = ssChannel.accept();
				  	
				  	// Dieser Channel soll auch "non-Blocking" sein, damit 
				  	// mehrere Client Anfragen bearbeitet werden können.
				  	client.configureBlocking(false);
				  	
				  	// Der Selector wird auch am SocketChannel registriert. 
				  	// Über das angegebene Operation bit, OP_READ, wird 
				  	// definiert, dass der Selector nur dann vom Channel 
				  	// informiert wird, wenn es was zu lesen gibt.
				  	client.register(selector, SelectionKey.OP_READ);
				  	
				}
				// Liefert der Key bei der Methode isReadable() true zurück, 
				// wissen wir, dass es sich um ein Ereignis von einem 
				// SocketChannel handelt, und ein bereits angemeldeter Client 
				// Daten geschickt hat, die nun zum Lesen bereit stehen . Folgender 
				// Block wird abgearbeitet. 
				else if (key.isReadable()) {
					
					// Eine Referenz auf den SocketChannel wird erfragt
					SocketChannel client = (SocketChannel) key.channel();
						
					// Die Client-Daten werden in einen Buffer geschrieben.
					// Falls der Client die Verbindung zwischenzeitlich 
					// unterbrochen hat, würde das Ende vom Stream durch einen 
					// Rückgabewert von -1 identifiziert werden. In diesem 
					// Fall wird der Channel geschlossen.
					try {
						int bytesread = client.read(buffer);
						if (bytesread == -1) {  
							key.cancel();
							client.close();
						}
					} 
					catch(Exception err) {
						err.printStackTrace();
					}
											
					// Client Eingabe befindet sich derzeit im Buffer, muss 
					// für die weitere Verarbeitung zum String umgewandelt 
					// werden. Der Buffer wird anschließend für spätere 
					// Verwendung wieder geleert.
					buffer.flip();
					CharBuffer charBuffer = decoder.decode(buffer);
					command= charBuffer.toString();
					command = command.trim();
					buffer.clear();
					
					// wird beim Befehl "date" ausgeführt
					if(command.equals("date")) {
						// Antwort wird als String zusammengesetzt zum 
						// ByteBuffer umgewandelt und zum Client geschickt. 
						response ="Das Datum im Java Format: "
								+ new java.util.Date()+"\n";
						client.write(encoder.encode(
								CharBuffer.wrap(response)));
					}
					// wird beim Befehl "help" ausgeführt
					else if(command.equals("help")) {
						response = "Befehle: \"date\" liefert Datum, "
								+"\"help\" fuer Hilfe,"
								+" \"end\" fuer Verbindungsende.\n";
						client.write(encoder.encode(
								CharBuffer.wrap(response)));
					}
					// wird beim Befehl "end" ausgeführt
					else if(command.equals("end")) {
						System.out.println("Verbindung zu einem Client wird "
								+"abgebrochen!");
						client.close();
					}
					// wird bei allen anderen Fällen ausgeführt.
					else {
						response = "Sorry, \""+command
								+"\" ist falscher Befehl!\n";
						client.write(encoder.encode(
								CharBuffer.wrap(response)));
					}
				}
			}
		}
	}
}

