什么是协程
协程是用代码实现的一个非标准线程,可看作是轻量级的线程
协程与线程的区别
调度方式
- 协程由用户代码来调度和管理
- 线程由操作系统分配、管理、调度
运行
- 进程内的多个协程,同一时间只能有一个在运行,其他协程是暂停状态
- 多线程可以并发运行
运行机制
- 在HTTP协程服务上,一个请求 = 一个协程。一个
worker可以同时处理多个请求(协程),无需IO等待,可以同时发起/维护大量的请求(协程)。 - 在每个
worker内有一个协程调度器,在遇到 I/O 时底层自动的进行隐式协程切换,进程内以单线程的方式运行协程 —— 同时刻只有一个协程在运行
代码也可以显式的控制协程切换
安装运行框架
安装PHP扩展
按照文档来干,首先得有个装好的环境,peclphpize啥的弄好,然后直接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.phpname 为 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 # 不在协程返回 -1Channel
用于协程间通信,生产者和消费者模式
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)
* })
*/ 
