This repository has been archived by the owner on Sep 1, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
78 changed files
with
2,294 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# sleep/usleep的影响 | ||
|
||
在异步IO的程序中,__不得使用sleep/usleep/time_sleep_until/time_nanosleep__。(下文中使用`sleep`泛指所有睡眠函数) | ||
|
||
* `sleep`函数会使进程陷入睡眠阻塞 | ||
* 直到指定的时间后操作系统才会重新唤醒当前的进程 | ||
* `sleep`过程中,只有信号可以打断 | ||
* 由于`Swoole`的信号处理是基于`signalfd`实现的,所以即使发送信号也无法中断`sleep` | ||
|
||
`Swoole`提供的`swoole_event_add`、`swoole_timer_tick`、`swoole_timer_after`、`swoole_process::signal`、`异步swoole_client` 在进程sleep后会停止工作。`swoole_server`也无法再处理新的请求。 | ||
|
||
|
||
实例程序 | ||
---- | ||
```php | ||
$serv = new swoole_server("127.0.0.1", 9501); | ||
$serv->set(['worker_num' => 1]); | ||
$serv->on('receive', function ($serv, $fd, $from_id, $data) { | ||
sleep(100); | ||
$serv->send($fd, 'Swoole: '.$data); | ||
}); | ||
$serv->start(); | ||
``` | ||
`onReceive`事件中执行了`sleep`函数,server在100秒内无法再收到任何客户端请求。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# exit/die函数的影响 | ||
|
||
在swoole程序中禁止使用exit/die,如果PHP代码中有`exit/die`,当前工作的Worker进程、Task进程、User进程、以及`swoole_process`进程会立即退出。 | ||
|
||
建议使用`try/catch`的方式替换`exit/die`,实现中断执行跳出`PHP`函数调用栈。 | ||
|
||
```php | ||
function swoole_exit($msg) | ||
{ | ||
//php-fpm的环境 | ||
if (ENV=='php') | ||
{ | ||
exit($msg); | ||
} | ||
//swoole的环境 | ||
else | ||
{ | ||
throw new Swoole\ExitException($msg); | ||
} | ||
} | ||
``` | ||
|
||
> 以上代码并未实现`ENV`常量和`Swoole\ExitException`,请自行实现 | ||
异常处理的方式比`exit/die`更友好,因为异常是可控的,`exit/die`不可控。在最外层进行try/catch即可捕获异常,仅终止当前的任务。Worker进程可以继续处理新的请求,而`exit/die`会导致进程直接退出,当前进程保存的所有变量和资源都会被销毁。如果进程内还有其他任务要处理,遇到`exit/die`也将全部丢弃。 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# 使用类静态变量/全局变量保存上下文 | ||
|
||
多个协程是并发执行的,因此不能使用类静态变量/全局变量保存协程上下文内容。使用局部变量是安全的,因为局部变量的值会自动保存在协程栈中,其他协程访问不到协程的局部变量。 | ||
|
||
实例 | ||
---- | ||
#### 错误的代码 | ||
```php | ||
$_array = []; | ||
$serv->on("Request", function ($req, $resp){ | ||
global $_array; | ||
//请求 /a(协程 1 ) | ||
if ($request->server['request_uri'] == '/a') { | ||
$_array['name'] = 'a'; | ||
co::sleep(1.0); | ||
echo $_array['name']; | ||
$resp->end($_array['name']); | ||
} | ||
//请求 /b(协程 2 ) | ||
else { | ||
$_array['name'] = 'b'; | ||
$resp->end(); | ||
} | ||
}); | ||
``` | ||
|
||
发起`2`个并发请求。 | ||
```shell | ||
curl http://127.0.0.1:9501/a | ||
curl http://127.0.0.1:9501/b | ||
``` | ||
|
||
* 协程`1`中设置了全局变量`$_array['name']`的值为`a` | ||
* 协程`1`调用`co::sleep`挂起 | ||
* 协程`2`执行,将`$_array['name']`的值为`b`,协程2结束 | ||
* 这时定时器返回,底层恢复协程1的运行。而协程1的逻辑中有一个上下文的依赖关系。当再次打印`$_array['name']`的值时,程序预期是`a`,但这个值已经被协程`2`所修改,实际结果却是`b`,这样就造成了逻辑错误 | ||
* 同理,使用类静态变量`Class::$array`、全局对象属性`$object->array`、其他超全局变量`$GLOBALS`等,进行上下文保存在协程程序中是非常危险的。可能会出现不符合预期的行为。 | ||
|
||
使用 Context 管理上下文 | ||
---- | ||
* 可以使用一个`Context`类来管理协程上下文,在`Context`类中,使用`Coroutine::getUid`获取了协程`ID`,然后隔离不同协程之间的全局变量 | ||
* 协程退出时清理上下文数据 | ||
|
||
#### Context | ||
```php | ||
use Swoole\Coroutine; | ||
|
||
class Context | ||
{ | ||
protected static $pool = []; | ||
|
||
static function get($key) | ||
{ | ||
$cid = Coroutine::getuid(); | ||
if ($cid < 0) | ||
{ | ||
return null; | ||
} | ||
if(isset(self::$pool[$cid][$key])){ | ||
return self::$pool[$cid][$key]; | ||
} | ||
return null; | ||
} | ||
|
||
static function put($key, $item) | ||
{ | ||
$cid = Coroutine::getuid(); | ||
if ($cid > 0) | ||
{ | ||
self::$pool[$cid][$key] = $item; | ||
} | ||
|
||
} | ||
|
||
static function delete($key = null) | ||
{ | ||
$cid = Coroutine::getuid(); | ||
if ($cid > 0) | ||
{ | ||
if($key){ | ||
unset(self::$pool[$cid][$key]); | ||
}else{ | ||
unset(self::$pool[$cid]); | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
#### 正确的代码 | ||
```php | ||
$serv->on("Request", function ($req, $resp) { | ||
if ($request->server['request_uri'] == '/a') { | ||
Context::put('name', 'a'); | ||
co::sleep(1.0); | ||
echo Context::get('name'); | ||
$resp->end(Context::get('name')); | ||
//退出协程时清理 | ||
Context::delete('name'); | ||
} else { | ||
Context::put('name', 'b'); | ||
$resp->end(); | ||
//退出协程时清理 | ||
Context::delete(); | ||
} | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Coroutine/Client | ||
|
||
`Swoole\Coroutine\Client`提供了`TCP`和`UDP`传输协议`Socket`客户端的封装代码,使用时仅需`new Swoole\Coroutine\Client`即可。 | ||
|
||
* `Swoole\Coroutine\Client`底层实现协程调度,业务层无需感知 | ||
* 使用方法和`Swoole\Client`同步模式方法完全一致 | ||
* `connect`超时设置同时作用于`Connect`和`Recv`、`Send` 超时 | ||
* 除了正常的调用外,`Swoole\Coroutine\Client`还支持并行请求。 | ||
* 使用方法和`Swoole\Client`一致的此处不再列出,请参考 [Swoole\Client](/wiki/page/p-client.html),对于使用有区别的函数,此处单独说明 | ||
|
||
协程版实例 | ||
----- | ||
```php | ||
$client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); | ||
if (!$client->connect('127.0.0.1', 9501, 0.5)) | ||
{ | ||
exit("connect failed. Error: {$client->errCode}\n"); | ||
} | ||
$client->send("hello world\n"); | ||
echo $client->recv(); | ||
$client->close(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Coroutine/Client->connect | ||
|
||
连接到远程服务器,函数原型: | ||
```php | ||
bool $swoole_client->connect(string $host, int $port, float $timeout = 0.1) | ||
``` | ||
connect方法接受4个参数: | ||
|
||
* `$host`是远程服务器的地址,`2.0.12`或更高版本可直接传入域名,底层会自动进行协程切换解析域名为`IP`地址 | ||
* `$port`是远程服务器端口 | ||
* `$timeout`是网络`IO`的超时时间,包括`connect/send/recv`,单位是秒`s`,支持浮点数。默认为`0.1s`,即`100ms`,超时发生时,连接会被自动`close`掉 | ||
* `connect`操作会有一次协程切换开销,`connect`发起时`yield`,完成时`resume` | ||
|
||
> 原先异步客户端不支持`recv`超时,现在协程版已经支持超时,复用上面的`$timeout`参数 | ||
----- | ||
connect不会发生阻塞,connect事件触发后,切回PHP上下文。 | ||
|
||
```php | ||
if ($cli->connect('127.0.0.1', 9501)) { | ||
$cli->send("data"); | ||
} else { | ||
echo "connect failed."; | ||
} | ||
``` | ||
如果连接失败,会返回false | ||
> 超时后返回,检查$cli->errCode为110 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Coroutine/Client->send | ||
|
||
发送数据,函数原型为 | ||
```php | ||
$client->send(string $data); | ||
``` | ||
|
||
* $data为发送的数据,必须为字符串类型,支持二进制数据 | ||
* 成功返回true,失败返回false | ||
* send操作是立即返回的,没有协程切换 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Coroutine/Client->recv | ||
|
||
`recv`方法用于从服务器端接收数据。底层会自动`yield`,等待数据接收完成后自动切换到当前协程。 | ||
|
||
```php | ||
function Coroutine\Client->recv(float $timeout = -1) : string; | ||
``` | ||
|
||
* `recv`方法,不接受长度参数,当设置了通信协议后。`recv`会返回完整的数据 | ||
* 未设置通信协议返回原始的数据,需要`PHP`代码中自行实现网络协议的处理 | ||
* 服务端主动关闭连接,`recv`返回空字符串 | ||
* `recv`操作需要进行一次协程切换,在收到数据后进行`resume` | ||
* `$timeout` 设置超时,单位为秒,浮点型,需要`2.1.2`或更高版本 | ||
|
||
超时设置 | ||
---- | ||
* 传入了`$timeout`,优先使用制定的`timeout`参数 | ||
* 未传入`$timeout`,但在`connect`时指定了超时时间,自动以`connect`超时时间作为`recv`超时时间 | ||
* 未传入`$timeout`,未设置`connect`超时,将设置为`-1`表示永不超时 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Coroutine/Client->close | ||
|
||
关闭连接。`close`不存在阻塞,会立即返回。 | ||
|
||
```php | ||
function Coroutine\Client->close() : bool; | ||
``` | ||
|
||
* 执行成功返回true,失败返回false | ||
* 关闭操作没有协程切换 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Coroutine/Client->peek | ||
|
||
窥视数据。`peek`方法直接操作`socket`,因此不会引起协程调度。 | ||
|
||
```php | ||
function Coroutine\Client->peek(int $length = 65535) : string; | ||
``` | ||
|
||
* `peek`方法仅用于窥视内核`socket`缓存区中的数据,不进行偏移。使用`peek`之后,再调用`recv`仍然可以读取到这部分数据 | ||
* `peek`方法是非阻塞的,它会立即返回。当`socket`缓存区中有数据时,会返回数据内容。缓存区为空时返回`false`,并设置`$client->errCode` | ||
* 连接已被关闭`peek`会返回空字符串 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Coroutine/Http/Client | ||
|
||
Swoole-2.0.0版本增加了对协程版`Http`客户端的支持。底层是用纯C编写,拥有超高的性能。 | ||
|
||
> 协程版Http客户端基于原生的AsyncIo中的异步Http客户端,基本的设置和使用方法和异步Http客户端一致,不在需要注册回调函数,只需要同步写法即可,使用方法和Swoole\Http\Client一致的此处不再列出,请参考 [swoole\AsyncIO\异步Http/WebSocket客户端](/wiki/page/p-http_client.html),对于使用有区别的函数,此处单独说明 | ||
启用协程Http客户端 | ||
---- | ||
* 需要在编译swoole时增加`--enable-coroutine`来开启此功能。 | ||
* swoole_http_client不依赖任何第三方库 | ||
* 支持`Http-Chunk`、`Keep-Alive`特性,暂不支持`form-data`格式 | ||
* Http协议版本为`HTTP/1.1` | ||
* `gzip`压缩格式支持需要依赖`zlib`库 | ||
|
||
构造方法 | ||
--- | ||
```php | ||
function Swoole\Coroutine\Http\Client->__construct(string $ip, int port, bool $ssl = false); | ||
``` | ||
|
||
* $ip 目标服务器的IP地址,可使用`swoole_async_dns_lookup_coro`查询域名对应的IP地址 | ||
* $port 目标服务器的端口,一般`http`为`80`,`https`为`443` | ||
* $ssl 是否启用`SSL/TLS`隧道加密,如果目标服务器是`https`必须设置`$ssl`参数为`true` | ||
|
||
|
||
使用实例 | ||
---- | ||
```php | ||
$cli = new Swoole\Coroutine\Http\Client('127.0.0.1', 80); | ||
$cli->setHeaders([ | ||
'Host' => "localhost", | ||
"User-Agent" => 'Chrome/49.0.2587.3', | ||
'Accept' => 'text/html,application/xhtml+xml,application/xml', | ||
'Accept-Encoding' => 'gzip', | ||
]); | ||
$cli->set([ 'timeout' => 1]); | ||
$cli->get('/index.php'); | ||
echo $cli->body; | ||
$cli->close(); | ||
``` | ||
* 如果未设置timeout,则将底层connect和IO回包都设为默认的500ms | ||
#### defer特性 | ||
- - - | ||
请参考[并发Client](http://wiki.swoole.com/wiki/page/p-coroutine_multi_call.html)一节。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Coroutine/Http/Client->get | ||
|
||
发起`GET`请求,函数原型: | ||
```php | ||
function Swoole\Coroutine\Http\Client->get(string $path); | ||
``` | ||
|
||
* $path 设置URL路径,如`/index.html`,注意这里不能传入`http://domain` | ||
* 使用get会忽略`setMethod`设置的请求方法,强制使用`GET` | ||
|
||
使用实例 | ||
---- | ||
```php | ||
$cli = new Swoole\Coroutine\Http\Client('127.0.0.1', 80); | ||
$cli->setHeaders([ | ||
'Host' => "localhost", | ||
"User-Agent" => 'Chrome/49.0.2587.3', | ||
'Accept' => 'text/html,application/xhtml+xml,application/xml', | ||
'Accept-Encoding' => 'gzip', | ||
]); | ||
|
||
$cli->get('/index.php'); | ||
echo $cli->body; | ||
$cli->close(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Coroutine/Http/Client->post | ||
|
||
发起`POST`请求,函数原型: | ||
```php | ||
function Swoole\Coroutine\Http\Client->post(string $path, mixed $data); | ||
``` | ||
|
||
* $path 设置URL路径,如`/index.html`,注意这里不能传入`http://domain` | ||
* $data 请求的包体数据,如果$data为数组底层自动会打包为`x-www-form-urlencoded`格式的POST内容,并设置`Content-Type`为`application/x-www-form-urlencoded` | ||
* 使用post会忽略`setMethod`设置的请求方法,强制使用`POST` | ||
|
||
使用实例 | ||
---- | ||
```php | ||
$cli = new Swoole\Coroutine\Http\Client('127.0.0.1', 80); | ||
$cli->post('/post.php', array("a" => '1234', 'b' => '456')); | ||
echo $cli->body; | ||
$cli->close(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Coroutine/Http/Client->upgrade | ||
|
||
升级为`WebSocket`连接。 | ||
|
||
```php | ||
function Coroutine\Http\Client->upgrade(string $path); | ||
``` | ||
|
||
* 失败返回`false`,成功返回`true` | ||
* 升级成功后可以使用`push`方法向服务器端推送消息,也可以调用`recv`接收消息 | ||
* `upgrade`会产生一次协程调度 | ||
|
||
使用实例 | ||
---- | ||
```php | ||
go(function () { | ||
$cli = new Co\http\Client("127.0.0.1", 9501); | ||
$ret = $cli->upgrade("/"); | ||
if ($ret) { | ||
while(true) { | ||
$cli->push("hello"); | ||
var_dump($cli->recv()); | ||
co::sleep(0.1); | ||
} | ||
} | ||
}); | ||
``` |
Oops, something went wrong.