# swoole_server高级特性
---
[TOC=2,3]
---
## **1.改变Worker进程的用户/组**
在某些情况下,主进程需要使用Root来启动,比如需要监听80端口。这时Worker进程的业务代码也会运行在root用户下,这是非常不安全的。 业务代码的漏洞可能会导致整个服务器被攻破,所以需要将Worker进程所属用户和组改为其他用户。 在PHP中使用posix系列函数即可完成此操作。可在swoole的[onWorkerStart](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#3onworkerstart)回调中加入以下代码:
```php
$user = posix_getpwnam('www-data');
posix_setuid($user['uid']);
posix_setgid($user['gid']);
```
另外,在PHP代码中访问/etc/目录,就是指文件系统的/etc/,这样是不安全的。比如PHP代码中误操作执行**rm -rf /**。会带来严重的后果。 可以使用chroot函数,将根目录重定向到另外一个安全的目录。
```php
chroot('/tmp/root/');
```
## **2.回调函数中的from_id和fd**
- from_id是来自于哪个reactor线程
- fd是tcp连接的文件描述符
调用swoole_server_send/swoole_server_close函数需要传入这两个参数才能被正确的处理。如果业务中需要发送广播,可以先通过[connection_list](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverconnection_list)获取全部连接的fd,然后通过[connection_info](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverconnection_info)查询到fd对应的from_id。
## **3.Buffer和EOF_Check的使用**
在外网通信时,有些客户端发送数据的速度较慢,每次只能发送一小段数据。这样[onReceive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#1onreceive)到的数据就不是一个完整的包。 还有些客户端是逐字节发送数据的,如果每次回调[onReceive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#1onreceive)会拖慢整个系统。<br>
Swoole提供了buffer和eof_check的功能,在C扩展底层检测到如果不是完整的请求,会等待新的数据到达,组成完成的请求后再回调[onReceive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#1onreceive)。<br>
在[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)中增加,[open_eof_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#13open_eof_check)和[package_eof](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#14package_eof-)来开启此功能。[open_eof_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#13open_eof_check)=1表示启用buffer检查,[package_eof](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#14package_eof-)设置数据包结束符<br>
> buffer功能会将所有收到的数据放到内存中,会占用较多内存<br>
通过设置[package_max_length](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#19package_max_length)<br> 来设定每个连接最大缓存多少数据,超过此大小的连接将会被关闭<br>
```php
swoole_server_set($serv, array(
'package_eof' => "\r\n\r\n", //http协议就是以\r\n\r\n作为结束符的,这里也可以使用二进制内容
'open_eof_check' => 1,
));
```
## **4.固定包头+包体协议自动分包**
swoole-1.7.3版本重构了length_check特性的代码,对于固定包头+包体格式的协议可以直接在master进程中进行分包和组包,worker进程中可以一次性收到一个完整的包。带来的好处是:<br>
- C扩展层进行协议的处理,性能最佳,原PHP代码虽然也可以实现协议处理,但需要耗费较多CPU
- TCP连接与业务逻辑分离,有效利用所有Worker进程,即使只有1个TCP连接,也可以利用所有Worker
使用时,仅需打开[open_length_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#15open_length_check)配置项,并设置相应的配置选项即可。<br>
相关配置项查看[swoole_server配置选项](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server配置选项.md)第15项到第19项
## **5.Worker与Reactor通信模式**
Worker进程如何与Reactor进程通信,Swoole提供了3种方式。通过[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)参数中修改[dispatch_mode](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#5dispatch_mode)的值来配置。<br>
| dispatch_mode | 名称 | 介绍 |
| ---- | ----- | ---- |
| 1 | 轮询模式 | 收到的请求数据包会轮询发到每个Worker进程。 |
| 2 | FD取模 | 数据包根据fd的值%worker_num来分配,这个模式可以保证一个TCP客户端连接发送的数据总是会被分配给同一个worker进程。 |
| 3 | Queue模式 | 此模式下,网络请求的处理是抢占式的,这可以保证总是最空闲的worker进程才会拿到请求去处理,缺点是客户端连接对应的worker是随机的。不确定哪个worker会处理请求。无法保存连接状态(可借助第三方库或者[swoole_table](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/13.swoole_table.md)实现)|
## **6.TCP-Keepalive死连接检测**
在TCP中有一个Keep-Alive的机制可以检测死连接,应用层如果对于死链接周期不敏感或者没有实现心跳机制,可以使用操作系统提供的keepalive机制来踢掉死链接。 在[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)中增加[open_tcp_keepalive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#24open_tcp_keepalive)=>1表示启用tcp keepalive。 另外,有3个选项可以对keepalive的细节进行调整。<br>
| 名称 | 介绍 |
| ---- | ----- |
| tcp_keepidle | 单位秒,连接在n秒内没有数据请求,将开始对此连接进行探测 |
| tcp_keepcount | 探测的次数,超过次数后将close此连接 |
| tcp_keepinterval | 探测的间隔时间,单位秒 |
## **7.TCP服务器心跳维持方案**
正常情况下客户端中断TCP连接时,会发送一个FIN包,进行4次断开握手来通知服务器。但一些异常情况下,如客户端突然断电断网或者网络异常,服务器可能无法得知客户端已断开连接。<br>
尤其是移动网络,TCP连接非常不稳定,所以需要一套机制来保证服务器和客户端之间连接的有效性。<br>
Swoole扩展本身内置了这种机制,开发者只需要配置两个个参数即可启用。Swoole在每次收到客户端数据会记录一个时间戳,当客户端在一定时间内未向服务器端发送数据,那服务器会自动切断连接。<br>
```php
$serv->set(array(
'heartbeat_check_interval' => 5,
'heartbeat_idle_time' => 10
));
```
上面的设置就是每5秒侦测一次心跳,一个TCP连接如果在10秒内未向服务器端发送数据,将会被切断。[点此查看参数说明](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#11heartbeat_check_interval)<br>
使用[swoole_server::heartbeat()](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverheartbeat)函数手工检测心跳是否到期。此函数会返回闲置时间超过[heartbeat_idle_time](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#12heartbeat_idle_time)的所有TCP连接。程序中可以将这些连接做一些操作,如发送数据或关闭连接。<br>
## **8.多端口监听的使用**
Swoole提供了多端口监听的机制,这样可以同时监听UDP和TCP,同时监听内网地址和外网地址。内网地址和端口用于管理,外网地址用于对外服务。<br>
[点此查看教程](https://github.com/LinkedDestiny/swoole-doc/blob/master/04.Swoole%E5%A4%9A%E7%AB%AF%E5%8F%A3%E7%9B%91%E5%90%AC%E3%80%81%E7%83%AD%E9%87%8D%E5%90%AF%E4%BB%A5%E5%8F%8ATimer%E8%BF%9B%E9%98%B6%EF%BC%9A%E7%AE%80%E5%8D%95crontab.md#1%E5%A4%9A%E7%AB%AF%E5%8F%A3%E7%9B%91%E5%90%AC)<br>
## **9.捕获Server运行期致命错误**
Server运行期一旦发生致命错误,那客户端连接将无法得到回应。如Web服务器,如果有致命错误应当向客户端发送Http 500 错误信息。<br>
在PHP中可以通过register_shutdown_function + error_get_last 2个函数来捕获致命错误,并将错误信息发送给客户端连接。具体代码示例如下:<br>
```php
register_shutdown_function('handleFatal');
function handleFatal()
{
$error = error_get_last();
if (isset($error['type']))
{
switch ($error['type'])
{
case E_ERROR :
case E_PARSE :
case E_DEPRECATED:
case E_CORE_ERROR :
case E_COMPILE_ERROR :
$message = $error['message'];
$file = $error['file'];
$line = $error['line'];
$log = "$message ($file:$line)\nStack trace:\n";
$trace = debug_backtrace();
foreach ($trace as $i => $t)
{
if (!isset($t['file']))
{
$t['file'] = 'unknown';
}
if (!isset($t['line']))
{
$t['line'] = 0;
}
if (!isset($t['function']))
{
$t['function'] = 'unknown';
}
$log .= "#$i {$t['file']}({$t['line']}): ";
if (isset($t['object']) && is_object($t['object']))
{
$log .= get_class($t['object']) . '->';
}
$log .= "{$t['function']}()\n";
}
if (isset($_SERVER['REQUEST_URI']))
{
$log .= '[QUERY] ' . $_SERVER['REQUEST_URI'];
}
error_log($log);
$serv->send($this->currentFd, $log);
}
}
}
```
## **10.SSL隧道加密TCP-Server**
1.7.4后swoole增加了对SSL隧道加密的支持,在swoole_server中可以启用SSL证书加密。使用仅需增加[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)的配置即可,并将listener端口的类型,增加[SWOOLE_SSL](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serveraddlistener)标志。<br>
```php
$serv = new swoole_server("0.0.0.0", 443, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); // 加密端口
$key_dir = dirname(dirname(__DIR__)).'/tests/ssl';
$serv->addlistener('0.0.0.0', 80, SWOOLE_SOCK_TCP); // 非加密端口
$serv->set(array(
'worker_num' => 4,
'ssl_cert_file' => $key_dir.'/ssl.crt',
'ssl_key_file' => $key_dir.'/ssl.key',
));
```
配置项说明[点此查看](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#23ssl_cert_file%E5%92%8Cssl_key_file).<br>
- swoole简介
- swoole功能概述
- 序章
- 开发必读
- 1 环境搭建
- 1.1 环境搭建
- 1.2 搭建Echo服务器
- 2 初识Swoole
- 2.1 Worker进程
- 2.2 TaskWorker进程
- 2.3 Timer定时器
- 2.4 Process进程
- 2.5 Table内存表
- 2.6 多端口监听
- 2.7 sendfile文件支持
- 2.8 SSL支持
- 2.9 热重启
- 2.10 http_server
- 附录*server配置
- 附录*server函数
- 附录*server属性
- 附录*server回调函数
- 附录*server高级特性
- 心跳检测
- 3 Swoole协议
- 3.1 EOF协议
- 3.2 固定包头协议
- 3.3 Http协议
- 3.4 WebSocket协议
- 3.5 MTQQ协议
- 内置http_server
- 内置websocket_server
- Swoole\Redis\Server
- 4 Swoole异步IO
- 4.1 AsyncIO
- 异步文件系统IO
- swoole_async_readfile
- swoole_async_writefile
- swoole_async_read
- swoole_async_write
- 5 swoole异步客户端
- ws_client
- http_client
- mysql_client
- redis_client
- tcp_client
- http2_client
- 6 swoole协程
- Swoole\Coroutine\Http\Client
- Swoole\Coroutine\MySQL
- Swoole\Coroutine\Redis
- Coroutine\PostgreSQL
- Swoole\Coroutine\Client
- Swoole\Coroutine\Socket
- Swoole\Coroutine\Channel
- Coroutine
- Swoole\Coroutine::create
- Swoole\Coroutine::resume
- Swoole\Coroutine::suspend
- Swoole\Coroutine::sleep
- Coroutine::getaddrinfo
- Coroutine::gethostbyname
- swoole_async_dns_lookup_coro
- Swoole\Coroutine::getuid
- getDefer
- setDefer
- recv
- Coroutine::stats
- Coroutine::fread
- Coroutine::fget
- Coroutine::fwrite
- Coroutine::readFIle
- Coroutine::writeFIle
- Coroutine::exec
- 7 swoole_process
- process::construct
- process::start
- process::name
- process::signal
- process::setaffinity
- process::exit
- process::kill
- process::daemon
- process->exec
- process::wait
- process::alarm
- 8 swoole定时器
- swoole_timer_tick
- swoole_timer_after
- swoole_timer_clear
- 9 swoole_event
- swoole_event_add
- swoole_event_set
- swoole_event_del
- swoole_event_wait
- swoole_event_defer
- swoole_event_write
- swoole_event_exit
- swoole提供的function
- 常见问题
- 客户端链接失败原因
- 如何设置进程数
- 如何实现异步任务
- 如何选择swoole三种模式
- php中哪些函数是阻塞的
- 是否可以共用1个redis或mysql连接
- 如何在回调函数中访问外部的变量
- 为什么不要send完后立即close
- 不同的Server程序实例间如何通信
- MySQL的连接池、异步、断线重连
- 在php-fpm或apache中使用swoole
- 学习Swoole需要掌握哪些基础知识
- 在phpinfo中有在php-m中没有
- 同步阻塞与异步非阻塞选择
- CURL发送POST请求服务器端超时
- 附录
- 预定义常量
- 内核参数调优
- php四种回调写法
- 守护进程程序常用数据结构
- swoole生命周期
- swoole_server中内存管理机制
- 使用jemalloc优化swoole内存分配性能
- Reactor、Worker、Task的关系
- Manager进程
- Swoole的实现
- Reactor线程
- 安装扩展
- swoole-worker手册
- swoole相关开源项目
- 写在后面的话
- 版本更新记录
- 4.0