17. august 2003 - 19:14Der er
8 kommentarer og 1 løsning
Avanceret: Problem med modtagelse af data gennem SocketChannel
Jeg har lavet en multiplayer-server der bygger på non-blocking, multiplexed I/O ved brug af java.nio. Serveren har et sidste problem, jeg længe har kæmpet med: XML-beskederne der modtages fra klienterne bliver ofte modtaget i to dele.
F.eks. hvis vi har en besked: '<tag noget="andet" noget1="andet1"/>' modtages i første omgang '<tag noget="an' og anden omgang 'det" noget1="andet1"/>' hvilket selvfølgelig gør at indholdet ikke kan parses. Her er lidt kode der viser hvordan jeg læser fra en SocketChannel (fra min Client-klasse):
int readBytes = 0; readBytes = channel.read(receiveBuf); if (readBytes < 0) throw new IOException("End of stream"); int length = receiveBuf.remaining(); receiveBuf.flip(); byte[] buf = new byte[readBytes]; receiveBuf.get(buf); String content = new String(buf); receiveBuf.clear();
receiveBuf er selvfølgelig en instans af ByteBuffer i Client-objektet arbejdes så længe klienten lever på den samme buffer (1 buffer til at læse fra og 1 til at modtage med). Jeg har prøvet to ting, som begge giver den fejl blot i to afskygninger:
ByteBuffer -> non-direct (placeres i JVM): Hvis beskeden læses i to dele går oftest et eller to tegn tabt, hvorfor de ikke kan samles og behandles som én besked.
ByteBuffer -> direct (placeres udenfor JVM): Anden del af den opsplittede besked modtages 90% af gangen slet ikke, men tilgengæld går der aldrig nogen tegn tabt, hvis endelig anden del modtages.
Jeg vil meget gerne have løst dette problem. Kan du give nyttig information får du del i de 60 point, men bidrager du med information der hjælper til den totale løsning af problemet får du hellere end gerne 200 point udover!
Altså det ligger jo i TCP/IP's natur at en besked kan blive splittet.
Du har sikkert læst den her 100 gange:
A read operation might not fill the buffer, and in fact it might not read any bytes at all. Whether or not it does so depends upon the nature and state of the channel. A socket channel in non-blocking mode, for example, cannot read any more bytes than are immediately available from the socket's input buffer; similarly, a file channel cannot read any more bytes than remain in the file. It is guaranteed, however, that if a channel is in blocking mode and there is at least one byte remaining in the buffer then this method will block until at least one byte is read.
Det betyder at din kode skal håndtere din splittede besked.
Jeg forstå ikke dine to forsøg. Kan du beskrive lidt nærmere. Jeg er ikke erfaren med nio.
I princippet burde det ikke gøre nogen forskel om en ByteBuffer er oprettet med ByteBuffer.allocate() eller ByteBuffer.allocateDirect(). Forskellen er om bufferen placeres som et array i JVM, eller som et stykke hukommelse direkte i OS - sidstnævnte skulle være en del hurtigere i længden.
Jeg har lige siddet og testet. Det lader til at jeg kan simulere problemet ved på min testklient ("normal" stream-klient) at flushe output-streamen efter hvert enkelt sendt tegn; i stedet for efter at hele besked er sendt til output-streamen. Det gør at serveren oftest modtager beskeden opslittet. Først modtager den et enkelt tegn af beskeden og jeg reagerer på parse-fejlen ved at læse en ekstra gang fra ByteBuffer'en. Jeg kan så se at der er kommet flere data i SocketChannel'en, men det lykkes mig ikke at få dem overført til ByteBufferen.
Man bliver næsten nødt til at læse fra SocketChannel'en flere gange, hvis ikke de modtagede data kan parses - så skal jeg bare finde ud af, hvordan jeg får overført og læst anden omgang fra ByteBufferen; jeg synes altså ikke de der buffere er lette at kringle :-/
Net-forbindelsen er rigelig hurtig og CPU'en rigtig hurtig (P4 gå hjem og vug), JVM er Sun 1.4.2. Problemet er det samme under Windows 2k og Linux Debian.
Jeg har prøvet at læse et tegn ad gangen hvilket både er langsomt og ikke ændre problemet i positiv retning. Det er ikke noget "problem" at have bufferen i JVM, men det andet er i langt de fleste tilfælde hurtigere og der forlanges meget af serveren...
Sidstnævnte kan ikke lade sig gøre - drift-klienten er Flash og kan ikke håndtere noget så avanceret.
Og jo, de to metoder er mystiske. Det hænger måske sammen med at jeg ikke helt ved hvordan man læser SocketChannel -> ByteBuffer -> String på den rigtige måde. Der findes på nettet mange ideer til hvordan det gøres og Sun's måde er den mindst rigtige (deres eksempler virker slet ikke). Jeg sidder og flipper, compacter og clearer bufferen på et utal af forskellige måder og må gå ud fra at jeg gør det rigtigt, når det virker (lige med dette problem som undtagelse).
Hvis jeg reagerer på parse-fejlen ved først at cleare bufferen (ByteBuffer.clear())og derefter læse en ekstra gang lykkes det mig at modtage præcis de manglende data.
At dette faktisk er løsningen forudsætter at mit simulerede problem er det samme der opstår i driften.
Hvis du har nogen gode ideer må du endelig komme med dem - NIO er efter min mening ret dårligt dokumenterert af Sun, hvilket gør det svært at finde ud af hvad man gør galt :-/
Lukker - jeg smider en ParseException, hvis modtagede data ikke kan parses, hvilket får tråden til at vente 100 ms og dernæst læse endnu en gang fra kanalen for at se om der er kommet flere data.
Synes godt om
Ny brugerNybegynder
Din løsning...
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.