int keysAdded = 0; while ((keysAdded = selector.select(WAIT_TIME)) > 0) { Set readyKeys = selector.selectedKeys(); Iterator i = readyKeys.iterator(); while (i.hasNext()) { SelectionKey key = (SelectionKey)i.next(); i.remove(); if (key.isReadable()) { ... } else if (key.isWritable()) { ... } } }
Når nu en SocketChannel er klar til at blive læst fra er det jo nemt, man læser blot til en ByteBuffer og gør hvad man ellers vil. Mit problem er, når nu en anden tråd i min server skal sende til en given klient. Hvordan gør jeg en SocketChannel "writable", for der skal jo først skrives med SocketChannel.write(ByteBuffer), når Selector'en siger at en SocketChannel er writable, eller hvordan foregår det? Håber problemet er forståeligt.
I lang tid har samarbejdsbranchen fokuseret på at forbedre enhedsfunktioner – bedre kameraer, klarere lyd og smartere software. Men den virkelige forvandling handler ikke om funktioner.
For det første: Når en SocketChannel kun skal bruges til at læse fra (og der således ikke p.t. er nogen data der skal skrives til SocketChannel'en) så skal den IKKE være registreret med OP_WRITE. Det vil nemlig gøre at dit while loop vil blive ved med at køre og derfor vil resultere i et CPU forbrug på 100%.
Mht. til at sende data i en anden tråd, så kan det ikke lade sig gøre direkte. Det fornuftigste ville nok være at implementere en metode i client klassen som varetager denne funktion ved at sætte data der skal sendes i "kø" (brug f.eks. en LinkedList). Husk at metoden i client klassen skal registrere SocketChannel'en til OP_WRITE. Selector tråden kan så hente data'ene fra client objektet og sende dem. Husk at Selector tråden skal registrere SocketChannel'en til OP_READ only hvis der ikke er flere data, da der ellers spildes en masse CPU cykler.
Og husk at bruge synchronized når din LinkedList med data tilgås af to forskellige tråde!
Det er også underligt, hvis ikke jeg angiver et timeout i select(), blokker den for evigt og registrere intet af at der er blevet tilfæjet nye SocketChannel's, som modtager en masse; en Selector.wakeup() får den heller ikke til at hoppe ud af select().
Hvordan finder Selector'en ud af, at der er noget i klientens data-kø der skal sendes?, eller det skal jeg selv vide ud fra hvad serveren lige har modtaget fra klienten?
Kan du uddybe: "Husk at metoden i client klassen skal registrere SocketChannel'en til OP_WRITE." ?
Vil det sige, at man så at sige ALDRIG skal registrere en SocketChannel med OP_WRITE? Jeg kan godt se at den går i seriøst busy-wait :)
Som du nok kan se er det her forholdsvis nyt for mig, og på nettet findes der langt flere spørgsmål en svar...
Det synes at hjælpe, hvis den samme selector lytter på både den ServerSocketChannel der modtager forbindelsene og alle de modtagede SocketChannel's. I så tilfælde reagerer den fint på modtagede data fra klienten (OP_READ). Burde man ikke kunne køre dem i forskellige Selector's, eller bare køre ServerSocketChannel i normal blocking-mode?
I dit while-loop skal du så have følgende: if(key.isWritable()) { Client c = (Client) key.attachment(); synchronized(c.linkedList) { if(c.linkedList.isEmpty()) { key.interestOps(SelectionKey.OP_READ); } else { byte[] data = c.linkedList.removeLast(); // kode til at sende data her } } }
Det er ikke færdig kode. Der skal bl.a. tages højde for at det måske ikke er muligt at sende hele data array'en på en gang.
Jo, det hjalp - men hvordan tager man så højde for at data array'et ikke kan sendes på én gang? Og er der andre specielle ting man skal tage højde for ?
Nu har jeg lavet en metode som kaldes ved "// kode til at sende data her":
private void processWrite(SelectionKey key) { try { Log.echoDebug("ClientWorker.processWrite"); key.interestOps(SelectionKey.OP_READ); SocketChannel sc = (SocketChannel)key.channel(); Client c = (Client)key.attachment(); QueueVector sendQueue = c.getSendQueue(); Message m; ByteBuffer buf; while (!sendQueue.isEmpty()) { m = (Message)sendQueue.remove(); buf = ByteBuffer.wrap(m.toString().getBytes()); sc.write(buf); System.out.println("Har sendt besked"); } } catch (Exception e) { e.printStackTrace(); } }
"Har sendt besked" udskrives det korrekte antal gange, men klienten modtager absolut intet. Det er i øvrigt en java-klient der anvender den gammeldags socket-model - kan du se hvorfor der ikke sendes noget?
Ja der bliver flippet en del, men ikke når SocketChannel.write() anvendes. I artiklen skriver de kun CharBuffer's ikke ByteBuffer's, det er muligvis derfor, men selv i Sun's egne eksempler: http://java.sun.com/j2se/1.4.1/docs/guide/nio/example/index.html flipper de ikke ved write.
Hvis ikke der flippes starter den åbenbart i den forkerte ende af bufferen og sender alle de tomme felter (som 0'er) og stopper når den kommer til det der egentlig skulle sendes. Nu sender jeg fra win2k til win2k, så der skulle vel ikke være problemer med at der er forskel på hvilken vej den læser fra, eller hvad ved jeg - det er nok det fra faget Teknologi jeg synes er længst ude i skoven :)
OK, nu har jeg godt nok fået et par øl, men jeg håber det jeg skriver giver mening alligevel!
Din kode er ikke OK. Der er ingen som helst garanti for at hele bufferens indhold sendes når du kalder sc.write(buf) - det kan være det hele, eller det kan være ingenting, eller det kan være noget af bufferen, det er det der er det vanskelige ved non-blocking IO. SOM REGEL vil alle bytes i bufferen blive sendt, men der er ingen GARANTI for at det sker. Det er man nødt til at tage højde for. Jeg skriver et kode-eksempel i morgen, når jeg er ædru!
OK, jeg har fundet noget kode jeg selv har skrevet, som du måske kan bruge som inspiration! Her der det godt nok String's der sendes, men princippet er vist det samme. Min 'Connection' klasse svarer vist nogenlunde til din 'Client' klasse.
public class Connection { public static final int BUFFER_SIZE = 256; private SocketChannel channel; private ByteBuffer receiveBuf, sendBuf; private LinkedList pendingStrings; private String sendingString; private StringBuffer receivingString; private int sendingStringPos;
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.