18 September 2018
Swoole: Is it Node in PHP or am I wrong?
Many PHP developers are green with envy when thinking about their colleagues who use Node. Asynchronous Node systems are sharing the codebase between protocols and serve them right out of the code. This really makes one wonder about switching to Node. Well, actually something resembling Node in PHP has been added as an extension. And it’s called Swoole.
Node in PHP? What is that Swoole thing?
Give it to us straight, documentation:
Production-Grade Async programming Framework for PHP. Enable PHP developers to write high-performance, scalable, concurrent TCP, UDP, Unix socket, HTTP, and Websocket services in PHP programming language without too much knowledge about non-blocking I/O programming and low-level Linux kernel.
Swoole works as an extension to PHP and it’s written in C. Sounds promising, right? Hosting an HTTP server with PHP? Websockets in PHP itself? Many other possibilities, some looking exotic, all while keeping high performance? Let’s check it out!
How to get it running?
Installation methods on native platforms vary.
For Linux it boils down to one pecl command:
pecl install swoole
For MacOS, brew can be used:
brew install swoole
brew install homebrew/php/php72-swoole
And on Windows, native installation is not possible, but we should use Docker anyway.
Of course, the best idea to run the PHP and Swoole is the Docker container. Let’s see how to create a container that will allow us to use Swoole. First, we need a Dockerfile.
RUN pecl install swoole
ADD php.ini /usr/local/etc/php
RUN usermod -u 1000 www-data
That was pretty straightforward, wasn’t it?
After extending the official PHP Docker image, installing Swoole with pecl, and copying php.ini – we got it. The last line is a standard Docker fix for MacOS.
When it comes to php.ini config file which is copied, it’s even simpler – requires only one line:
What does it offer?
Swoole has a variety of functionalities. Most of them are asynchronous. Here are some of the most interesting ones (others can be found in Swoole documentation):
- TCP/UDP server and client,
- HTTP server and client,
- Websocket server and client,
- Redis protocol server and client,
- MySQL client,
Let’s see how to use three of them: HTTP server, Websocket server, and Filesystem. These are the most important ones in my opinion.
HTTP Server using Swoole
Swoole allows us to run a simple HTTP server that works asynchronously. Here’s the simple code that will also use asynchronous filesystem access to respond with an index.html file to every request it handles.
As we can see, it looks a bit like Node.
First, we create the
swoole_http_server object that resembles the HTTP server. Then, we bind two anonymous functions for two events: one for
start, which will be executed when the server is started, and another for request, which will be executed for every request. It takes a request and responses objects as parameters.
The request object contains all the data related to the request itself: requested path, headers, and so on. On the other hand, it’s used to provide output, set headers, etc. It’s worth mentioning that these two objects are not PSR conformant, but rather they are custom Swoole objects.
In the request event, an asynchronous file system is used to load the data from a file. Once the data is available, a callback is fired with the content loaded. Then, this content is used to fill the response object and close the response. This sends the data back to the browser effectively.
That looks neat and most of all – works. But what about the performance?
Have a read on npm packages:
HTTP Server benchmark
To test the performance of the HTTP server using Swoole, I’ve created an app in Node – which can do exactly the same thing as the one in Swoole – and also an NGINX server that serves index.html as a static file. All are dockerized in three separate containers.
Then, I used wrk tool to hit these containers hard. The results were astounding.
That came as a surprise.
I didn’t expect Swoole to overtake NGINX, but it actually did! Node was outdistanced. The raw power of this extension is really impressive, but it fades away with more work being done in the request.
Unfortunately, Swoole has two little drawbacks that make these raw benchmarks irrelevant. We will get to them later.
WebSocket Server using Swoole
As mentioned before, Swoole offers a method to create a WebSocket server. It works asynchronously and follows the same methodology as HTTP and other Swoole functionalities. In my opinion, it’s one of the most important Swoole parts. WebSockets in PHP? I’m in. Let’s see how it looks.
Looks similar to the HTTP server example, doesn’t it?
First, we create the
swoole_websocket_server object which resembles the WebSocket server. Then, we bind four anonymous functions to the four events:
- Start event that will work just like starting an event from an HTTP server,
- Open event that will be executed once a new WebSocket connects,
- Message event that will be executed when a WebSocket sends a message to the server,
- Close event which runs when a WebSocket disconnects.
Websockets connected to the server are identified by the unique ID that increments with each of the new WebSockets.
Problems encountered whilst using Swoole
It all worked well so far, but there were two problems I encountered during the tests of some solutions using Swoole. These were:
- no real HTTPS support in the HTTP server,
- no support for global variables in scripts.
The first one is easy to mitigate. We just need to set up a reverse proxy using NGINX or any load balancer and it’s done. But by doing that, we lose the extreme performance boost that Swoole offers.
The second one is more tricky. Swoole spawns worker processes for handling requests which means that if we create a global variable, its value is independent between threads and it can’t work. Here’s an example showing this behavior.
One would expect the response would return 0, then 1, 2, 3, and so on, but instead it always returns 0.
I reached out to the Swoole authors to check if it’s a bug, but it isn’t. In order to get the behavior we expect, we can set
worker_num = 1 in the configuration, but this decreases the performance.
gRPC knowledge galore!
Swoole, like everything, has its bright and dark sides.
I think it’s still a good idea to bring asynchronous programming to PHP. It can be used in various cases, ranging from fast prototyping to concise single-responsibility microservices, and low-latency game servers, and ending up being a backend server for big frameworks.
Do you feel like Swoole would be a perfect addition to your project but you don't want to handle this alone?
At The Software House, we have a team of very talented PHP developers who will gladly help you. In order to receive a free consultation, drop us a line. We’re waiting for your message!