[Java NIO Sockets] Wanneer mag ik schrijven?

Pagina: 1
Acties:

  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
Voor een intern projectje moet ik 16 keyboards aan 1 pc hangen en al die input naar eigen inputveld in flash (mx 2004) sturen.

Op zich was dat geen probleem mbv een XMLSocketServer (zonder XML natuurlijk).

De code daarvoor had ik al 2 jaar liggen maar door er opnieuw naar te kijken begreep ik iets niet:

NIO sockets gebruiken select() en 1 Thread voor quasi alle throughput. Nu kun je mooi luisteren naar Accept / Read (er is iets aangekomen) / Write.

Die laatste gebruik ik niet. En ik vraag me af of dat wel 'proper' is. Volgens de spec als je SocketChannel.write(...) doet geeft ie terug hoeveel ie er 'geschreven' (ik neem aan dat dat niet gelijk is aan het verstuurde bytes maar gewoon hoeveel ie naar zijn interne (native) buffer heeft geschreven.

Wat ik echter nooit nakijk is dat ie wel alles heeft geschreven. Ik heb nog geen probleem hiermee ondervonden - data is tamelijk klein maar wel heel veel packets.

Nu vraag ik me af of ik die write() in feite moet doen in de select() clause enkel en alleen als die OP_WRITE aanstaat (en deze aanzetten wanneer er iets te schrijven valt).

Ik heb geen enkele online demo code gezien die dit doet maar aangezien de select() tamelijk typisch concept is vraag ik me af of andere (c / php / ...) servers die wel anders oplossen? (ie er rekening mee houden en if zo - waar).

Beetje vreemde vraag omdat ik daar opzich nog geen problemen mee heb gehad, maar nu ik deze code wil gebruiken voor een online multiplayer 'carcasonne' - type game (met vele players) verwacht ik het probleem wel.

Verwijderd

Ten eerste.... Een SocketChannel.write() geeft aan hoeveel bytes er zijn geschreven naar de socket waar dit channel bij hoort.

De methode waarop het moet (zoals Sun het bedoelt heeft) is als volgt.

Je zet je IntrestOps alleen op OP_ACCEPT en OP_READ... (je mag ook OP_WRITE erbij doen om te wachten tot je socket klaar is om te schrijven maar dit is ie vaak direct)

Als je iets te schrijven hebt.... dan doe je dat meteen. Je hoeft alleen maar OP_WRITE toe te voegen aan je IntrestOps als je SocketChannel.write() minder bytes terug geeft dan dat er in je output buffer zaten. Je select() wacht dan tot je socket weer bytes kan schrijven en geeft je dan de SelectionKey voor het SocketChannel. Je zult dan een methode moeten aanroepen die probeert de resterende bytes in je output buffer naar je socket te schrijven. Mocht dit lukken dan heb je geen intresse meer in OP_WRITE... mocht je weer minder bytes terug krijgen.... dan doe het je process opnieuw.

Als je gaat werken met OP_WRITE als trigger voor je write gaat je dat heel wat extra CPU kosten. Stel je hebt 1000 clients aan je server hangen en je wilt wat schrijven. Dan zal ie dus 1000 keys terug geven in je select loop. En ze allemaal langs lopen terwijl je er maar eigenlijk 1 of 2 zou moeten hebben van clients met een trage verbinding ofzo.

Als je nog vragen hebt... let me kow :)

  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
hmm idd beetje tricky. ik had verwacht dat ik die interestOps PER socketChannel zou kunnen zetten ie:

ik ben geintereseerd als DEZE connection kan schrijven ipv ALLE connection. Maar zoals je aanhaalt is dat blijkbaar niet mogelijk en krijg je idd duizende (waarschijnlijk 'new'-ed keys terug). En idd je idee
van die op_write aan te zetten NA een fail is interessant (ook al uit debugging issues), helaas dan idd voor ALLE (is dit ook zo bij alle ander select() systemen -> lijkt me toch wel tamelijk overhead terwijle het eigenlijk toch wel mooier op te lossen valt: een registry per channel van interesses, in Win32 valt dit vast op te lossen met WaitForMultipleObjects...)

Misschien doe ik er het beste aan om gewoon dit bij te houden en te testen wanneer zoiets voorkomt en pas als dat echt gebeurt dan een oplossing ervoor te zoeken.

Als je eventueel nog een tip kan geven:

mijn server is XMLSocket (dus messages eindigen met /0 (null byte)). Nu moet ik dus eigenlijk alle chars die binnen komen gaan controleren. (wat ik ook doe). Vraag me af of daar ergens intern een snellere methode voor zou zijn... (uit testen heb ik al wel gemerkt dat bij elke read het laatse char ook /0 is. En door een message count bij te houden (per channel) kan ik snel checken of er daarvoor nog een nieuwe is (ie ipv Front->Back search een Back->Front search)...

Alvast erg bedankt voor de verduidelijking.

Verwijderd

Je kan je IntrestOps PER SocketChannel setten hoor :)

Alleen als je hem bij de selector registreert moet je hem wat default intrestOps meegeven. (hoef niet perce is wel handig)....

Dus je kan voor alleen een SocketChannel wat dus niet genoeg schrijft daarvan de SelectionKey pakken en daarvan de intrestOps op OP_WRITE zetten. Dit geld dan dus niet voor ALLE channels.

Let wel op dat je alleen de intrestOps van een SelectionKey kan veranderen met de thread die ook de select() doet... Sommige boeken beweren van niet... maar uit praktijk weet ik dat het wel zo is. (en in de API staat het ook alleen niet duidelijk... "method may block for a period of time" ze zeggen niet waarom ie dat doet :))

Verwijderd

Nog even over je XMLSocket... De reden waarom je per read op het einde een /0 hebt is omdat je waarschijnlijk steeds een HEEL berichtje binnenkrijgt.... hier moet je niet vanuit gaan (online) en je zult dus idd je bytebuffer moeten scannen op een /0 char...

Het kan dus ook voor komen dat er bijvoorbeeld 2 hele xml berichten in je buffer zitten en een halve... hier moet je dus goed rekening mee houden als je je buffer aan het bekijken bent... en dus niet zomaar leeg gooien als er nog iets inzit...

  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
hey thx voor de reply nog.

in feite zou ik dus best per 'connection' een helper classe maken met daarin een 'ref' naar de socketchannel en een interne (native) buffer(s) voor het schrijven en lezen. Dan werk ik met die classe die eventuele miswrites oplost. een soort read stack en write stack (als nog een extra buffer voor 'issues'). Dan een thread (voor 'logic') die x - aantal keer per seconde alles afgaat (afhankelijk van het 'spel' natuurlijk, turn based zou gewoon wachten op de volgende message).

ivm die 1 thread only had ik wel gelezen ergens. Nu goed the whole point is om maar 1 thread te gebruiken.

tja die scan op /0 zal wel pijn doen vrees ik. Ik neem dan ook aan dat andere 'protocollen' eventueel al zeggen hoe groot een message is zonder te hoeven scannen (kan ik eventueel ook).

Ik neem aan dat de interne TCP/IP stack ervoor zorgt dat ik alle messages die mijn clients naar de server sturen a) volledige correct zijn (ie geen CRC nodig) b) in volgorde aankomen c) geen messages wegvallen?

of zoiets als MESSAGEID (optellend nummer per connectie) SPACE SIZE (in string van heel de message zodat ie snel naar de volgende kan springen) gevold door message. Nou ja misschien als de messages klein zijn is het gewoon sneller om te 'scannen'.

Nu heb ik een vriend die hetzelfde doet in C-sharp en die ondervindt wel veel 'loss' en misinformatie...

Je hebt blijkbaar veel ervaring hiermee.

Verwijderd

Je krijgt inderdaad de data zonder fouten :)

Maar wat ik bedoelde met halve messages is het volgende. Als jij een buffer hebt van 20 bytes en je messages zijn 8 bytes dan kan het dus voorkomen dat je hele buffer vol is en dat er dus 2 hele pakketjes inzitten en 1 halve. De rest daarvan zal dan met je volgende read in de buffer komen. Je zou daarvoor ByteBuffer.compact() kunnen gebruiken.

En ja wat je inderdaad zou kunnen doen is van te voren de lengte van je msg te sturen (wel zorgen dat je minimaal de lengte hebt binnen gekregen aan bytes) want als je een int leest terwijl er maar 3 bytes in je buffer zit krijg je een exception.

Mocht je nog meer specifieke vragen hebben kan je me een PM sturen of op icq aanspreken (53361313)
Pagina: 1