@ PHP 也有 Day # 33
By Albert Chen
{
"data": {
"human": {
"name": "Albert Chen",
"occupation": "Software Engineer",
"company": "Unisharp Techonology",
"website": "http://albert-chen.com",
"interests": [
"traveling",
"programming",
"cooking"
],
"age": 25,
"relationship_status": "single"
}
}
}
Open Source
Based on Apache 2.0 Liscense
姓名:韓天峰 (Rango)
經歷:阿里巴巴集團
車輪互聯 · 總架構師
簡介:資深 PHP 程序員
Swoole 開源項目創始人
PHP官方擴展開發组成員
Written in C, extremely high performance
Event driven, non-blocking I/O
Supports TCP /UDP / UnixSock
Supports Async / Sync / Coroutine
Supports IPv4 / IPv6 network
TCP / UDP server and client
Task worker
Database connection pool
Millisecond timer
Async I/O client
Async Http / WebSocket client
Async Redis / MySQL client
Operation system: Linux, FreeBSD or MacOS
Linux kernel version >= 2.3.32
PHP version >= 5.3.10
GCC version >= 4.4 or Clang
Cmake version >= 2.4 (Cmake is required to compile libswoole as C/C++ library)
Linux
pecl install swoole
MacOS X
brew install php71-swoole
Manually Compile
# enter the directory of swoole source code
cd swoole
# prepare the build environment for a PHP extension
phpize
# add configuration paramaters as needed
./configure
# a successful result of make is swoole/module/swoole.so
make
# install the swoole into the PHP extensions directory
sudo make install
# check the php.ini file location
php -i | grep php.ini
# add the extension=swoole.so to the end of php.ini
sudo echo "extension=swoole.so" >> php.ini
# check if the swoole extension has been enabled
php -m | grep swoole
# check swoole version and modules
php --ri swoole
After install the swoole extension to the PHP extensions directory, you will need to adjust php.ini and add an extension=swoole.so line before you can use the extension.
<?php
$http = new Swoole\Http\Server("127.0.0.1", 9501);
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$http->on("request", function ($request, $response) {
$response->header("Content-Type", "text/plain");
$response->end("Hello World\n");
});
$http->start();
<?php
$http = new Swoole\Http\Server("127.0.0.1", 9501);
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$http->on("request", function ($request, $response) {
$response->header("Content-Type", "text/html");
$response->end("hello world");
});
$http->start();
var http = require('http');
var app = function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.end('hello world');
};
var server = http.createServer(app);
server.listen(9502, function() {
console.log("Server running at http://127.0.0.1:9502");
});
wrk -t4 -c400 -d10s http://127.0.0.1:9501
Running 10s test @ http://127.0.0.1:9501
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 17.16ms 26.41ms 199.98ms 86.94%
Req/Sec 12.53k 1.59k 17.51k 72.75%
499976 requests in 10.03s, 78.20MB read
Socket errors: connect 0, read 235, write 0, timeout 0
Requests/sec: 49853.41
Transfer/sec: 7.80MB
Running 10s test @ http://127.0.0.1:9502
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 30.62ms 5.28ms 144.94ms 92.94%
Req/Sec 3.14k 645.97 4.37k 85.50%
125051 requests in 10.03s, 19.20MB read
Socket errors: connect 0, read 164, write 19, timeout 0
Requests/sec: 12462.35
Transfer/sec: 1.91MB
Requests arrive main reactor
Main reactor binds request to reactor
Reactor dispatches socket to worker
Worker handles the main logic
Callback to correspondent reactor
Reactor returns result to client
<?php
$http = new Swoole\Http\Server("127.0.0.1", 9501);
$http->set([
'daemonize' => true,
'reactor_num' => 4,
'worker_num' => 4,
'task_worker_num' => 4,
'max_request' => 10000,
'task_max_request' => 10000,
'open_cpu_affinity' => true,
'log_file' => __DIR__ . '/swoole.log',
'ssl_cert_file' => $key_dir . '/ssl.crt',
'ssl_key_file' => $key_dir . '/ssl.key'
]);
<?php
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
// don't define any global variables here
});
In the callback function registered for start, it only allows to log record and modify the name of process.
The event start and workerstart happen concurrently in different processes and have no precedence.
<?php
$http->on("workerStart", function (Server $server, int $worker_id) {
echo "worker start id: {$worker_id}\n";
echo 'taskworker: ' . json_encode($server->taskworker) . "\n";
// not allowed in osx
// swoole_set_process_name('process name');
});
The event workerstart happens when the worker process or task worker process starts.
<?php
$http->on("connect", function (Server $server, int $fd, int $from_id) {
// echo json_encode($server) . "\n";
});
This event connect happens when the new connection comes. And the worker process will call the callback function registered for the event connect.
In the mode UDP, there is only receive event and there is no connect and close event.
$fd the id number of client
$from_id the id number of reactor thread
<?php
$http->on("request", function ($request, $response) {
echo json_encode($request);
// forget php magic functions
// echo json_encode($_GET);
$response->header("Content-Type", "text/html");
$response->end('Hello World!');
});
You can get the information of request here (GET, POST, etc.) and handle your response
<?php
$http->set([
'task_worker_num' => 4
]);
Send data to the task worker processes.To call this method, it needs to set the configuration of task_worker_num and the callback function of onTask and onFinish.
This method is non-blocking and return immediately after sending the task data to the pool of task worker.
<?php
$http->on("task", function (Server $server, $task_id, $from_id, $data) {
// task worker received data
echo "task received:" . json_encode($data) . "\n";
return $data;
});
$http->on("finish", function (Server $server, $task_id, $data) {
// task worker callback
echo "task finished:" . json_encode($data) . "\n";
});
global $globalServer;
$data = [
'code' => 'ok',
'error' => false,
'payload' => 'Hello World'
];
$globalServer->task($data);
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
The swoole_websocket_server class inherits from the class swoole_server.
If the swoole_websocket_server has been setted the callback function of event request, it can be used as Http server.
If the swoole_websocket_server hasn't been setted the callback function of event request and received http request, it would repond http 400 error.
<?php
use Swoole\Websocket\Server;
$server = new Server("127.0.0.1", 9503);
$server->on('open', function (Server $server, $request) {
// echo json_encode($request) . "\n";
echo "server: handshake success with fd{$request->fd}\n";
});
$server->on('message', function (Server $server, $frame) {
echo "receive from {$frame->fd}:{$frame->data}, opcode:{$frame->opcode}, fin:{$frame->finish}\n";
$server->push($frame->fd, "this is server");
});
$server->on('close', function (Server $server, $fd) {
echo "client {$fd} closed\n";
});
$server->start();
<?php
$server->on('handshake', function (Request $request, Response $response) {
// you can choose to implement your handshake method
$secWebSocketKey = $request->header['sec-websocket-key'];
$patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
$response->end();
return false;
}
echo $request->header['sec-websocket-key'];
$key = base64_encode(sha1(
$request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
true
));
$headers = [
'Upgrade' => 'websocket',
'Connection' => 'Upgrade',
'Sec-WebSocket-Accept' => $key,
'Sec-WebSocket-Version' => '13',
];
if (isset($request->header['sec-websocket-protocol'])) {
$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
}
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
$response->status(101);
$response->end();
echo "connected!" . PHP_EOL;
return true;
});
<?php
public function broadcast($server, $data)
{
$connections = $server->connections;
$sender = $data['sender'] ?? 0;
foreach($connections as $fd) {
if ($sender !== (integer) $fd) {
$server->push($fd, $message, $opcode);
}
}
}
There's no socket.io library for swoole yet, that means you need to handle every detail of websocket on your own.
<?php
use Swoole\Async;
// you can configure your async settings here.
Async::set([
'aio_mode' => SWOOLE_AIO_LINUX,
'thread_num' => 4,
]);
Async::readfile(__DIR__ . "/file.txt", function($filename, $content) {
echo "$filename: $content";
});
Read files in the async way. When the operation of reading content from file finished, the callback registered is callback automatically.
The max length of file is 4M.
<?php
use Swoole\Async;
// If the aio mode setted by swoole_async_set is SWOOLE_AIO_BASE,
// the method can't support append content to the end of file and
// must set the value of $fileContent to integer multiples of 4096.
Async::writeFile(__DIR__ . "/file.log", "test\n", function ($filename) {
echo "wirte ok.\n";
}, $append = false);
Write file in the async way. When the operation of writing content to file finished, the callback registered is callback automatically.
<?php
use Swoole\Async;
Async::read(__DIR__ . "/file.txt", function ($filename, $content) {
echo "file: $filename\ncontent-length: " . strlen($content) . "\nContent: $content\n";
if (empty($content)) {
echo "file is end.\n";
return false;
} else {
return true;
}
}, 8192);
Read the file content stream in the async way. When the operation of reading content from file finished, the callback registered is callback automatically.
<?php
use Swoole\Async;
for ($i = 0; $i < 10; $i++) {
Async::write(__DIR__ . "/file.log", "A\n", -1, function ($file, $writen) {
echo "write $file [$writen]\n";
//return true: write contine. return false: close the file.
return true;
});
}
Write file stream in the async way. When the operation of writing content to file finished, the callback registered is callback automatically.
<?php
$a = sum(1, 2);
$b = sum($a, 3);
$c = sum($b, 4);
var_dump(array($a, $b, $c));
<?php
async_sum(1, 2, function($a) {
async_sum($a, 3, function($b) use ($a) {
async_sum($b, 4, function($c) use ($a, $b) {
var_dump(array($a, $b, $c));
});
});
});
<?php
use Swoole\Coroutine\MySQL;
$mysql = new Swoole\Coroutine\MySQL();
$mysql->connect([
'host' => '127.0.0.1',
'user' => 'user',
'password' => 'pass',
'database' => 'test',
]);
$result = $mysql->query('select sleep(1)');
// your business logic
<?php
use Swoole\Coroutine\MySQL;
use Swoole\Coroutine\Redis;
$redis = new Redis;
$redis->connect('127.0.0.1', 6379);
$redis->setDefer();
$redis->get('key');
$mysql = new MySQL;
$mysql->connect(['host' => '127.0.0.1', 'user' => 'user', 'password' => 'pass', 'database' => 'test']);
$mysql->setDefer();
$mysql->query('select sleep(1)');
$redis_res = $redis->recv();
$mysql_res = $mysql->recv();
// your business logic
Redis sent packet
Redis received packet
MySQL sent packet
MySQL received packet
Redis sent packet
Redis received packet
MySQL sent packet
MySQL received packet
<?php
use Swoole\Http\Server;
$http = new Server("127.0.0.1", 9501);
$http->on("start", function ($server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$i = 1;
$http->on("request", function ($request, $response) {
global $i;
$response->header("Content-Type", "text/html");
$response->end($i);
$i++;
});
$http->start();
Swoole table is a high performance memory management module, implemented based on shared memory and spin lock.
In order to keep the data synchronization, you have to use Swoole\Lock if the memory is modified and accessed by multiple threads or processes.
<?php
use Swoole\Table;
// create table
// columns size
$table = new Table(1024);
// 1, 2, 4, 8
$table->column('id', swoole_table::TYPE_INT, 4);
$table->column('name', swoole_table::TYPE_STRING, 64);
$table->column('num', swoole_table::TYPE_FLOAT);
$table->create();
// set column
$table->set('tianfenghan@qq.com', array('id' => 145, 'name' => 'rango', 'num' => 3.1415));
$table->set('350749960@qq.com', array('id' => 358, 'name' => "Rango1234", 'num' => 3.1415));
// get column
$ret1 = $table->get('350749960@qq.com');
// delete column
$table->del('350749960@qq.com');
<?php
$count = 0;
$pool = new SplQueue();
$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
$server->on('Request', function($request, $response) use(&$count, $pool) {
if (count($pool) == 0) {
$redis = new Swoole\Coroutine\Redis();
$res = $redis->connect('127.0.0.1', 6379);
if ($res == false) {
$response->end("redis connect fail!");
return;
}
$pool->push($redis);
$count++;
}
$redis = $pool->pop();
$ret = $redis->set('key', 'value');
$response->end("swoole response is ok, count = $count, result=" . var_export($ret, true));
$pool->push($redis);
});
$server->start();
pool_server start
pool_server stop
pool_server restart
pool_server status
<?php
// redis
$obj = new redisProxy();
$rs = $obj->connect("192.168.20.130");
$obj->select(5);
$obj->set("test", '1111');
var_dump($obj->get("test"));
$obj->release();
// pdo
$obj1 = new pdoProxy('mysql:host=192.168.20.131;dbname=db1', "admin", "admin");
$rs = $obj1->query("show tables");
var_dump($rs->fetchAll());
$obj1->release();
// aync pdo
$obj2 = new asyncPdoProxy('mysql:host=192.168.1.19;dbname=mz_db', "public_user", "1qa2ws3ed");
$stmt = $obj2->prepare("select * from mz_account where user_id=:user_id");
$stmt->bindParam(':user_id', "311");
$stmt->execute(function($stmt, $ret) {
$data = $stmt->fetchAll();
var_dump($data);
$stmt->release();
});
Current related projects on github
Implementations
public function onRequest(Request $request, Response $response)
{
$illuminateRequest = IlluminateRequest::swooleCapture($request);
$illuminateResponse = $this->runApplication($illuminateRequest);
$swooleResponse = ResponseFactory::createFromIlluminate(
$response,
$illuminateResponse
);
$swooleResponse->send();
}
wrk -t4 -c400 -d10s http://lumen51.app:9999
Running 10s test @ http://lumen51.app:9999
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 692.27ms 147.64ms 1.51s 85.77%
Req/Sec 44.84 28.67 160.00 72.78%
1679 requests in 10.05s, 2.52MB read
Socket errors: connect 0, read 300, write 0, timeout 21
Non-2xx or 3xx responses: 41
Requests/sec: 167.04
Transfer/sec: 256.68KB
Running 10s test @ http://localhost:1215
4 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 22.38ms 5.88ms 116.88ms 77.94%
Req/Sec 4.18k 446.00 5.10k 74.25%
167130 requests in 10.06s, 247.37MB read
Socket errors: connect 0, read 226, write 0, timeout 0
Requests/sec: 16616.24
Transfer/sec: 24.59MB
Swoole Framework
shenzhe/zphp
keaixiaou/zapi
hprose/hprose-php
bixuehujin/blink
swoft-cloud/swoft
tmtbe/SwooleDistributed