参考于Git地址:https://github.com/LinkedDestiny/swoole-doc
在实际运用场景中,服务器可能需要监听不同host下的不同端口。比如,一个应用服务器,可能需要监听外网的服务端口,同时也需要监听内网的管理端口。在Swoole中,可以轻松的实现这样的功能。
Swoole提供了[addlistener]函数用于给服务器添加一个需要监听的host及port,并指定对应的Socket类型(TCP,UDP,Unix Socket以及对应的IPv4和IPv6版本)。
```php
$serv = new swoole_server("192.168.1.1", 9501); // 监听外网的9501端口
$serv->addlistener("127.0.0.1", 9502 , SWOOLE_TCP); // 监听本地的9502端口
$serv->start(); // addlistener必须在start前调用
```
此时,swoole_server就会同时监听两个host下的两个端口。这里要注意的是,来自两个端口的数据会在同一个onReceive中获取到,这时就要用到swoole的另一个成员函数[connection_info]通过这个函数获取到fd的from_port,就可以判定消息的类型。
```php
$info = $serv->connection_info($fd, $from_id);
//来自9502的内网管理端口
if($info['from_port'] == 9502) {
$serv->send($fd, "welcome admin\n");
} else {
//来自外网
$serv->send($fd, 'Swoole: '.$data);
}
```
所谓热重启,就是当服务器相关代码有所变动之后,无需停止服务,而是在服务器仍然运行的状态下更新文件。Swoole通过内置的reload函数以及两个自定义信号量实现了这一功能。
首先我讲解一下Swoole可用的三个信号:SIGTERM,SIGUSR1,SIGUSR2。SIGTERM用于停止服务器,SIGUSR1用于重启全部的Worker进程,SIGUSR2用于重启全部的Task Worker进程。
那要如何实现热更新代码文件呢?Swoole的回调函数中有这个一个回调[onWorkerStart]该回调会在Worker进程启动时被调用。因此,当swoole_server收到SIGUSR1信号并重启全部Worker进程后,onWorkerStart就会被调用。如果在onWorkerStart中require全部的代码文件,每次onWorkerStart后都会重新require一次php文件,这样就能实现代码文件的热更新。
```php
public function onStart( $serv ) {
cli_set_process_title("reload_master");
}
public function onWorkerStart( $serv , $worker_id) {
require_once "reload_page.php";
Test(); // reload_page.php中定义的一个函数
}
```
首先,在[onStart]回调函数中通过php的cli_set_process_title函数设置进程名。
在[onWorkerStart]中,require相关的php文件。
然后,新建一个reload.sh文件,输入如下内容:
```shell
echo "Reloading..."
cmd=$(pidof reload_master)
kill -USR1 "$cmd"
echo "Reloaded"
```
这样,就可以通过执行这个脚本重启服务器了。
在swoole-1.7.7stable版本中,Timer新增了一个函数[after]在swoole-1.7.7stable版本中,Timer新增了一个函数[after]
```php
$serv->after( 1000 , array($this, 'onAfter') , $str );
```
这里指定在1000ms后,执行onAfter回调函数,函数参数为$str。
举个例子,比如服务器要求在收到某个请求后,在30S后向所有用户发起推送。这样的需求就可以直接用after函数来实现。
协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。Swoole可以为每一个请求创建对应的协程,根据IO的状态来合理的调度协程,这会带来了以下优势:
开发者可以无感知的用同步的代码编写方式达到异步IO的效果和性能,避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护。
同时由于swoole是在底层封装了协程,所以对比传统的php层协程框架,开发者不需要使用yield关键词来标识一个协程IO操作,所以不再需要对yield的语义进行深入理解以及对每一级的调用都修改为yield,这极大的提高了开发效率。
协程API目前针对了TCP,UDP等主流协议client的封装,包括:
可以满足大部分开发者的需求。对于私有协议,开发者可以使用协程的TCP或者UDP接口去方便的封装。
$http = new Swoole_http_server('0.0.0.0', 8001);
$http->on('request', function($request, $response){
//获取redis里面的key的内容,然后输出浏览器
$redis = new Swoole\Coroutine\Redis();
$redis->connect('localhost', 6379);
$value = $redis->get($request->get['a']);
$response->header("Content-Type", "text/plain");
$response->end($value);
});
$http->start();
$http = new swoole_http_server("127.0.0.1", 9501);
$http->on("request", function ($request, $response) {
$client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
$client->connect("127.0.0.1", 8888, 0.5);
//调用connect将触发协程切换
$client->send("hello world from swoole");
//调用recv将触发协程切换
$ret = $client->recv();
$response->header("Content-Type", "text/plain");
$response->end($ret);
$client->close();
});
$http->start();
当代码执行到connect()和recv()
函数时,swoole会触发进行协程切换,此时swoole可以去处理其他的事件或者接受新的请求。当此client连接
成功或者后端服务回包
后,swoole server会恢复协程上下文,代码逻辑继续从切换点开始恢复执行。开发者整个过程不需要关心整个切换过程。具体使用可以参考client的文档。