Design probleempje ..
jammergenoeg zit ik met een probleem wat m'n hele vooruitgang belemmert. Ik weet namelijk niet hoe ik nou het beste en het netste m'n klassen kan scheiden. Hier de situatie. Je hebt een beheerder, en een deel daarvan. Zegmaar een index, en een socket. De socket klasse heeft zelf zijn eigen functies voor beheer ( lezen / schrijven / connecten ), logisch. Maar de beheerder / index heeft in 1 functie toch echt een property van de socket nodig ( de resource ). Nu kun je dit heel simpel oplossen ( getResource() ), maar ik ben bang dat dit nogal wat performance-verlies tot gevolg heeft ( zit in een while(true) loop en als je dan elke keer al die resources moet opvragen .. ).
Nu kan het ook heel simpel worden opgelost, dat de beheerder/index zelf functies heeft om naar een socket te schrijven, en dat je dan een 2e object hebt, puur om data in op te slaan. Maar dat is dan weer niet zo "OOP-conform", normaal had ik de 2e manier al lang gekozen maar ik heb mezelf voorgenomen dat ik wat moet werken aan m'n oop design skills. :P
Iemand een ideetje? :$
class beheer extends socket
Dan kun je binnen beheer met $this->functie socket functies aanroepen
Lijkt mij de gewenste oplossing
Laat die maar even zien, dan kunnen we pas een echt goed antwoord op je vraag geven!
De Socket is puur een OO jasje om de socket_* functies van PHP. De instantie beheert zelf de socket resource, die komt er niet buiten. Alle read & write acties gaan via de public methods.
De BeheerderHandler is specifiek voor dat wat er aan de andere kant van de socket zit. Deze klasse is als het ware de enige die dingen over de socket stuurt, die de taal spreekt die naar de andere kant moet. Alle acties die mogelijk zijn aan de andere kant zijn in dit object gerepresenteerd door public methods. Bijvoorbeeld addIets, removeIets, getIets etc. vertalen en sturen binnen de klasse iets naar de instantie van de Socket-klasse, en wachten op antwoord. Dit antwoord vertalen ze weer naar een waarde waar je in PHP wat mee kan (dictionary strings worden vertaald naar associative arrays om maar even een voorbeeld te geven) Voor de naam van de klasse zou ik trouwens [naam-van-applicatie-aan-de-andere-kant]Handler gebruiken. Ik heb bijvoorbeeld een klasse die met XBT praat, en die heet heel toepasselijk XBTHandler.
De BeheerderHandler en de Socket zijn in iedere bedenkbare applicatie herbruikbaar. Je zou ze in een PHP-GTK programmaatje kunnen gebruiken, of in een webapp. Ze bevatten dus geen presentatie, geen echo-aanroepen, geen die of exit. Ze hebben alleen methods die je aanroept en die een antwoord in de vorm van een return value teruggeven. Onopgemaakt, gewoon als pure PHP dingen. Geen strings die HTML bevatten, of tekst die door htmlentities is gehaald etc.
Als laatste heb je dan nog het frontend. Deze is afhankelijk van de toepassing die je maakt. Voor een webapp verwerkt hij bijvoorbeeld de aanvraag en geeft HTML terug. Deze communiceert dus met de BeheerderHandler, maar niet met de Socket.
Waarom van Socket nog een aparte klasse? Omdat je die dan eventueel zou kunnen vervangen door een andere klasse die dezelfde methods heeft. Zo zou je heel gemakkelijk je applicatie werkend kunnen krijgen met een proxy of een ssh tunnel. Je hoeft alleen maar de Socket klasse te extenden en uit te breiden zodat deze via de nieuwe weg verbinding maakt met de andere kant. Waarom dan de BeheerderHandler apart? Omdat je dan gemakkelijk herbruikbare code hebt voor later, of omdat je nu gemakkelijk een andere of nieuwere versie van het programma aan de andere kant kan installeren die een nieuw protocol hanteert. Het enige wat je in je huidige applicatie aan deze kant dan hoeft te bewerken is de Handler.
Mijn idee was meer: de protocol afhandelaar maakt een socketClient instantie, en koppelt die aan de socketIndex. Deze kan dan weer met pollSockets() een socket_select() doen.
Of, de socketIndex beheeft gewoon alle sockets, en socketClient dient slechts voor opslag van de resource etc.
Gewijzigd op 01/01/1970 01:00:00 door Remco
Wat zit er dan aan de andere kant van de sockets? Vraagt datgene dingen op, of wil je dat ding juist vragen stellen? Is het een server of een client?
Gewijzigd op 01/01/1970 01:00:00 door Remco
Ik zou uitgaan van 1 algemene handler, die beheert het programma. Bij deze handler kan je subprocessen registreren. Een daarvan is bijvoorbeeld het proces dat de verbinding met de chatserver afluistert, een ander daarvan kan een direct chat via dcc zijn.
De algemene handler zorgt voor de oneindige lus, en loopt dus oneindig lang alle subprocessen af. Om de beurt wordt bijvoorbeeld $subProces->yield() aangeroepen waar dan het subproces 1 rondje mag uitvoeren. Dat kan een select_socket zijn, of een beetje bestand ophalen, of een setje chatberichten verwerken (en eventueel dus een nieuw subproces starten dat een DCC SEND aanroep verder afhandelt)
Geen idee hoe de performance is, maar ik denk dat het een mooie structuur is die makkelijk is uit te breiden en later indien nodig makkelijk is om te zetten naar echte threads zodat het ophalen van bestanden werkelijk tegelijkertijd (of iig op kernelniveau opgedeeld) gebeurt.
Heb je verstand van sockets? In een eerder project van me had ik precies de opzet die je noemt, een klasse voor irc protocol, andere voor dcc, en weer eentje die een socket voorstelt. Op een gegeven moment wordt dat traag-as-hell en is je bot niet meer vooruit te branden. Je hebt blockende, en non-blockende sockets. Logischerwijs blockt de 1e en wacht totdat de actie is voltooid, en doet de 2e dat niet. Jammergenoeg heeft dat meer zorg nodig, en als je dan elke socket bijlanges gaat (pollen) gaat dat nogal wat cpu-cycles vreten.
usleep kan je je cpu cycles drukken. Een simpel testje wat ik ooit gedaan heb met pcntl_fork en socket_accept (voor iedere verbinding die er gemaakt wordt forkte ik m'n script zodat iedere socket door een apart child process werd afgehandeld) en een usleep(200) in de while-lus koste me ongeveer 1% processorkracht. Maar volgens mij kan je die usleep nog wel wat opvoeren, 200 microseconden is heel weinig.
Ik was uitgegaan van non-blocking sockets. Met socket_select kan het zó veel efficienter. Je kunt met die functie alles controleren.
Je kunt lezen dat je met een simpele functie precies weet wat een socket kan, en al heeft gedaan. Het is dan zeer onlogisch om de heletijd te loopen en gewoon de functies aanroepen en hopen dat er wat gebeurt. (Van de Windows documentatie .. )
Het kan wel, wat je zei, maar met Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
readfds:
* If listen has been called and a connection is pending, accept will succeed.
* Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
* Connection has been closed/reset/terminated.
writefds:
* If processing a connect call (nonblocking), connection has succeeded.
* Data can be sent.
exceptfds:
* If processing a connect call (nonblocking), connection attempt failed.
* OOB data is available for reading (only if SO_OOBINLINE is disabled).
* If listen has been called and a connection is pending, accept will succeed.
* Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
* Connection has been closed/reset/terminated.
writefds:
* If processing a connect call (nonblocking), connection has succeeded.
* Data can be sent.
exceptfds:
* If processing a connect call (nonblocking), connection attempt failed.
* OOB data is available for reading (only if SO_OOBINLINE is disabled).
Je kunt lezen dat je met een simpele functie precies weet wat een socket kan, en al heeft gedaan. Het is dan zeer onlogisch om de heletijd te loopen en gewoon de functies aanroepen en hopen dat er wat gebeurt. (Van de Windows documentatie .. )
Gewijzigd op 01/01/1970 01:00:00 door Remco
Ook is het probleem met socket_select dat het een array van socket-resources wil, en ook slechts socket-resources teruggeeft. Je zal dus op basis van de socket-resource moeten achterhalen wel proces - of het nu chatten of toch bestanden binnentrekken was - achterhalen, en dat maakt een echt mooie OO aanpak inderdaad lastig.
Op basis van socket_select een systeem maken, hmm, ik denk dat ik het het zo zou doen:
De opperhandler wordt een SocketList die de main() lus beheert, en daarin socket_select constant aanroept en veranderde sockets afhandelt.
Bij deze opperhandler kan je processen registreren, en deze processen hebben allemaal 2 dingen in gemeen: een method om de Socket-instantie(s) op te vragen, en een method die wordt aangeroepen wanneer hun socket weer wakker wordt, wanneer er weer werk te doen is.
De Socket-instanties hebben met elkaar in gemeen dat ze een state hebben. Ze lezen, of ze schrijven. (of als je nog een exception-state wilt gebruiken, doe je die er ook bij) Op basis van deze state kan de opperhandler bepalen in welke array de socket moet. Verder kunnen de socket-instanties hun resource via een public method weggeven aan de opperhandler. En dit is waar het OO gezien lelijk wordt.
De opperhandler verbindt op basis van de resources (key) de sockets & hun processen (value) aan elkaar, en vergelijkt deze bij een antwoord van socket_select weer zodat hij weet welke processen een schop moeten hebben.
Je kunt heel simpel een uniek id genereren op basis van het resource id, die kun je opvragen met intval( $resource );