什么是协程
协程是用代码实现的一个非标准线程,可看作是轻量级的线程
协程与线程的区别
调度方式
- 协程由用户代码来调度和管理
- 线程由操作系统分配、管理、调度
运行
- 进程内的多个协程,同一时间只能有一个在运行,其他协程是暂停状态
- 多线程可以并发运行
运行机制
- 在HTTP协程服务上,一个请求 = 一个协程。一个
worker
可以同时处理多个请求(协程),无需IO
等待,可以同时发起/维护大量的请求(协程)。 - 在每个
worker
内有一个协程调度器,在遇到 I/O 时底层自动的进行隐式协程切换,进程内以单线程的方式运行协程 —— 同时刻只有一个协程在运行
代码也可以显式的控制协程切换
安装运行框架
安装PHP扩展
按照文档来干,首先得有个装好的环境,pecl
phpize
啥的弄好,然后直接pecl install 扩展
开搞。会有提示缺少其他扩展的,或者缺少系统软件的,按提示继续安装即可。
比如,在安装 redis
扩展时 pecl install redis
提示缺少 igbinary
,继续 pecl install igbinary
即可
或者又报错,缺少 libzstd
,搜索一下 apt search libzstd
,发现这个包是 libzstd-dev
,继续 apt install libzstd-dev
即可
创建项目
composer create-project hyperf/hyperf-skeleton newProject
之后会是一些功能模块的选择,按需添加,完成后会自动进行安装
如果加载模块的时候报错终止脚本了,不用慌,先把已经加载的模块安装上
composer install
如果之后发现缺少哪个模块,看文档再单独加载即可,到时候报错再处理也可以
运行
装好扩展,配置好php.ini
,开始运行。注意每次变更配置,代码需要重新运行
php bin/hyperf.php start
简单配置
更改server
的端口,多 server
,worker
数量以及COROUTINE
数量
'servers' => [
[
...
'name' => 'http', # 服务名字 多服务要区分
'port' => 80, # 监听端口
],
[
...
'name' => 'http_2', # 第二服务器
'port' => 8080, # 可以设置不一样的端口
'callbacks' => [ # 文档中有解释,使用一个新的Server类
Event::ON_REQUEST => ['secondServer', 'onRequest'],
],
]
],
'settings' => [
# 是否开启协程
Constant::OPTION_ENABLE_COROUTINE => true,
# worker数量 (本地开发我选了两个,默认等于cpu核心数)
Constant::OPTION_WORKER_NUM => 2,
# 一个worker 最大的协程数量
Constant::OPTION_MAX_COROUTINE => 100000,
# https支持
Constant::OPTION_OPEN_HTTP2_PROTOCOL => true,
# 一个worker最大请求数
Constant::OPTION_MAX_REQUEST => 100000,
],
config/autoload/server.php
name
为 http_2
的 server
需要的依赖关系
文档中有解释,使用一个新的Server类
return [
'secondServer' => Hyperf\HttpServer\Server::class
];
config/autoload/dependencies.php
使用热更新
不使用开发有点难受。生产环境不装。时间建议用1秒
安装
composer require hyperf/watcher --dev
配置
# 发布配置
php bin/hyperf.php vendor:publish hyperf/watcher
# 配置文件:config/autoload/watcher.php 可选几种驱动。更换详见文档
启动
跟项目直接启动方式不同
php bin/hyperf.php server:watch
用协程开发
注意点
- 不写阻塞代码,数据库、请求、文件等。有协程客户端的用客户端,没有的用
\Swoole\Runtime::enableCoroutine()
。不行的再想办法 - 协程内不直接使用全局变量。一个
worker
内的所有协程共享全局变量,从 请求中拿全局变量。 底层中 协程以单线程模式运行,但一个主协程可以创建多个子协程
- 协程间本质上是相互独立的没有父子关系
- 一次创建了5个协程直接运行,就是该协程有5个子协程在同时运行
- 拿到一个接口的数据后,再依次拿下一个,就是只有一个子协程在同时运行
- 可以限制代码块同时运行的协程数量
Concurrent
- 子协程阻塞的是主协程,不会阻塞其他主协程
创建协程
co(callable $callable);
go(callable $callable);
Hyperf\Utils\Coroutine::create(callable $callable);
判断是否在协程内
Hyperf\Utils\Coroutine::inCoroutine(): bool
获取当前协程ID
Hyperf\Utils\Coroutine::id(): int # 不在协程返回 -1
Channel
用于协程间通信,生产者和消费者模式
Defer
协程结束后执行的代码,先进后出
WaitGroup
主协程阻塞等待子协程执行完成
Parallel
基于WaitGroup
,用于方便的创建需等待的子协程。可控制最大同时运行协程数量
Concurrent
基于Channel
,用于控制代码块内 最大同时运行协程数量
协程上下文
获取/设置 仅限当前协程的上下文。在协程结束后会自动释放
Hyperf\Utils\Context::set('key','value'); # 设置值到当前协程
Hyperf\Utils\Context::get('key','default'); # 取出指定值
Hyperf\Utils\Context::has('key'); # 是否存在 返回bool
Hyperf\Utils\Context::override('key','value'); # 重写,取出key,更改后写回
HTTP 路由
标准路由
Router::get('/index', 'IndexController::hello');
Router::post('/index', 'IndexController@hello');
Router::put('/index', [IndexController::class,'index']);
# 支持多种方式。没有 ANY可用
Router::addRoute(['PATCH', 'DELETE','HEADER','OPTIONS'], '/index', 'IndexController@index');
config/routes.php
路由组
Router::addGroup('/user/',function (){
Router::get('index','UserController@index');
});
为路由分配不同的 server
不指定 server
时,默认使用名为 http
的 server
Router::addServer('http_2',function (){
Router::addGroup('/index', function (){
Router::get('/index',[IndexController::class,'index']);
});
Router::get('/index', [IndexController::class,'index']);
});
注解路由
待续
控制器
通过控制器处理HTTP请求,需要绑定路由
与请求相关的数据要储存到协程上下文(Context)
内,不可存储在类属性内
请求和响应对象实际上是代理类,数据最终也是通过协程上下文(Context)
获取
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
class UserController
{
public function index(RequestInterface $request, ResponseInterface $response)
{
}
}
创建控制器
php bin/hyperf.php gen:controller UserController
请求
使用上基本与laravel一致
Router::get('/index/{id:\d+}', 'controller');
# 依赖注入 请求对象、参数
use Hyperf\HttpServer\Contract\RequestInterface;
public function info(RequestInterface $request, int $id)
{
# 使用方法获取参数
$id = $request->route('id', 'default');
# 获取请求路径
$uri = $request->path();
# 获取URL
$url = $request->url();
# 判断路径 支持通配
$request->is('user/*'):bool
# 带参数URL
$url = $request->fullUrl();
# 获取请求方法
$url = $request->getMethod();
# 判断请求方法
$request->isMethod('post'):bool
# 获取输入
$all = $request->all();
$name = $request->input('name', 'default');
$name = $request->input('products.0.name'); // 数组、json数组
# 从查询字符串获取输入 ??没理解
$name = $request->query('name', 'default');
# 判断值
$request->has('name'):bool
$request->has(['name', 'email']):bool
# 获取cookie
$request->getCookieParams(); // 所有cookie
$name = $request->cookie('name', 'default');
}
文件
$file = $request->file('photo');
$request->hasFile('photo'):bool
// 验证文件是否有效
$request->file('photo')->isValid();
// 文件临时路径
$path = $request->file('photo')->getPath();
// 原始文件后缀名
$extension = $request->file('photo')->getExtension();
// 保存上传文件
$file->moveTo('/savePath/newFileName.Extension');
// 是否移动成功
$file->isMoved():bool
响应
public function json(ResponseInterface $response)
{
return $response->json(array);
return $response->xml(array);
return $response->raw(string);
return $response->redirect('/anotherUrl',302,'http or https');
return $response->withCookie($cookie)->withContent('Hello Hyperf.');
return $response->download(file, saveFileName);
}
数据库
创建模型
php bin/hyperf.php gen:model table_name
中间件
在标准路由的 options 中配置
OPTIONS = ['middleware' => [ JwtMiddleware::class ]];
Router::get(PATH,FALLBACK,OPTIONS);
Router::addGroup(PREFIX,FALLBACK,OPTIONS);
Router::addRoute(FUNCTION,PATH,FALLBACK,OPTIONS);
注解
/**
* 单个
* @Middleware(FooMiddleware::class)
*/
/**
* 多个
* @Middlewares({
* @Middleware(FooMiddleware::class),
* @Middleware(BarMiddleware::class)
* })
*/