The reality of PHP WebSockets

Once inside the loop, never outside the loop

Italo Baeza Cabrera
ITNEXT

--

One thing I had to implement against my will was a Websocket server… in PHP. You heard that right.

I’m always been very vocal about PHP being used for thing that is not suited for, like holding socket connections, when the nature of the language and the runtime itself is single-threaded, or “blocking” if you say.

But, even If I didn’t wanted to and looked into Go and Rust for the implementation, I figured out that WebSocket on PHP wasn’t THAT bad to work with, but there were a few caveats.

Websockets and the async nature of PHP

Some time ago, some folks found that PHP could handle some sort of concurrency through using Generators.

In a nutshell, Generators are functions that can iterate over an “unknown” and return values as these are received, by just simply pausing the function execution and waiting for the next value. It’s like the Fiber less-friendly cousin.

Generator are the key for Concurrency, a technique used in some programming languages, like Javascript. You can generate a loop over multiple “callables” registered inside a value that always returns the result of the callable in the stack:

Just a simple approach to almost-async functions.

That’s just the tip of the iceberg. This example is simple and will return “bar foo” if you run it. There is an interesting small book about generators and how these can work to make code asynchronous.

For those wanting performance, these libraries can “bypass” the Generator performance penalty and directly use libraries that control events themselves, like libuv, libevent and libev. ReactPHP will ask for the user to set a “driver”, while Amphp will use them automatically in that order.

Socket as the generator source

The thing is, PHP has everything it needs to work with WebSockets. The stream_socket_server function in PHP allows to create a socket that returns whatever connection goes in, as it works like a Generator, along stream_set_blocking set as non-blocking.

Couple that with an async library like ReactPHP or Amp, among the most used these days, and you can have a connection handler that doesn’t block the whole application while waiting for the next connection being made. That’s in a nutshell, as the details deserve a whole article on its own.

Going for WebSockets

Now that we have very “hacky” but stable way to make async calls on PHP — at least until Fibers come in PHP 8.1 at the end of the year — the next step is to hook into the socket server, and then upgrading the incoming HTTP Request connections to WebSockets connections, keeping them open in memory.

Luckily for us, there are already some libraries that thought on that and offers their own WebSocket Server. From the most used, there is Ratchet that uses ReactPHP behind the scenes, while Amp has its own mature WebSocket Server.

After choosing your poison, you will be mostly preoccupied of doing something with the Client connected and the message that go back and forth the server using a single persistent PHP instance.

Swoole and Roadrunner Websockets

The thing with Swoole and Roadrunner is that these are a kind of different beasts, each with its own way to make PHP faster.

Roadrunner is simpler to understand as it can be considered total replacement of NGINX or Apache. It’s a web server binary that connects itself to PHP and load-balances it without bringing down the PHP runtime every time a request is served, but instead, it keeps the runtime alive. PHP itself is unaffected, there is no need to add external dependencies and extensions. It’s a very clean implementation that soon should have friendly support for receiving WebSockets connections and passing them to PHP. It even supports gRPC, metrics, and braindead-easy configuration.

On the other hand, Swoole is itself an extension that you must load into PHP. This extension allows to runs the Web Server inside PHP. You can think about it has having NGINX inside your code, but with more granular control.

The problem is that you need to compile and install the Swoole extension via PECL, which is something not everyone is keen to do, specially in Windows considering you’ll need to use WSL2 to actually use it. The advantage of Swoole is reusing its own concurrency stack by replacing database drivers, HTTP Clients and Redis to work async, which sometimes yields better performance if you work heavily on these parts.

Websocket are possible… now

You can use and deploy a WebSocket server in a PHP server today. Take into consideration that if you use a pure PHP implementation based on ReactPHP or Amp means going into their Event Loop, and abide to whatever they work in an async context.

The performance of a Websocket Server made in pure PHP it’s not bad. The guys at BeyondCode use Ratchet, and shared this performance metric with their server on top of a Laravel application.

Here is another benchmark that was run on a 2GB Digital Ocean droplet with 2 CPUs. The maximum number of concurrent connections on this server setup is nearly 60,000.

Of course using PHP will yield less performance that any compiled language like C++, Rust, or even Go if we consider the famous example of 1 million of connections with just using 600MB of memory. We can take also Node.js handling also a million of connections with the help of a library for WebSockets written in C.

The advantage here is code reuse. If you’re already wrote part of your application in PHP, running a WebSocket with the same codebase means faster development as you don’t need to translate anything.

Well, if your server starts to handle hundred of thousands of users connected, you may start to look into larger solutions like Pusher, PubNub, Stream, PieSocket, AWS API Gateway, Firebase Realtime Database, and other systems for robust streaming.

Hopefully, once Fibers land on PHP and async libraries update their codebase, WebSockets on PHP may become faster, and hopefully, more broadly adopted for experiments like Laravel Livewire to get better.

--

--

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com