# Java NIO eingehende und ausgehende Verbindungen mit einem Selector



## Nightmares (6. Nov 2011)

Hallo,

ich habe bereits einiges an Erfahrung mit NIO und weiß was ich tue. Ich habe bereits eine Lauffähige Version etc jetzt möchte ich aber Eigehende Verbindungen und Ausgehende Verbindungen mit einem Selector realisieren. Die unterscheidung dieser findet mit zwei verschiedenen Objekten die ich ConnectionKey (als wrapper für SelectionKey mit buffern, nachrichten schlangen, etc) nenne stat. Das is auch nicht das Problem. Jedoch schaffe ich es nicht eine ausgehende Verbindung mit einem Selector zu schaffen...

Dazu jetzt erstmal die Frage:
Kann ein Selector überhaupt als "Server" und als "Client" gleichzeitig agieren? Ich denke schon bin da aber nicht ganz sicher. Dazu ist ja die Unterscheidung connectable (client seite) & acceptable (server seite) da. Mein Ansatz war jetzt einfach mit folgender Methode eine Verbindung auf zu bauen.
Wenn ich dies tue bleibt der Key aber bei dem Selector des Zielservers auf connectable stehen


```
protected void connectTo(InetSocketAddress inetSocketAddress) throws IOException {
		SocketChannel socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		socketChannel.connect(inetSocketAddress);
		socketChannel.register(this.selector, SelectionKey.OP_CONNECT);
	}
```


```
private void onKeyIsAcceptable() {
		System.out.println("acceptable...");
		// acceptable key; accepting and adding new SocketChannel to
		// register for READ only;
		this.log("Verbose",
				" Acceptable key, accepting and adding new SocketChannel to selector for read only and registering in map",
				null);
		try {
			ServerSocketChannel ssc = (ServerSocketChannel) this.selectionKey.channel();
			SocketChannel sc = ssc.accept();
			
// SIEHE NOTIZ XXX
			if (sc == null) {
				System.out.println("sc null");
				System.out.println(this.selectionKey.readyOps());
				return;
			}
			
			sc.configureBlocking(false);
			sc.socket().setTcpNoDelay(true);
			System.out.println(this.selectionKey.readyOps());

		} catch (IOException e) {
			e.printStackTrace();
			this.log("Verbose", "Acceptable key generated IOException, cancelling... " + e.getMessage(), e);
			this.connectionKey.cancel();
		}
	}
	
	private void onKeyIsConnectable() {
		// attempting outgoing connection...
		System.out.println("connectable...");
		// flag this key as an outgoing connection since we initiate the connection
		this.connectionKey.setOutgoingConnection(true);
		
		try {
			SocketChannel sc = ((SocketChannel) this.selectionKey.channel());
			if (sc.finishConnect()) {
				System.out.println("connectednow");
			}
			
			this.selectionKey.interestOps(SelectionKey.OP_READ);
			
		} catch (IOException e) {
			this.log("Verbose",
					"Connectable key is cancelled or corrupted, cancelling. " + e.getMessage() + e.getStackTrace(),
					e);
			e.printStackTrace();
			this.connectionKey.cancel();
		} catch (NoConnectionPendingException e) {
			this.log("Verbose",
					"Connectable key is cancelled or corrupted, cancelling. There was no connection pending."
							+ e.getMessage() + e.getStackTrace(), e);
			this.connectionKey.cancel();
			e.printStackTrace();
		}
	}
```

NOTIZXXX: Hier findet nun folgendes Phänomen statt: beim ersten durchlauf Funktioniert alles, allerdings kann ich den Key nicht auf read oder write umflaggen, weil er laut readyOps nur acceptable ist obwohl ich accept() aufrufe und diese methode einen socketChannel zurück liefert.
Beim zweiten durchgang dann jedoch passiert etwas komisches: Der gleiche key ist immer noch acceptable jedoch gibt die accept() methode nichts mehr (null) zurück. Und der Key ist dann für immer in diesem Zustand und die readyOps veränder sich auch nicht (immer noch 16, OP_ACCEPT).

Irgentwie steh ich hier total auf dem Schlauch oder was ich versuche geht einfach nicht.


----------



## Nightmares (8. Nov 2011)

Okay das ganze Ding ist "aufgegeben".

Es ist nach weiterer Forschung und VIELEN VIELEN tests mir zwar bedingt gelungen (komischerweiße nur unter Linux, unter Windows krieg ichs nicht zum laufen, keine Ahnung warum, theoretisch müsste alles gehen).

Stat dessen habe ich jetzt einfach 2 Selectoren die undabhängig von einander arbeiten. Diese werdem vom selben Thread nacheinander abgearbeitet und alle gewählten Keys an einen ThreadPoolExecutor übergeben. Dadurch entsteht nahezu kein Performence verlust und keine großen nebenwirkungen für den jeweils anderen Selector (alternierende select()'s). 

Die Verzögerung liegt bei 100 Verbindungen bei wenigen Millisekunden. Diese delegieren ihre arbeit an die selben ThreadPoolExecutor damit ist fairness zwischen den Selectoren gewährt und die Programmierung deutlich vereinfacht / effizienter da unnütze Threads gespart werden und auch Speicher und die Ressourcen geteilt werden können.

Für interesse am Source Code bitte melden. Den gibts natürlich nicht vollständig, sondern nur den betreffenden Ausschnitt und hinweiße auf was geachtet werden muss.


----------



## Domi F (11. Nov 2011)

Ein solches Problem hatte ich noch nie, ich verwende nur einen Selector für alle SocketChannels.

Was mich aber wundert bei deiner Beschreibung, dass du versuchst den SelectionKey von Accept auf Read zu setzen. Du musst den neuen SocketChannel mit Read registrieren (register). Den mit Accept kannst so lassen, wenn du weitere Verbindungen erwartest, oder sonst halt entfernen.

Hast du den SelectionKey der auf Accept reagiert hat aus dem Set (selector.selectedKeys()) entfernt? Wenn ich das nicht tue, habe ich auch das Verhalten, dass accept plötzlich null zurück gibt.


----------



## Nightmares (11. Nov 2011)

Hallo,

ja da habe ich mich unklar ausgedrückt. Natürlich muss der SocketChannel der mittels serverSocketChannel.accept() angenommen wird mit dem Selector per .register(selector,ops) registriert werden. Außerdem gibt es hier zu noch ein Update: Es ist mit jetzt doch gelungen. So wie es aussieht war das ganze eher ein Problem meines Resource Caches (ich Cache alles so gut wie möglich um Objekt Erstellungen zu minimieren). Hatte mich bei meiner Klasse, die die Änderungen Speichert bis diese vom Selector Thread vor dem select() ausgeführt werden etwas vertan. Die Objekte wurden bereits wieder dem Pool hinzu gefügt bevor diese fertig waren. (Einfach ein Zeilendreher was die 2 betreffenden Methoden angeht...).

Zu deiner Frage wegen den .selectedKeys(), ich habe und werde dieses nie modifizieren. Das ist auch gar nicht nötig oder vor gesehen.

Das Teil läuft jetzt wie geschmiert. 10k Verbindungen, die je zwischen 4 Nachrichten pro Sekunde schicken und empfangen: nahe zu 0% CPU last auf meinem i7 980 mit 6x 3,3ghz. 

Wenn es nichts zu tun gibt ist das ganze Ding praktisch IDLE, Selector bleibt einfach bei select() bis was zu tun ist. Das ganze Dinge läuft auch noch nach 12 Stunden (atm hab ich nen Test Client mit 10k Verbindungen zu einem Test Server die sich Gegenseitig ints schicken, diese hoch bzw runter zählen und zurück schicken) stabil ohne Speicher Probleme (Verbrauch ist nicht größer geworden).

Was noch fehlt ist das meine Load Balance etwas fairer wird. Ist zwar auf lange Zeit (mehrere Sekunden und mehr) absolut fair aber zwischendurch wenn immer wieder "Datenstöße" ankommen nicht direkt fair, hier dauert es etwas bis der Server das erkennt ebenso bei hohen Latenzen muss ich noch gucken wie ich da richtige fairness erreiche. Hab zwar paar Ansätze um das zu verbessern aber noch nichts konkretes.


----------



## Kr0e (11. Nov 2011)

Ab JDK 7 gibts Asynchrone-Channels, da kannste dir den ganzen Kram mit Selektors sparen. Die interne Impl. ist dann genauso wie beim Selektorprinzip je nach OS optimiert und handled verdammt viele Verbindungen. Die Benutzung ist seeehr viel einfacher... Ich würde mich heutzutage nicht mehr mit Selektors rumärgern. Das Prinzip ist sowieso überholt...


----------



## Kr0e (12. Nov 2011)

Bin ich grad drüber gestolpert:

An NIO.2 primer, Part 1: The asynchronous channel APIs


----------



## Nightmares (12. Nov 2011)

Das hat genau so Vorteile wie Nachteile...


----------



## Kr0e (12. Nov 2011)

Welche Nachteile ? Mir fiele nur einer ein: Man muss Java7 benutzen...


Um das nochmal klar zu formulieren: AIO macht unter der Haube etwas ähnlcihes wie der Selektor... Nur eben viel einfacher benutzbar als Nio1.4. Es ändert sich nur die Fassade! Man könnte z.b. beim Selektorprinzip denken, dass die Socketfunktion "select" benutzt wird, das trifft aber auch nicht überall zu. Unter Linux wird epoll und unter Windows die WSASelect Funktion benutzt, welche wiederum auf den CompletionPorts basiert. Selektors verkomplizieren einfach nur das Ganze, vorallem weil Selektors eigentlich als API nicht geeignet ist, denn wie schon erwähnt, haben unterschiedliche OS unterschiedliche Konzepte und da ist ein AsynchronousChannel einfach besser geeignet als Abstraktion..


----------



## Lumaraf (13. Nov 2011)

Wenn man vor hat häufig Daten von einer Datei in ein Socket oder umgekehrt zu übertragen ist NIO etwas schneller durch Methoden wie z.b. FileChannel#transferTo(long position, long count, WritableByteChannel target). Insofern man nicht auf die etwas bessere Performance in dem speziellen Fall angewiesen ist und Java 7 verwenden darf ist AIO eigentlich fast immer zu bevorzugen.


----------



## Nightmares (18. Nov 2011)

Was mich an NIO2 irgendwie stört nachdem ich mich jetzt eine Weile damit beschäftigt habe ist, dass das ganze relativ unübersichtlich geworden ist. Die ganze Sache mit den Future/CompletionHandler ist mehr als kompliziert. Dagegen sieht NIO richtig "freundlich" aus und ist leichter zu benutzen. Da verzichte ich lieber erstmal auf den kleinen Geschwindigkeits Vorteil. Wenn ich wirklich Performance brauche schreib ich den Server halt in C.


----------



## Kr0e (18. Nov 2011)

Lustig, ich finde es genau umgekerht! Aber ok, jedem das Seine. Ich persönlich mag das asynchrone Konzept und ich komme viel besser damit klar, als mit reinem NIO. Wenn ich was mit NIO mache, würde ich sowieso ein Framework nehmen, wie Netty,Mina etc... Aber vlt liegts auch daran, dass ihc mich shcon lange mit Event-driven/Concurrency beschäftige, wodurch das Konzept mir sehr vertraut ist.,..

SChönes Wochenende


----------



## Nightmares (21. Nov 2011)

NIO ist genau so Event-Driven wie NIO2. Und das Prinzip mit dem Selector ist viel übersichtlicher: Es wird dir gezeigt wofür dein Key gerade zugelassen ist, was er möchte bzw. man meldet ein Interesse zu schreiben. Dann hat man seine Warteschlange für Eingehende & Ausgehende Daten. Und für NIO habe ich selber ein "Framework" geschrieben was übrigens gleiche Performence wie Netty erreicht mit meinem eigenen Protokoll, dass jeden Java Primitiven Datentyp übertragen kann sowie deren Arrays mit nahezu keinem Overhead, wahlweise Kompression und gerechte Verteilung der Bandbreite mit verschiedenen Einstellmöglichkeiten

EDIT:
Was mir bei NIO2 irgendwie fehlt / mich stört ist das Future Konzept. Ich finde es unnötig, dass ich diese Objekte dann auch noch habe und überprüfen muss. Die Alternative sind die CompletionHandler, die einem mehr Möglichkeiten geben. Was ein Fortschritt ist, ist z.B. das Attachment, das jetzt nicht mehr nur ein Object ist das man zurecht casten muss sondern Generalisiert werden kann. Was mir aber fehlt ist, das ich keine Liste der "arbeiten" kriege was mich effektiv um einen Teil meiner Möglichkeiten zur Gerechten Verteilung der Leistung bringt.


----------



## Kr0e (21. Nov 2011)

Also ich verstehe deine Kritik nicht wirklich. Hilft dir folgender Screencast um meine Begeisterung an NIO2 zu verstehen:

Parleys.com - The Next Generation E-Learning Platform

NIO2 ist außerdem schneller/performanter und zwar ganz einfach deshalb, weil hin und wieder mehrere Schreib/lesevorgänge innerhalb eines Threadcontexts geschehen und du dir damit etwas scheduling sparst. (Am Ende des Screencast siehst du was ich meine)

Ich persönlich empfinde das Selektorprinzip ätzend und nichts für ungut für dein "eigenes" NioFramework. Aber ich finde die Aussage "gleich wie NEtty" etwas krass. 1. glaube ich nicht , dass du umfassende Tests gemacht hast, die nötig wären, für eine quantifizierbare Aussage wie diese. Und 2. Liegt Netty's Stärke vorallem im guten Threading Modell. DAs ist überhaupt das Wichtigste an allen NioFrameworks. Scheduling kann einem sehr viel kaputt machen, aber wenn jeder User einen dedizierten Thread hat (Ein konstanter Thread bedient mehrere User), dann leidet die Flexibilität. Sprich du müsstest mehrere Tausend Verbindungen simulieren mit unteschiedlichen Anforderungen und Requestgrößen mit unterschiedlichen Latenzen. NEtty ist in sofern schon arg optimiert. Sprich wenn dein Framework mit 100 Clients gut klappt, dann heißt das noch überhaupt nichts. 100 Cleints wären mit Threads übrigens besser bedient -> IO.

- Übrigens einer der Gründe warum AIO so genial ist: Aufgrund des gegebenen Threadpools und des internen Modells muss man als Programmierer endlich nicht mehr Frameworks benutzen, weil es von Hand zu aufwendig wäre. Warum gibt es wohl sooo viele Frameworks für NIO ? Genau, weil es von Hand einfach zu aufwendig ist. NIO ist ein wirklcih unübersichtliches API und orientiert sich noch am alten Berkley select() was aber überholt ist. -

Du redest von einer "Liste der Arbeiten", das verstehe ich nicht. Bei AIO kann immer nur ein Befehl gleichzeitig abgearbeitet werden, sonst fliegen eh Read/WRitePending Exceptions. Für solche Listen ist der PRogrammierer zuständig und es geht uach garnicht anders, gerade für WriteQueues...

AIO hat ein gutes Konzept, ist viel leichter zu verwenden (Threading ist ja schon abgedeckt, im Gegensatz zu NIO) und bingt auch noch ein paar Prozent mehr Leistung. Absolut kein Grund nicht zu wechseln. Allerhöchetns der, dass AIO Java 7 ist und alles nocht recht neu. Warte mal ein paar Monate ab, dann wird sich das durchsetzen... War ja bei NIO auch so.

Und der Punkt, dass es kaum Tuts gibt, ist hinterlich, gerade weil noch sowas wie "Best-Practices" fehlt, aber das merkt man selbst, wenn man ein wenig damit rumspielt und iwann hat man das Prinzip verstanden...


----------

