Das folgende System liefert eine eingehende Einführung in den Funktionsmechanismus und die Verwendung von Java Nio durch Prinzipien, Prozesse usw. Lassen Sie es uns lernen.
Vorwort
Dieser Artikel erklärt hauptsächlich den IO -Mechanismus in Java
In zwei Teile unterteilt:
Der erste Teil erklärt den IO -Mechanismus unter Multithreading. Der zweite Teil erklärt, wie die Verschwendung von CPU -Ressourcen im Rahmen des IO -Mechanismus (neuer IO) optimiert werden kann.
Echo -Server
Ich muss den Sockelmechanismus nicht unter einem einzigen Faden einführen. Wenn Sie es nicht wissen, können Sie die Informationen unter so vielen Themen überprüfen. Was ist, wenn Sie Sockets verwenden?
Wir verwenden den einfachsten Echo -Server, um jedem zu helfen, das zu verstehen
Schauen wir uns zunächst das Workflow-Diagramm des Servers und des Clients unter Multi-Threading an:
Sie können sehen, dass mehrere Clients gleichzeitig Anforderungen an den Server senden
Der Server hat eine Maßnahme erstellt, mit der mehrere Threads mit dem entsprechenden Client übereinstimmen können.
Und jeder Thread vervollständigt seine Kundenanfragen allein
Lassen Sie uns nach Abschluss des Prinzips sehen, wie es implementiert wird
Hier habe ich einen einfachen Server geschrieben
Verwenden Sie die Thread Pool -Technologie, um Threads zu erstellen (ich habe die spezifische Codefunktion kommentiert):
public class myserver {private static ExecutorService Executorservice = Executors.NewCachedThreadpool (); // Erstellen Sie einen Thread Pool Private Static Class Handlemsg implementiert Runnable {// Sobald es eine neue Client -Anforderung gibt, erstellen Sie diesen Thread, um den Socket -Client zu verarbeiten. // Erstellen Sie einen Client public Handlemsg (Socket Client) {// Konstruieren Sie eine Parameterbindung this.client = client; } @Override public void run () {bufferedReader bufferedReader = null; // Erstellen Sie einen Charakter -Cache -Eingangsstrom -Pressewriter Printwriter = NULL; // Charakter -Schreibstream erstellen {bufferedReader = new bufferedReader (neuer InputStreamReader (client.getInputStream ())); // Erhalten Sie den Eingabestream des Clients Printwriter = New Printwriter (client.getOutputStream (), true); // Erhalten Sie den Ausgabestream des Clients, true, um String inputline = null zu aktualisieren. lang a = system.currentTimemillis (); while ((inputline = bufferedReader.readline ())! = null) {printwriter.println (inputline); } long b = system.currentTimemillis (); System.out.println ("Dieser Thread hat:"+(ba)+"Sekunden!"); } catch (ioException e) {e.printstacktrace (); } endlich {try {bufferedReader.close (); printwriter.close (); client.close (); } catch (ioException e) {e.printstacktrace (); }}}} public static void main (String [] args) löst IOException {// Der Haupt -Thread des Servers wird verwendet, um Client -Anforderungen in einem Loop -ServerSocket Server = New ServerSocket (8686) anzuhören. // Erstellen Sie einen Server mit Port 8686 Socket Client = null; while (true) {// Loop listen client = server.accept (); // Der Server hört auf ein Client -Anforderungssystem.out.println (client.getRemotesocketadDress ()+"Die Client -Verbindung ist erfolgreich!"); ExecutorService.Submit (neue Handlemsg (Client)); // Die Client -Anforderung zur Verarbeitung in den Handlmsg -Thread über den Thread -Pool einfügen}}} über den Thread -PoolIm obigen Code schreiben wir eine Klasse, um einen einfachen Echo -Server zu schreiben, um das Port -Hören im Haupt -Thread mithilfe einer toten Schleife zu aktivieren.
Einfacher Kunde
Mit einem Server können wir darauf zugreifen und einige String -Daten senden. Die Funktion des Servers besteht darin, diese Zeichenfolgen zurückzugeben und den Thread auszudrucken.
Schreiben wir einen einfachen Client, um auf den Server zu antworten:
public class myclient {public static void main (String [] args) löst IOException {Socket Client = null aus; Printwriter Printwriter = NULL; BufferedReader bufferedReader = null; try {client = new Socket (); Client.Connect (New Inetsocketaddress ("Localhost", 8686)); printwriter = neuer printwriter (client.getOutputStream (), true); printwriter.println ("Hallo"); printwriter.flush (); bufferedReader = new bufferedReader (neuer InputStreamReader (client.getInputStream ())); // Lesen Sie die vom Server- und Ausgabesystem zurückgegebenen Informationen. } catch (ioException e) {e.printstacktrace (); } endlich {printwriter.close (); bufferedReader.close (); client.close (); }}}Im Code verwenden wir den Zeichenstrom, um eine Hallo -Zeichenfolge zu senden. Wenn der Code in Ordnung ist, gibt der Server eine Hello -Daten zurück und druckt die von uns festgelegten Protokollinformationen aus.
Echo -Server -Ergebnisse Anzeige
Lass uns rennen:
1. Öffnen Sie den Server und aktivieren Sie das Loop -Hören:
2. Öffnen Sie einen Kunden:
Sie können sehen, dass der Client das Rückgabeergebnis druckt
3. Überprüfen Sie das Serverprotokoll:
Sehr gut, eine einfache Multithread -Socket -Programmierung wird implementiert
Aber denken Sie darüber nach:
Wenn ein Client anfordert, fügen Sie während des Schreibens des IO zum Server Schlaf hinzu.
Lassen Sie jede Anfrage 10 Sekunden lang den Server -Thread belegen
Dann gibt es viele Kundenanfragen, die jeweils so lange aufnehmen
Dann werden die Vereinigung des Servers stark reduziert
Dies liegt nicht daran, dass der Server viele schwere Aufgaben hat, sondern weil der Service -Thread auf IO wartet (weil sie akzeptieren, lesen und schreiben, blockieren alle)
Es ist sehr unbezahlt, Hochgeschwindigkeits-CPUs auf ihr ineffizientes Netzwerk zu warten.
Was soll ich zu diesem Zeitpunkt tun?
Nio
New IO löste das obige Problem erfolgreich. Wie hat es es gelöst?
Die Mindesteinheit für IO zur Verarbeitung von Client -Anfragen ist Thread
NIO verwendet ein Gerät, das eine Ebene ist, die kleiner als ein Thread ist: Kanal (Kanal)
Es kann gesagt werden, dass in NIO nur ein Thread benötigt wird, um alle Empfang, Lesen, Schreiben und andere Operationen abzuschließen.
Um NIO zu lernen, müssen Sie zuerst seine drei Kernpunkte verstehen
Selektor, Selektor
Puffer, Puffer
Kanal, Kanal
Der Blogger ist nicht talentiert, also zog er ein hässliches Bild, um seinen Eindruck zu vertiefen. ^
Geben Sie mir ein weiteres NIO -Workflow -Diagramm unter TCP (sehr schwierig zu zeichnen Linien ...)
Verstehe es einfach grob, lass uns Schritt für Schritt gehen
Puffer
Zunächst müssen Sie wissen, was ein Puffer ist
Dateninteraktion verwendet keine Streams wie IO -Mechanismen mehr
Verwenden Sie stattdessen einen Puffer (Puffer)
Der Blogger ist der Meinung, dass Bilder am einfachsten zu verstehen sind
Also...
Sie können sehen, wo sich der Puffer im gesamten Workflow befindet
Werfen wir einen Blick auf die tatsächlichen. Der spezifische Code in der obigen Abbildung lautet wie folgt:
1. Zuerst den Puffer in Bytes Platz zuweisen
Bytebuffer bytebuffer = bytebuffer.alcode (1024);
Erstellen Sie ein ByteBuffer -Objekt und geben Sie die Speichergröße an
2. Schreiben Sie Daten in den Puffer:
1). Daten von Kanal zu Buffer: Channel.read (ByteBuffer); 2). Daten von Client zu Puffer: bytebuffer.put (...);
3.. Lesen Sie Daten aus dem Puffer:
1). Daten von Puffer zu Kanal: Channel.Write (ByteBuffer); 2). Daten vom Puffer zum Server: bytebuffer.get (...);
Wähler
Der Selektor ist der Kern von NIO, es ist der Administrator des Kanals
Durch Ausführen der Select () -Blockungsmethode hören Sie zu, ob der Kanal fertig ist
Sobald die Daten lesbar sind, ist der Rückgabewert dieser Methode die Anzahl der SelectionKeys
Daher führt der Server normalerweise die Select () -Totschleife aus, bis der Kanal fertig ist und dann beginnt zu arbeiten
Jeder Kanal bindet ein Ereignis an den Selektor und generiert dann ein SelectionKey -Objekt.
Was beachtet werden sollte ist:
Wenn der Kanal und der Selektor gebunden sind, muss sich der Kanal im nicht blockierenden Modus befinden.
Filechannel kann nicht in den Nicht-Blocking-Modus umsteigen
In NIO gibt es vier Arten von Ereignissen:
1.SelectionKey.op_connect: Verbindungsereignis
2.SelectionKey.op_accept: Ereignisse erhalten
3.SelectionKey.op_read: Ereignisse lesen
4.SelectionKey.op_write: Schreiben Sie Ereignisse
Kanal
Insgesamt gibt es vier Kanäle:
Filechannel: Aktiert auf IO -Dateistream
Datagramchannel: Es funktioniert im UDP -Protokoll
Socketchannel: Es funktioniert im TCP -Protokoll
ServerSocketchannel: Es wirkt auf das TCP -Protokoll
Dieser Artikel erklärt NIO durch das häufig verwendete TCP -Protokoll
Nehmen wir ServerSocketchannel als Beispiel:
Öffnen Sie einen ServerSocketchannel -Kanal
ServerSocketchannel ServerSocketchannel = ServerSocketchannel.open ();
Schließen Sie den ServerSocketchannel -Kanal:
ServerSocketchannel.close ();
Looping Socketchannel:
while (wahr) {Socketchannel socketchannel = serversSocketchannel.accept (); ClientChannel.ConfigureBlocking (Falsch);} clientChannel.configureBlocking(false); Aussage besteht darin, diesen Kanal auf Nicht blockierende zu setzen, dh eine asynchrone freie Kontrolle der Blockierung oder Nicht-Blockierung ist eine der Eigenschaften von NIO
SelectionKey
SelectionKey ist die Kernkomponente der Interaktion zwischen Kanälen und Selektoren
Binden Sie beispielsweise einen Selektor am Socketchannel und registrieren Sie ihn als Verbindungsereignis:
Socketchannel clientChannel = socketchannel.open (); ClientChannel.ConfigureBlocking (false); ClientChannel.Connect (New InetSocketaddress (Port)); ClientChannel.register (Selector, SelectionKey.op_connect);
Der Kern befindet sich in der Register () -Methode, die ein SelectionKey -Objekt zurückgibt
Um Kanalereignisse zu erkennen, können Sie die folgende Methode verwenden:
SelectionKey.isacceptable (); SelectionKey.isconnectable (); SelectionKey.isreadable (); SelectionKey.IsWrible ();
Der Server führt entsprechende Vorgänge bei der Umfrage durch diese Methoden aus
Natürlich können die an den Kanal und den Selektor gebundenen Schlüssel wiederum erhalten werden.
Channel Channel = SelectionKey.Channel (); Selector Selector = SelectionKey.Selector ();
Bei der Registrierung von Ereignissen auf dem Kanal können wir auch einen Puffer binden:
ClientChannel.register (Key.Selector (), SelectionKey.op_read, bytebuffer.allocatedirect (1024));
Oder ein Objekt binden:
SelectionKey.attach (Objekt); Objekt Anthorobj = SelectionKey.Attachment ();
Der TCP -Server von NIO
Nachdem wir so viel gesprochen haben, werden wir uns den einfachsten und Kerncode ansehen (es ist nicht elegant, so viele Kommentare hinzuzufügen, aber es ist für alle für alle zu verstehen):
Paket cn.blog.test.niotest; import java.io.ioxception; import Java.net.inetsocketaddress; Import Java.nio.Bytebuffer; Import Java.nio.Channels importieren java.nio.charset; // Erstellen Sie einen Selektor Private Final Static Int Port = 8686; private endgültige statische int buf_size = 10240; private void InitServer () löscht IOException {// Channel Manager -Objekt -Selector this.selector = selector.open () erstellen; // Erstellen Sie einen Kanal -Objekt -Kanal -ServerSocketchannel Channel = ServerSocketchannel.open (); Channel.ConfigureBlocking (false); // Setzen Sie den Kanal auf nicht blockierende Kanal.socket (). Bind (New InetSocketaddress (Port)); // Binden Sie den Kanal an Port 8686 // Binden Sie den oben genannten Kanalmanager und den Kanal und registrieren Sie das OP_ACCEPT -Ereignis für den Kanal // nach der Registrierung des Ereignisses Selector.select () kehren zurück (ein Schlüssel). Wenn das Ereignis nicht selector.select () erreicht, blockiert es SelectionKey SelectionKey = Channel.register (Selector, SelectionKey.op_accept); while (true) {// Poll Selector.Select (); // Dies ist eine Blockierungsmethode, warten Sie, bis die Daten lesbar sind. Der Rückgabewert ist die Anzahl der Schlüssel (es kann mehrere) set keys = selector.selectedKeys (); // Wenn der Kanal Daten hat, greifen Sie auf die generierten Schlüssel in den Iterator der Schlüsselerfassung zu, Iterator = keys.iterator (); // Holen Sie sich den Iterator dieser Schlüsselsammlung während (iterator.hasnext ()) {// Verwenden Sie den Iterator, um die Sammlung SelectionKey Key = (SelectionKey) Iterator.Next () zu durchqueren; // Erhalten Sie eine Schlüsselinstanz in der Sammlung iterator.remove (); // Denken Sie daran, dieses Element im Iterator nach der aktuellen Schlüsselinstanz zu löschen, was sehr wichtig ist. Andernfalls tritt ein Fehler auf, wenn (key.isocceptable ()) {// beurteilen Sie beurteilen, ob der vom aktuelle Schlüssel dargestellte Kanal im akzeptablen Zustand und falls doaccept (Schlüssel) liegt; } else if (key.isreadable ()) {doread (key); } else if (key.iswrible () && key.isvalid ()) {dowrite (key); } else if (key.isconnectable ()) {System.out.println ("Connectable!"); }}}} public void doaccept (SelectionKey -Schlüssel) löst IOException {ServerSocketchannel serverchannel = (ServerSocketchannel) key.channel (); System.out.println ("ServerSocketchannel hört in einer Schleife"); Socketchannel clientChannel = serverchannel.accept (); ClientChannel.ConfigureBlocking (falsch); ClientChannel.register (Key.Selector (), SelectionKey.op_read); } public void doread (selectionKey key) löst ioException {Socketchannel clientChannel = (socketchannel) key.channel (); Bytebuffer bytebuffer = bytebuffer.Allecode (buf_size); long byteSread = clientChannel.read (byteBuffer); while (byteSread> 0) {bytebuffer.flip (); byte [] data = bytebuffer.Array (); String info = new String (Daten) .trim (); System.out.println ("Die vom Client gesendete Nachricht lautet:"+info); bytebuffer.clear (); byteSread = clientChannel.read (byteBuffer); } if (byteSread ==-1) {clientChannel.close (); }} public void dowrite (SelectionKey -Schlüssel) löst ioException {bytebuffer bytebuffer = bytebuffer.Allocate (buf_size) aus; bytebuffer.flip (); Socketchannel clientChannel = (socketchannel) key.channel (); while (bytebuffer.hasremaining ()) {ClientChannel.Write (bytebuffer); } bytebuffer.compact (); } public static void main (String [] args) löst ioException aus {mynioserver mynioserver = new mynioServer (); mynioserver.initServer (); }} Ich druckte den Monitor -Kanal und sagte Ihnen, wann die ServerSocketchannel ausgeführt wurde
Wenn Sie mit dem Debuggen des NIO -Kunden zusammenarbeiten, können Sie es deutlich finden. Vor der Eingabe der Umfrage aus select ()
Obwohl es bereits einen Schlüssel für das Akzeptierenereignis gibt, wird Select () nicht standardmäßig aufgerufen
Stattdessen müssen Sie warten, bis andere interessante Ereignisse von Select () erfasst werden, bevor Sie Accepts SelectionKey anrufen
Erst dann begann die ServerSocketchannel, um eine kreisförmige Überwachung durchzuführen
Mit anderen Worten, in einem Selektor wird das ServerSocketchannel immer beibehalten.
Und serverChannel.accept(); ist wirklich asynchron (Channel.ConfigureBlocking (Falsch); in der InitServer -Methode)
Wenn die Verbindung nicht akzeptiert wird, wird ein Null zurückgegeben
Wenn ein Socketchannel erfolgreich miteinander verbunden ist
Und asynchron einstellen
Der TCP -Client von NIO
Es muss einen Client geben, wenn ein Server vorhanden ist
In der Tat, wenn Sie den Server vollständig verstehen können
Der Code des Clients ist ähnlich
Paket cn.blog.test.niotest; import java.io.ioxception; import Java.net.inetsocketaddress; Import Java.nio.Bytebuffer; Import Java.nio.Channels.SselectionKey; Mynioclient {private selector selector; // Erstellen Sie einen Selektor Private Final Static Int Port = 8686; private endgültige statische int buf_size = 10240; private static bytebuffer bytebuffer = bytebuffer.alcode (buf_size); private void initclient () löscht ioException {this.selector = selector.open (); Socketchannel clientChannel = socketchannel.open (); ClientChannel.ConfigureBlocking (falsch); ClientChannel.Connect (New Inetsocketaddress (Port)); ClientChannel.register (Selector, SelectionKey.op_connect); while (true) {selector.select (); Iterator <SelectionKey> iterator = selector.selectedKeys (). Iterator (); while (iterator.hasnext ()) {SelectionKey Key = iterator.next (); iterator.remove (); if (key.isconnectable ()) {doconnect (key); } else if (key.isreadable ()) {doread (key); }}} public void doconnect (SelectionKey -Schlüssel) löst IOException {Socketchannel clientChannel = (socketchannel) key.channel () aus; if (ClientChannel.isconnectionPending ()) {ClientChannel.finishConnect (); } ClientChannel.ConfigureBlocking (false); String info = "Hallo Server !!!"; bytebuffer.clear (); bytebuffer.put (info.getBytes ("utf-8")); bytebuffer.flip (); ClientChannel.Write (ByteBuffer); //clientChannel.register(Key.Selector(), SelectionKey.op_read); ClientChannel.CLOSE (); } public void doread (selectionKey key) löst ioException {Socketchannel clientChannel = (socketchannel) key.channel (); ClientChannel.read (ByteBuffer); byte [] data = bytebuffer.Array (); String msg = new String (Daten) .trim (); System.out.println ("Server sendet eine Nachricht:"+msg); ClientChannel.CLOSE (); Key.Selector (). Close (); } public static void main (String [] args) löst ioException {mynioclient mynioclient = new mynioclient () aus; mynioclient.initclient (); }}Ausgangsergebnis
Hier öffne ich einen Server und zwei Clients:
Als nächstes können Sie versuchen, gleichzeitig tausend Kunden zu eröffnen. Solange Ihre CPU leistungsfähig genug ist, kann der Server die Leistung aufgrund von Blockade nicht reduzieren.
Das obige ist eine detaillierte Erklärung von Java Nio. Wenn Sie Fragen haben, können Sie sie im Nachrichtenbereich unten besprechen.