Swoole允许通过PHP构建一个全新的Server,用于创建一个异步服务器程序,监听请求并做出响应。
- 支持TCP、UDP、UnixSocket三种协议
- 支持IPv4、IPv6
- 支持SSL/TLS单向双向证书的隧道加密
使用注意
- Swoole Server是一个独立的服务,只能用于
php-cli
环境,在其他环境下会抛出致命错误。 - 不要在Swoole Server创建之前调用其它异步IO的API,否则将会创建失败。
运行流程
- 构建Server对象
- 设置运行时参数
- 注册事件回调函数
- 启动服务器
构建Server对象
原型
swoole_server(string $host, int $port = 0, int $mode = SWOOLE_PROCESS, int $socket_type = SWOOLE_SOCK_TCP)
参数
-
string $host
服务器主机地址 -
int $port
服务器端口号 -
int $mode
服务器运行模式,支持SWOOLE_PROCESS
默认多进程模式、SWOOLE_BASE
单线程模式 -
int $socket_type
套接字类型,支持TCP
、UDP
、UDP6
、UnixSocket
、Stream/Dgram
六种
$serv = new Swoole\Server("0.0.0.0", 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);
服务器主机地址string $host
Server的创建需要绑定监听的IP地址和端口,如果IP地址指定为127.0.0.1
表示客户端只能位于本机才能连接,其它计算机将无法连接。如果需要所有客户端都能连接则需要设置为0.0.0.0
。
服务器主机端口int $port
端口号的取值范围是[1, 655353],一般1000以下的端口都会被常用服务所占用。
Server绑定端口号时需要注意端口号是否已经被占用,如果端口被占用需要更改为其它端口。
查看端口是否被占用
$ ss -lntpd | grep :22
tcp LISTEN 0 0 *:22 *:*
tcp LISTEN 0 0 :::22 :::*
tcp LISTEN 0 0 *:22 *:*
tcp LISTEN 0 0 :::22 :::*
$ netstat -tnlp | grep :22
netstat: showing only processes with your user ID
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 :::22 :::* LISTEN -
将某个服务通过某个端口对外提供服务的行为称为监听。例如Apache监听本机80端口,如果客户端通过80端口发送请求过来,操作系统会将请求转交给Apache,Apache就可以根据请求的具体内容进行处理并做出响应。
服务器运行模式 int $mode
多线程模式
多线程Worker模式中Reactor线程用来处理网络事件循环并读取数据,得到的请求会交给Worker线程去处理。当一个线程发生内存错误时整个进程会全部结束。由于PHP的ZenVM虚拟机在多线程模式下存在内存错误,多线程模式在Swoole1.6.0版本以后就关闭了。
多进程模式 SWOOLE_PROCESS
多进程模式用于大量的进程间通信、进程管理机制。适合业务逻辑非常复杂的场景,Swoole提供了完善的进程管理、内存保护机制,在业务逻辑非常复杂的情况下可以长期稳定的运行。
Swoole在Reactor线程中提供了Buffer功能,可以应对大量慢速连接和逐字节的恶意客户端。另外提供了CPU亲和设置选项,使程序运行效率更佳。
多进程模式的优点
- 连接与数据请求发送是分离的,不会因为某些连接数据量大或某些连接数据量小而导致Worker进程不均衡。
- Worker进程发送致命错误时连接并不会被切断
- 可实现单连接并发,仅保持少量TCP连接,请求可并发地在多个Worker进程中处理。
多进程模式的缺点
- 存在2次IPC开销,Master进程与Worker进程需要使用UnixSocket进行通信。
- 不支持某些高级功能,如
sendwait
、pause
、resume
等操作。
多线程模式操作流程
注意:不考虑TaskWorker进程的情况下
- Master进程fork创建Manager进程
- Master进程
pthread_create
创建Reactor线程
注意这里的顺序,Master进程不能先pthread_create
创建Reactor线程然后再fork
创建Manager进程,因为fork
时只能复制一个线程。 - Manager进程
fork
创建Worker进程
Master进程fork
创建的Manager进程、Manager进程fork
创建的Worker进程,都会继承Master中已经打开的文件描述符。 - Master进程先创建多个套接字对,用于和Worker进程通信。
多进程模式请求处理流程
Swoole的Master进程与Worker进程之间的通信流程
- Master进程创建N个套接字对
- Master进程fork创建Manager管理进程
- Manager进程fork创建Worker进程
- Master进程与Worker进程都有
sv[1] - sv[2n+2]
共2n+2
个文件描述符,也就是fork
出的子进程会自动继承父进程的文件描述符。 - Master进程会与Worker进程进行通信,Worker进程之间不会相互通信。
单线程模式SWOOLE_BASE
单线程模式是传统的异步非阻塞Server,与Nginx和Node.js等程序完全是一致的。单线程模式中会在事件循环中直接调用PHP的函数,而不是使用dispatch
投递任务。如果回调函数中有阻塞操作会导致Server退化为同步模式。单线程模式下worker_num
配置参数仍然有效,它会启动多个Worker进程。
单线程模式的特点
- 单线程模式下没有Master进程的角色
- 每个Worker进程同时承担了Process模式下Reactor线程和Worker进程两部分职责
- 单线程模式下Manager进程是可选的,当设置了
worker_num=1
且没有使用Task和MaxRequest特性时,底层将会直接创建一个单独的Worker进程,而不会创建Manager进程。
单线程模式的优点
- 没有IPC开销性能更好
- 代码更简单不容易出错
单线程模式的缺点
- TCP连接是在Worker进程中维持的
当某个Worker进程挂掉时此Worker内的所有连接都将被关闭 - 少量TCP长连接无法利用到所有Worker进程
- TCP连接与Worker是绑定的
长连接应用中某些连接的数量大,这些连接所在的Worker进程负载会非常高,但是某些连接数据量小时。某些连接数量小时所在的Worker进程的负载会非常低,不同的Worker进程无法实现均衡。
单线程模式适用场景
如果客户端连接之间不需要交互,可以使用单线程模式,如Memcache、HTTP服务器等。
例如:
服务器
$ vim server.php
<?php
class Server
{
private $server;
public function __construct($host = "0.0.0.0", $port = 9501, $configs = [])
{
//创建服务器对象
$this->server = new swoole_server($host, $port);
//设置服务器运行时参数
$this->server->set($configs);
//注册监听函数
$this->server->on("Start", [$this, "onStart"]);
$this->server->on("Connect", [$this, "onConnect"]);
$this->server->on("Receive", [$this, "onReceive"]);
$this->server->on("Close", [$this, "onClose"]);
$this->server->on("Task", [$this, "onTask"]);
$this->server->on("Finish", [$this, "onFinish"]);
//启动服务器
$this->server->start();
}
public function onStart(swoole_server $server)
{
$version = SWOOLE_VERSION;
$master_pid = $server->master_pid;
$manager_pid = $server->manager_pid;
echo "[start] version:{$version} master:{$master_pid} manager:{$manager_pid}".PHP_EOL;
echo json_encode($server).PHP_EOL;
}
public function onConnect(swoole_server $server, $fd, $reactor_id)
{
echo "[connect] reactor:{$reactor_id} client:{$fd}".PHP_EOL;
echo json_encode($server).PHP_EOL;
}
public function onReceive(swoole_server $server, $fd, $reactor_id, $data)
{
echo PHP_EOL."[receive] reactor:{$reactor_id} client:{$fd} data:{$data}".PHP_EOL;
echo json_encode($server).PHP_EOL;
//send a task to task worker process
$param = [];
$param["fd"] = $fd;
//start a task
$message = json_encode($param);
$server->task($message);
echo "[receive] continue handle worker".PHP_EOL;
}
public function onClose(swoole_server $server, $fd)
{
echo "[close] client:{$fd}".PHP_EOL;
echo json_encode($server).PHP_EOL;
}
public function onTask(swoole_server $server, $task_id, $reactor_id, $data)
{
echo "[task] task:{$task_id} reactor:{$reactor_id} data:{$data}".PHP_EOL;
echo json_encode($server).PHP_EOL;
for($i=0; $i<3; $i++){
sleep(1);
echo "[task] task {$task_id} handle {$i} times...".PHP_EOL;
}
$param = json_decode($data, true);
$fd = $param["fd"];
$message = "task {$task_id} success";
$server->send($fd, $message);
return "task {$task_id} finished";
}
public function onFinish(swoole_server $server, $task_id, $data)
{
echo "[finish] task:{$task_id} data:{$data}".PHP_EOL;
echo json_encode($server).PHP_EOL;
}
}
$host = "0.0.0.0";
$port = 9501;
$configs = [];
$configs["worker_num"] = 8;
$configs["task_worker_num"] = 8;
$configs["daemonize"] = false;
$configs["max_request"] = 1000;
$configs["log_file"] = "./swoole.log";
$server = new Server($host, $port, $configs);
客户端
$ vim client.php
<?php
class Client
{
private $client;
public function __construct($host, $port, $timeout=1)
{
$this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$this->client->on("Connect", [$this, "onConnect"]);
$this->client->on("Receive", [$this, "onReceive"]);
$this->client->on("Close", [$this, "onClose"]);
$this->client->on("Error", [$this, "onError"]);
if(!$this->isConnected()){
$this->connect($host, $port, $timeout);
}
}
public function isConnected()
{
return $this->client->isConnected();
}
public function connect($host, $port, $timeout)
{
$fp = $this->client->connect($host, $port, $timeout);
if(!$fp)
{
echo "[connect] errno:{$fp->errCode} errmsg:{$fp->errMsg}".PHP_EOL;
return;
}
}
public function send($message)
{
$this->client->send($message);
}
public function onConnect(swoole_client $client)
{
fwrite(STDOUT, "send: ");
swoole_event_add(STDIN, function(){
fwrite(STDOUT, "send: ");
$msg = trim(fgets(STDIN));
$this->send($msg);
});
}
public function onClose(swoole_client $client)
{
echo "[close]".PHP_EOL;
}
public function onReceive(swoole_client $client, $data)
{
echo "[receive] {$data}".PHP_EOL;
}
public function onError(swoole_client $client)
{
echo "[error]".PHP_EOL;
}
}
$host = "127.0.0.1";
$port = 9501;
$timeout = 1;
$client = new Client($host, $port, $timeout);
运行服务器
$ php server.php
[start] version:4.3.2-alpha master:264 manager:265
查看当前服务器对象的值
{
"setting": {
"worker_num": 8,
"task_worker_num": 8,
"daemonize": false,
"max_request": 1000,
"log_file": "./swoole.log",
"buffer_output_size": 2097152,
"max_connection": 100000
},
"connections": {},
"host": "0.0.0.0",
"port": 9501,
"type": 1,
"mode": 2,
"ports": [
{
"host": "0.0.0.0",
"port": 9501,
"type": 1,
"sock": 4,
"setting": {
"worker_num": 8,
"task_worker_num": 8,
"daemonize": false,
"max_request": 1000,
"log_file": "./swoole.log"
},
"connections": {}
}
],
"master_pid": 264,
"manager_pid": 265,
"worker_id": -1,
"taskworker": false,
"worker_pid": 0
}
运行客户端
$ php client.php
send:
此时客户端处于输入等待状态,此时在查看服务端打印输入信息为
$ php server.php
[start] version:4.3.2-alpha master:264 manager:265
[connect] reactor:0 client:1
此时服务器对象与之前的区别
{
"setting": {
"worker_num": 8,
"task_worker_num": 8,
"daemonize": false,
"max_request": 1000,
"log_file": "./swoole.log",
"buffer_output_size": 2097152,
"max_connection": 100000
},
"connections": {},
"host": "0.0.0.0",
"port": 9501,
"type": 1,
"mode": 2,
"ports": [
{
"host": "0.0.0.0",
"port": 9501,
"type": 1,
"sock": 4,
"setting": {
"worker_num": 8,
"task_worker_num": 8,
"daemonize": false,
"max_request": 1000,
"log_file": "./swoole.log"
},
"connections": {}
}
],
"master_pid": 264,
"manager_pid": 265,
"worker_id": 1,
"taskworker": false,
"worker_pid": 276
}
对比发现,当前的服务器对象中
"worker_id": 1,
"worker_pid": 276
客户端向服务端发送消息
$ php client.php
send: hello
send:
观察服务器对象的变化
$ php server.php
[start] version:4.3.2-alpha master:264 manager:265
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":-1,"taskworker":false,"worker_pid":0}
[connect] reactor:0 client:1
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":1,"taskworker":false,"worker_pid":276}
[receive] reactor:0 client:1 data:hello
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":1,"taskworker":false,"worker_pid":276}
[receive] continue handle worker
[task] task:0 reactor:1 data:{"fd":1}
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":8,"taskworker":true,"worker_pid":267}
[task] task 0 handle 0 times...
[task] task 0 handle 1 times...
[task] task 0 handle 2 times...
[finish] task:0 data:task 0 finished
{"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log","buffer_output_size":2097152,"max_connection":100000},"connections":{},"host":"0.0.0.0","port":9501,"type":1,"mode":2,"ports":[{"host":"0.0.0.0","port":9501,"type":1,"sock":4,"setting":{"worker_num":8,"task_worker_num":8,"daemonize":false,"max_request":1000,"log_file":".\/swoole.log"},"connections":{}}],"master_pid":264,"manager_pid":265,"worker_id":1,"taskworker":false,"worker_pid":276}
未完待续...