EDIT: If you need to submit bugs or want to improve my
work, its now available on github!
Ever
tried searching for information on how to properly create a multi-client socket server in PHP? You’ll get
plenty of results with outdated and messy source code, some of which won’t even work.
This
was the conclusion I'd come to a couple years ago when I decided that I wanted to try my hand at writing an
IRC server. The why is not important… (For the fun of building an IRC Server, if you can call
that fun). So I googled around a bit until I finally found some code that worked on its own, and
quickly built a semi-functional IRC server using it, and headed off to sleep at 5am.
The
next day I was very, very happy with the results of my hard labor, but it wasn't good enough, so I started
re-writing it from scratch as an Object, and thus I created class::IRCServer.
Then,
once I felt that I was finished screwing around with my newly built IRCd, I decided to modify the function
enough to be used on its own as a socket server, to share with the world. However, that was a couple years
ago (The non-edited version of this article was written in 2010).
Recently, however,
PHP has evolved to a point where the Socket Server class I had written was out-dated, buggy, and just plain
bad.
So I built a new library from
scratch after looking at the old code, and came up with Navarr\Socket. The code is open-source,
MIT-license, and available on Github. I heavily encourage you to fork and submit pull requests. The code
is namespaced, follows the PSR-2 standard, and even has some PHPUnit tests.
Out of interest, what are the limitations as far as the max_clients is concerned?
ReplyDeleteAs much as your system can handle I suppose. I haven't really tested it, nor do I know
ReplyDeletePHP well enough to say.
Hi, Navarr -
ReplyDeleteThank you for taking the time to put this code
together!
I have a question for you about this line in SocketServer::loop_once():
$this->trigger_hooks("CONNECT",$this->clients[$i],"");
I'm assuming that
"CONNECT" is a method that needs development. Is this correct?
_______________________
I
have another question for you in regards to JavaScript. Do you think JavaScript frameworks that abstract
developers away from HTML/CSS will end up being the future of web development?
If you're asking what I think you're asking, no. "CONNECT" is the name of the hook
ReplyDeletebeing triggered.
When using the class, you attach a method to that hook with $server->hook("CONNECT","method_name");_______________________As
for the JavaScript bit.. I think it has an important place, but won't be the future.
with this example i get this if a client connects:
ReplyDeletePHP Warning: Parameter 1
to handle_connect() expected to be a reference, value given in /var/rba/socketserver.class.php on line 193
This is a masterpiece, after many days searching on google and many days struggling
ReplyDeletewith socket servers, I've found you! and your wonderful code.
This is definetly a great
job, thank you very much for making my day man.
I'm glad you liked it! If you have any bugs or any improvements to make with it,
ReplyDeleteplease feel free to put in a bug report or fork it on github at
https://github.com/navarr/PHP-SocketServer
Hello again, as I said before this is a masterpiece, unfortunatelly I've been testing
ReplyDeleteas a socket server whit several devices connecting by GPRS, the connection is fine, but after a while the
server doesn't seem to recognize that the device have changes socket, for some reason I think it could be a
problem with the phone company and it's APN.
I have only 6 devices and almost 1000
sockets opened. Have you experienced something like this before? Do I have to implement a system like a
heartbeat to recognize the disconnection?
Any advice is very welcome. I will keep
working on it, and I'll post re results.
Thanks anyway!
it should recognize and clear up disconnected sockets. I will probably need to do more
ReplyDeletetesting before I can get back to you with a proper answer.
i experience this error too, any help?
ReplyDeleteThis seems to be a general issue. I'm not passing it as a reference. Its only a warning
ReplyDeleteso it can be ignored. Additionally, you can remove the ampersand from the first parameter of handle_connect.
Wicked bro!
ReplyDeleteAfter proper check on out it's working, and some tuning to add
some auth form for connected clients, and a couple of more treats, its been a solid php socks class so
far...
nice.
Hi there. I've been trying to get my head around socket servers for days - as you say,
ReplyDeletethe examples out there are either poorly explained or flatly don't work. For me, nothing worked; I kept
getting socket bind errors, saying the host lookup failed. In fact that's what I get with yours, too, to
start with, but then I changed the 'null' host reference to 'localhost'. This time the browser hung, as
though it was loading a massive file. Is this expected behaviour? I opened a new tab and loaded the page
again and it said the address was already in use. Presumably this means the previous page load started the
server, and it's still running. How would I restart it? Do I need the console for this sort of thing? Thanks
again.
If you try to run a server on a web page it will hang, yes.
ReplyDeleteThe
expected use of this is over command line PHP, not as a web page. Binding as null means that it listens on
all ports, if you're using shared hosting its possible that your provider doesn't allow it to listen for
incoming connections. Using localhost means it'll listen to connections to it from within the server.
-----
Navarr
T. Barnier
[email protected]
http://navarr.me/
Thanks, Navarr. So when would one start the script via the console - when launching the
ReplyDeletesite? Would it then run, er, forever? I'm trying to get my head around the process here. Never done PHP via
command line before (not even sure how on Mac...)
This program is a server in itself. You'd start it when you want the server running and
ReplyDeleteit runs until an error or you close it.
Presumably if it's, say, a chat ap, though, I'd want it running constantly. So I'd
ReplyDeletestart it when I launched the site and stop it... never?
Yes, essentially. Though in that case node us with web sockets would be the better
ReplyDeletetechnology. If your host doesn't allow node, you probably won't be allowed to run a socket server.
If
you're building a chat app you'll instead want to publish chats to a database and use Ajax polling and Ajax
posting to keep it up to date.
Hey thanks for this fantastic class! Awesome piece of work! Just having a similar
ReplyDeleteproblem to others below.. When disconnecting/ inputting or connecting I receive this message
"PHP
Warning: Parameter 1 to handle_connect() expected to be a reference, value given in
/var/www/test/SocketServer.class.php on line 203"
or
"PHP
Warning: Parameter 1 to handle_input() expected to be a reference, value given in
/var/www/test/SocketServer.class.php on line 203
"
etc
And
the functions do not run. Hopefully you can tell me where I'm being stupid!
I fixed it in the example. On that line just put an ampersand (&) in front of
ReplyDelete$this and $client.
Hi,
ReplyDeleteI'm trying to use this cose. I'm trying with my Public Static IP using
telnet, when I type the command line, this is what get out.
telnet ***
35100Trying ***...Connected to ***.Escape character is '^]'.
Any ideas?
First off, it may not be entirely situated for telnet. There are a couple weird things
ReplyDeletegoing on there. It's a raw connection handler atm.
Secondly, that looks like
it's working... was there no "String?" request? If not, it may be telnet not knowing what to do with it.
I
recommend you download PuTTY.
Meanwhile, thanks for the reply.
ReplyDeleteI tried using putty on
port 35100 through SSH, but does not connect to the server either.Point out that the server is already using
apache listen on port 35100, to address all requests that come to the script in question. La porta รจ aperta
e il reindirizzamento funziona correttamente.
Maybe I did not understand what you mean by use
PuTTY. Is there something I'm missing?
thanks.
This isn't a web page script. This is intended to be run from the command line with its
ReplyDeleteown port.
Thank you.It's nice code.
ReplyDeleteGreat job man! This works fine for me and will be very useful for my projects.
ReplyDeleteThanks for the post. It was definitely educational, but the code is not very practical.
ReplyDeleteThe limitation of max_clients is too severe. And the blocking of the sockets that can happen on each
iteration of max_clients can be a severe performance problem. As it stands, any kind of heavy handle_input
processing would block all processing on all other connected sockets until that one socket connection was
handled.
If anyone is looking here for a jumpstart on their socket server, its good to
get your feet wet, but you'll be wanting to settle with a non-blocking socket that forks each connection off
into its own process so that you can process all connections asynchronously.
Closest
thing I've found to this is https://code.google.com/p/phpsocketdaemon/ but I haven't actually tested it yet
and reviewed the code indepth to say if it does the job well. I originally skipped over it simply due to its
age without updates.
Also regarding this article and code, there's another explanation
written at http://devzone.zend.com/209/writing-socket-servers-in-php/ which uses a lot of the same code as
in the class above (not sure who came first, but doesn't matter) - so its a good read for those learning
about socket servers and playing with this class.
Correct me if I'm wrong, but without some cross-socket communication forking will
ReplyDeleteprevent the processes from sharing variables, right? (Not to mention a complete lack of semaphores and such
in the PHP language, since it hasn't yet been written to take advantage of those types of features).
Either
way, thank you - I think this class is very good for getting your feet wet, as you've said. The DevZone
article definitely came first - but was one of the many articles that I was referencing when I wanted to
create this. I believe this will only block on input from any client plus processing time.
Either
way, I think a language like NodeJS is *much* better suited to this type of work (and the max connections
can of course be changed)
Well cross-client communication could happen via proper DB integration, which is
ReplyDeleteprobably how it should be done considering most applications. I just finished some testing on that
phpsocketdaemon script i linked and it doesn't seem to be asynchronous as it claims. Two connections in,
while one is processing some data input, then other is waiting, and only continues after that first
finishes. I've yet to dig into code to see if that behavior is configurable, or if its pretty much the same
limitation.
it'd be like having a web server that could only handle 1 request at a
time, definitely a hefty limitation for any real world application.
so i'm still on the
hunt for the magic article or class that does the trick. If I have to write it up from scratch, I would
probably rather go NodeJS since as you point out, it is much better suited since it is precisely what it was
built for.
Well, not everything would be synced in the DB - for example an array of socket
ReplyDeleteconnections, or pointers to sockets, etc. It's not that big of a limitation for smaller servers (since a
single processor can only do one thing at a time anyway), but once you get into quad-core etc then it
becomes a limitation.
In that case, it would be more preferential to do non IO
intensive stuff for the socket and then stuff like updating the database in a quick fork so that the main
program can continue on to the next socket.
Keeping all of your socket and
quick processing in one place while keeping your IO intensive stuff in another.
This
then only becomes a problem if you need to wait on data (from a file, db, etc.) before communicating with
the socket and jumping from the next.
But yeah, NodeJS is better for this
kind of thing xD;
Thats a good point, and one that hadn't occurred to me. Just fork the intensive stuff.
ReplyDeleteWhile there is overhead in forking, at least all the other clients aren't waiting on what one of the other
clients caused to be executed. Again, clearly nodejs wins on efficiency, but in the end a little fork logic
in the handler makes php able to handle it as well without the bottlenecks.
In
the end I just added some fork logic to the phpsocketdaemon i linked before. It doesn't have any setting for
max clients, nor does it loop based on max clients, but the code is virtually without any comments, and
there's not a lick of documentation, so there might still be some bombshells hiding in there I've yet to run
across.
In any case, your article gave me the well-documented launch point
that I really needed, so hats off to you, thanks!
No problem and glad it could help. :)
ReplyDeleteHi Navarr, Nice job thank you!
ReplyDeleteI have a question. How can i send a
message from one client connected to the other client?
Not mass message to all others,
just for a selected client?
For example i have a client with id:0 another with id:1,
third with id:2 etc. and i would like to send message from id:0 to id:2
$clients[2]->socket
does not seem to working?
10x in advance!
Daniel
If you want to be able to reference other clients, you'll want to create a hashmap with
ReplyDeleteids as keys and references to the socket object as values.
On connect you'll
want to do something like $clientMap[1] = &$client;
You should also remember to
clean this array up on disconnect.
However you should still be able to do
$clients[2]->socket (there is no reason that shouldn't work so long as $clients[2] is set and the
socket is still connected).
Let me know if you have any more problems. I
think I'm going to re-evaluate the class and PSR-2 it and write tests and everything too. (Off topic, more
of a reminder for myself)
Hey thank you!
ReplyDeletehashmap is a nice idea. i didn't know that i can save
references to the socket object in it.
$clients[2]->socket was a stupid mistake from me. I
have forgotten to reference $server ;-)
thanks once again, it
just works!