Avatar billede dsj Nybegynder
17. august 2003 - 19:14 Der 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!
Avatar billede dsj Nybegynder
17. august 2003 - 19:16 #1
Problemet opstår i snit 1 gang i minuttet for hver aktiv klient.
Avatar billede arne_v Ekspert
17. august 2003 - 20:16 #2
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.
Avatar billede dsj Nybegynder
17. august 2003 - 20:31 #3
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 :-/
Avatar billede arne_v Ekspert
17. august 2003 - 20:40 #4
Medmindre det er en meget hurtig net-forbindelse og en halv-sløv CPU,
så burde det ikke være noget problem at gemme i JVM memory.

Din beskrivelse af fejlene på de 2 metoder lyder mystisk. Hvilken
platform og hvilken JVM ?

Har du prøvet at hive ud af ByteBuffer hver gang og gemme i en normal
StringBuffer ?
Avatar billede arne_v Ekspert
17. august 2003 - 20:42 #5
Umiddelbart tror jeg at jeg ville føle mig fristet til at wrappe XML'en
i en anden protokol som indeholdt en længde.

Inspiration fra HTTP:

Content-Type: text/xml
Content-Length: 1234

1234 bytes XML data

fordi så vidste du når du var færdig og kunne starte parsning.
Avatar billede dsj Nybegynder
17. august 2003 - 20:47 #6
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.
Avatar billede dsj Nybegynder
17. august 2003 - 20:50 #7
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).
Avatar billede dsj Nybegynder
17. august 2003 - 21:06 #8
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 :-/
Avatar billede dsj Nybegynder
29. september 2003 - 11:39 #9
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.
Avatar billede Ny bruger Nybegynder

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.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester