# 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>