2012-07-03 8 views
6

Ich implementiere einen einfachen Dateiserver mit Java NIO mit einem auswählenden Thread und mehreren Worker-Threads (um echtes Lesen/Schreiben durchzuführen).Java NIO SocketChannel.read() mit Multithread

Der Hauptteil des Codes sieht wie folgt aus:

while (true) { 
    int num = selector.select(); 
    if (num > 0) { 
     Iterator<SelectionKey> keys = selector.selectedKeys().iterator(); 
     final SelectionKey key = keys.next(); 
     keys.remove(); 

     if (key.isValid()) { 
      if (key.isAcceptable()) { 
       accept(key); 
      } else if (key.isReadable()) { 
       performReadInWorkerThread (key); 
      } else if (key.isWritable()) { 
       performWriteInWorkerThread (key); 
      } 
     } 
    } 
} 

Wie man aus dem Code-Schnipsel sehen kann, wenn ein lesbarer/beschreibbares Kanal ausgewählt wird, abzuladen I die Lese-/Schreib von der Auswahl Gewinden zu einem Worker-Thread.

Jetzt ist das Problem, dass, wenn ein lesbarer Kanal an den Arbeiterthread übergeben wird, und bevor es aus dem Kanal liest/beginnt, der Auswahlfaden wiederholt wird und selector.select() den zuvor ausgewählten lesbaren Kanal auswählt (weil es immer noch gibt Eingabepuffer in dem Kanal, der noch nicht vollständig von dem zuvor zugewiesenen Worker-Thread belegt wurde), so dass der Kanal erneut an einen anderen Worker-Thread übergeben wird, was zu mehreren Worker-Threads führt, die den gleichen Kanal lesen.

Ich glaube, das ist ein Design-Problem. Meine Frage ist, wie kann ich sicherstellen, dass nur ein Thread gleichzeitig einen Kanal liest?

+0

refer ma für den Leseabschnitt von ma Antwort, die Ihnen helfen kann – Amith

+0

Wenn Sie einen einfachen Dateiserver wollen, würde ich Blockierung NIO oder Blockierung IO verwenden. IMHO Dies ist viel einfacher als mit Selektoren herumzualbern. –

Antwort

6

Warum? Der Lesevorgang wird nicht blockiert. Mach es im aktuellen Thread. Sie sind einfach für endlose Probleme auf diese Weise. Sie müssen OP_READ vor der Übergabe an den Lesethread abmelden, was einfach ist, aber der schwierige Teil ist, dass wenn der Lese-Thread den Lesevorgang beendet, er OP_READ erneut registrieren muss, was entweder (i) einen Selektor erfordert wakeup(), was bewirkt, dass der Auswahl-Thread ausgeführt wird, wenn möglicherweise nichts zu tun ist, was verschwenderisch ist, oder (ii) eine Warteschlange ausstehender Reregistrierungen verwendet, die den nächsten Lesevorgang auf diesem Kanal bis nach dem nächsten Mal verzögert wacht auf, was auch verschwenderisch ist, oder Sie müssen den Selektor sofort beim Hinzufügen zur Warteschlange aktivieren, was auch verschwenderisch ist, wenn nichts bereit ist. Ich habe nie eine überzeugende NIO-Architektur gesehen, die verschiedene Auswahl- und Lese-Threads verwendet hat.

Tun Sie dies nicht. Wenn Sie über Multithreading verfügen müssen, ordnen Sie Ihre Channels in Gruppen an, die jeweils über einen eigenen Selektor und einen eigenen Thread verfügen, und alle diese Threads führen ihre eigenen Lesevorgänge durch.

In ähnlicher Weise müssen Sie nicht in einen separaten Thread schreiben. Schreib einfach, wenn du etwas zu schreiben hast.

+0

Vielen Dank für die Erklärung. Du meinst also ich brauche nur einen Thread?Was, wenn ich sehr große Dateien gleichzeitig lesen und schreiben muss, sagen wir, dass es Zehntausende von Clients gibt, die große Dateien anfordern, wie bedient mein Server diese Anfragen gleichzeitig mit einem einzigen Thread? – neevek

+0

Neevek, ich denke, EJP offenbarte nur Leseverfahren, und seine Antwort scheint sehr vernünftig. Sie lesen in einem einzelnen Thread, wechseln dann jedoch zum Worker-Thread, um Anforderungsdaten zu analysieren. Umgang mit großen Dateien - besser, um zu nginx zu wechseln, wenn diese Dateien statisch sind, anstatt Java dafür zu machen, aber man könnte nach Zerocopy-Sachen suchen: http://www.ibm.com/developerworks/library/j-zerocopy/ – user486075

+0

@Neevek EJP ist rit. Wenn Ihre Verbindung akzeptiert wird, muss jeder Client als separater Thread agieren. – Amith

-1

Überprüfen Sie die SelectionKey.cancel() Methode.

+0

Ich brauche 'Keep-Alive'-Verbindung, ich werde den geöffneten Socket benutzen, um mehrere Anfragen zu bedienen, also sollte ich den SelectionKey nicht abbrechen, ich muss immer noch' aus dem Kanal in der Zukunft lesen'. – neevek

+0

Es würde das Problem nur woanders verschieben: siehe meine Antwort. – EJP

1

Für NIO, nur ein Prinzip im Kopf behalten: Lesen/schreiben in der Hauptauswahl Thread. Dieses Prinzip spiegelt die Hardware-Natur wider. Mach dir keine Sorgen, dass das Lesen in Hauptauswahl Thread nicht schnell ist. Im modernen Server ist die CPU immer schneller als die Netzwerkkarte. So ist das blockierungsfreie Lesen in einem Thread auch schneller als das Arbeiten mit Netzwerkkarten. Ein Thread ist bereits genug, um ein Paket zu lesen. Wir brauchen keine weiteren Threads.