ghedipunk / PHP-Websockets

A Websockets server written in PHP.
BSD 3-Clause "New" or "Revised" License
916 stars 372 forks source link

Discussion of new major version #14

Closed ghedipunk closed 9 years ago

ghedipunk commented 10 years ago

Based on maturing as a developer, I feel that there are some fundamental architectural changes to the project that would benefit other developers and projects.

While the current implementation is technically sound, and can easily handle a large number of clients using the same WS application, I think that we could benefit from scaling outwards as well.

For instance, if a site wants multiple WS applications specified through different request URIs or through different Host headers (in the style of web server Virtual Hosts), then as it stands, all code is loaded into a single instance, and connection-based routing to the correct appliance must happen on all network events.

Additionally, as currently implemented, the application blocks until it receives a network event. If, for example, data in a database that should be sent to all clients gets updated from outside of our application, then we would have to wait until a client sends a message to the server before the server can then unblock, check state, then update all clients.

Further, there's no support for multiple servers, such as clustered environments or environments replicated across regional data centers. Granted, this is a fringe feature, but one that I believe is possible and should be a reasonable goal.

And, while this "bug" doesn't exist in the vanilla implementation (it's far too simple for this bug to even exist simply because the "process()" method is abstract), it's easy for bugs to be introduced to have user information bleed over between connections. I want to avoid this, and make such bugs harder to be introduced.

However, I'm not the only user on this project any more, and my plans will break all current applications that have already implemented this WS server, requiring those applications to be rewritten if they attempt to upgrade.

I do plan on continuing to maintain the current version of this WS server through development of the new branch and for a reasonable time after the new version is released, but wouldn't be adding any new features (not like I had been adding any new features before, so nothing lost there).

So, this issue is one big happy feature request and architectural discussion thread. Feel free to comment.

ghedipunk commented 10 years ago

My first idea is to fork out a new process each time a client connection is successfully initiated.

This will require communication between processes, which also requires a central controlling process.

This would help compartmentalize user-specific data and allow clustering.

Additionally, with my second idea that I'll introduce below, this would allow us to reduce the load of routing between appliances and prevent appliances from sharing the same footprint.

ghedipunk commented 10 years ago

My second idea is to switch from an inheritance model to an interface model.

This will allow appliances to be able to be swapped out, and limit its area of concern. Instead of dealing with the user's connection as well as its data and logic, it would simply worry about the user's data and logic. Along with forking above, it will allow only one appliance to exist within a process.

blondie101010 commented 10 years ago

After trying multiple possible websocket solutions for my current project, yours is the only one really usable for me.

I agree that it can be improved but would encourage you to focus more on compatibility, security, stability and performance than on packaging it with other components which would make it less flexible.

nickrobillard commented 10 years ago

Firstly, thanks for this project! Web sockets make me feel like a kid again. Up until now I've used things like Firebase where I don't have to think about the server. I'm now working on a Bitcoin bot project and for my web admin, I'd like to push trade data to clients with web sockets rather than pulling with ajax for obvious performance gains.

So I need something that pushes data from a database out to all clients - ie: server initiated pushing. Ideally, and more efficiently, I'd love to be able to push out trade data to clients directly from the bot's run loop. (Yeah the bot is written in PHP - far from ideal but let's not get into that right now.) I'm not sure how this would be accomplished. I've written loads of CMS-ish PHP but this is getting into new territory for me. Is this where inter-process communication comes in?

I see that you addressed the need for server initiated pushing in the first comment in this thread. Any updates in the past month since this thread was started? I am very interested in helping out here.

ghedipunk commented 10 years ago

No coding updates yet.

I'm considering two different ways to allow the server to initiate its pushes. One option involves multiple child processes spawned on new connections, and the other involves non-blocking sockets.

Both have their limitations. I may implement both and leave it as a configuration option. If we go the first route, then yes, this is where inter-process communication comes in.

A more elegant solution will depend on test results using socket_sendmsg which is not documented, but is available since PHP 5.5. Since 5.4 is still in its maintenance lifecycle, I'd have to have a fallback to support that version as well, though.

AMA3 commented 10 years ago

Thanks and kudos for this project! I'd like to suggest that the new version's API should follow an event-based model as close as possible to the browser's JavaScript API (and perhaps somewhat similar to the Java EE 7 javax.websocket API). The key difference might be that the constructor would have a different signature, with one parameter being a timeout for how long to wait for a connection to be established (with -1 indicating forever).

ghedipunk commented 10 years ago

Great idea: pool of custom callbacks on events. Thanks, AMA3.

Would allow multiple callbacks the opportunity to handle the same event.

For example:

// Appends $this->myReceiveCallback() to the pool of callbacks registered to handle the onReceive event.
$ws->addOnReceive(array($this, 'myReceiveCallback'));
if ($debugging == true) 
{
  $logger = $dice->create('Logger');
  $ws->addOnReceive(array($logger, 'logOnReceive'));
}

Would use the same syntax as defined in http://php.net/manual/en/language.types.callable.php, since I'd probably be using call_user_func().

Unrelated tangent: I'm strongly considering adding Dice as a dependency: https://r.je/dice.html https://github.com/TomBZombie/Dice

AMA3 commented 10 years ago

I'd personally recommend against adding dependencies. Firstly, it seems like every project that I use that starts with dependencies ends up regretting it later. Secondly, a lot of developers I know start getting turned off whenever a project has dependencies that they aren't already a fan of.

By comparison, I think it's a nice idea to build in support for other projects, but keep them optional.

Just my two cents!

ghedipunk commented 10 years ago

In the time I wrote that and by the time I got back, I'd come to the same conclusion... Who am I to say how a project should perform its Dependency Injections? What if they're already using a framework that has its own (obviously inferior) DI container? ;-)

Dice would certainly be welcome in client code, but yes, I agree it wouldn't be appropriate to require (or even strongly encourage) it here.

ghedipunk commented 10 years ago

Another option to allow the server to periodically stop waiting for input and be able to process other non-socket based events: SIGALRM.

This would be most backwards compatible with the existing version. It's also very exclusively Posix, so only Linux and Unix environments could run this... which isn't too terrible, since I'm working on daemon code right now, which also makes the project Posix only.

merged-packets commented 9 years ago

I like the idea of PHP websocket server. It greately simplifies the development of server applications. But I would not overload the server itself with extra functions and dependencies which can make it heavier and more restrictive. In my opinion, the server primary and disinctive function, apart from handling handshakes and control frames, should be encoding/decoding websocket messages and dispatching them down to apps.

Please, correct me if you think that I am wrong

merged-packets commented 9 years ago

From this perpective, current single-threaded design is very optimal. If developers want to isolate their apps and run them in separate processes or threads, they can start a thread on new connection and then pass incoming messages to this thread in the process() handler using any interface or protocol of their choice. This would be both - scalable and flexible, giving developers freedom to optimize performance, memory footprint and other important things they care about. For this reason it would make sense to allow each user (client socket) to have custom WebSocketUser::process() handler matching the requested websocket subprotocol and/or extension.

merged-packets commented 9 years ago

Sending messages back to clients is more straightforward. Since socket writes are typically non-blocking, it would make sense to allow sending websocket messages directly from the caller context, e.g. by calling WebSocketUser::send(). This means that server doesn't need to run any sending threads and developers are free to use any threading scheme of their choice.

I hope this makes sense to anyone.

merged-packets commented 9 years ago

One capability that I would add to the server is support for earlier websocket versions - some browsers build with Qt4 can handle only those.

Another useful thing could be a PHP websocket client. This would allow the server connect to other websocket servers and use their apps.

amb3rl4nn commented 9 years ago

The only thing I noticed immediately is the blocking for new connections and the while loop in the main run function. I think adding an override for both of these would be two simple things that can be done quickly. I'm running this library within my own server loop and other code is being executed when there is nothing to be done with websockets. I've changed these both myself but I'm sure others might want an official implementation.

In regards to older browsers... it might be better to look at a solution like jquery-graceful-websocket where the client drops to a polling post method and allow the server to understand this and reply with what is in buffer for that client. This would work well in many circumstances I believe.

Xaraknid commented 9 years ago

First I would like to thank you for this awesome work. Personally I like this version of ws because it's simple and small, around 616 lines of codes over 3 files and it do the work. And work on it's own did not rely on a patchwork of libraries like Ratchet requires React, Guzzle, and Symfony2's HttpFoundation in order to work. It's a very lightweight and easy solution!

About : -Multiple ws apps for multiple virtual host: The easiest way should be to run multiple ws app each on different port. In the event you want a single ws app to run several virtual host you'll need to keep track of host in users and when broadcast will only send on same host users for example.

-Data sent outside of apps and need to push to client : As Amb3rl4nn said the easy way will be to add an abstract function like process at the end of while(true) loop. All will depend on own everyone intend to use WS but I think in most use it will have a constant flow. And if not you can set a ping request in client with an interval that ping will unblock the select.

-Multiple server deserving the same host : I think too that should be an awesome feature and will help scalability. My personnal opinion about that as I think this multiple servers can be on different server in same region. Will be using a internal socket connection to a router server to communicate between server. Still at stage of idea.

-Process function can lead to "bug": I think I understand what "bug" you talk about as a bad manipulation from dev could lead of alteration of users data. That is the job of dev to sanitize all data from client as "never thrust internet :)".


The current version need a revision over some little thing that will improve as blondie101010 said over security and stability :

As written in RFC : -we should set a maximum payload size : this will prevent exhaustion of memory -maximum accepted fragmented payload ( continuous frame ) : this will prevent someone sending 10 000 frames of 1 bytes if we had a max_payload_size of 10K for example.

This 2 things depend on usage the dev do with WS so this 2 variables should be configurable.

Add a write_buffer : like read, write can write less than the length of the message we want to send and if this happens it's on the apps to deal with that as socket_write said I write X bytes, for the rest of message it's goes into the void.

Also set a maximum for write_buffer to prevent exaustion of memory leading to fatal.

With all this info we will be able to dynamically set a MAXIMUM CLIENT based on memory usage mem_pool_clients 66% of php memory available.

Max_clients = mem_pool_clients / ( max_buffer_write + max_buffer_read + internal_data_client ).

The handshake need to be buffered in case of more than one packet needed to complete the request + a max_handshake_request_size. I post a fix about this issue #28 . Also it'll need a timeout to free ressource in event of a bad use of handshake request.

Consider also that unless you tweak and recompile php there is a hard maximum of 1024 concurent connection while using socket_select().

One last thing it should check that it have socket library in some way. Most version come with socket-enabled but still a chance that some did not have it.

If you want I can help with this major revision.

Continue your good work.

ghedipunk commented 9 years ago

Current plans:

1) Fix bugs, security issues, implement TLS. 2) Experiment with better structured, more maintainable, more testable and loosely coupled code. 3) PHP-CPP. (Will still maintain both of the raw PHP versions but also have a compiled PHP extension for people who want/need screaming power.)

ghedipunk commented 9 years ago

Created a dev mailing list using Google Groups. Anyone can read, anyone can apply to join. I will create a user mailing list some time in the future for more support related discussion in the future, but would like to keep bug and feature requests here on GitHub. The dev mailing list will be for people who submit pull requests.

Mailing list is https://groups.google.com/forum/#!forum/gp-websockets-dev

Next immediate goal is a suite of unit tests. The specifics aren't set yet, and anyone who had input is encouraged to drop by the mailing list. As soon as we have some unit tests in place, I'm going to switch over to working on a project web site which will host a dogfood web chat to be further used in dev and support. Finally, I'm still hard set on implementing TLS. Other features, like accessing the user's PHP session, will be discussed further after TLS is implemented.