# 控制器-Controller Controller需要遵循的规则: 1.控制器需要继承Controller类。 2.控制器里所有public方法都是公开给外界访问的,private和protected定义的方法无法被外界访问。 3.控制器里的方法需要搭配前缀名,具体的前缀名的定义在ports.php配置中。 4.控制器采用了对象池复用技术,需要注意调用destroy()方法,否则会产生内存泄露。 >特别声明:从3.1.1版本起destroy方法会自动调用,无需手动书写。 ## initialization 初始化方法,Controller是对象池复用模式,所以不要在__construct中进行初始化,通过重写initialization方法实现Controller的初始化,每次路由到Controller中时都会执行initialization方法。 ```php class AppController extends Controller { /** * @var AppModel */ public $AppModel; protected function initialization($controller_name, $method_name) { parent::initialization($controller_name, $method_name); $this->AppModel = $this->loader->model('AppModel', $this); throw new \Exception("错误"); } } ``` 可以在initialization中进行loader操作,也可以进行细节的检查,如果想中断操作直接抛出异常即可,会直接进入异常处理函数,不会执行后面的方法。 >特别声明:从3.1.1版本起增加了interrupt方法专门用于中断 ## interrupt 3.1.1版本新增方法 中断,调用此方法Controller立马进入销毁,后面的代码一概不会执行。 ``` protected function initialization($controller_name, $method_name) { parent::initialization($controller_name, $method_name); $this->AppModel = $this->loader->model('AppModel', $this); $this->interrupt(); } ``` ## defaultMethod 当控制器方法不存在的时候的默认方法,开发者可以重写此方法作为默认方法 ## onExceptionHandle onExceptionHandle 异常的回调,当Controller方法中出现异常会转到这个回调方法中,开发者可以自定义异常的处理和输出。 >在任何方法中包括调用的Model,Task抛出异常最终都会中断流程,并且流入到onExceptionHandle方法中 ## HTTP 列举下对应的配置 port.php ``` $config['ports'][] = [ 'socket_type' => PortManager::SOCK_HTTP, 'socket_name' => '0.0.0.0', 'socket_port' => 8081, 'route_tool' => 'NormalRoute', 'middlewares' => ['MonitorMiddleware', 'NormalHttpMiddleware'], 'method_prefix' => 'http_' ]; ``` ports配置中配置了8081端口为HTTP协议,路由使用了NormalRoute路由,中间件使用了MonitorMiddleware和NormalHttpMiddleware,方法前缀为'http_'。 关于路由和中间件,请看相应的文档,这里我们对应下url和目标Controller的关系。 URL:http://localhost:8081/TestController/echo Controller文件:将会优先寻找app/Controllers/TestController.php,如果找不到会寻找框架里的Server/Controllers/TestController.php >在server.php配置中有个allow_ServerController参数,如果为false将不能访问到框架自带的控制器,Controller,Model,Task,Middlewares,Route,Pack均会优先从app目录下匹配。 由于设置了method_prefix,那么我们访问的方法就是TestController类中的http_echo。 我们接下来看看http_echo方法,代码如下: ```php public function http_echo() { $this->http_output->end(123); } ``` 通过$this->http_output->end方法进行输出,通过游览器访问可以看到123的字样。 >end方法只能调用一次,多次调用无效。 通过$this->http_output可以获取到HttpOutPut类,里面有相应的API,这里不多做介绍。 相应的通过$this->http_input可以获取到HttpInPut类。 ``` public function http_test() { $max = $this->http_input->get('max'); if (empty($max)) { $max = 100; } $sum = 0; for ($i = 0; $i < $max; $i++) { $sum += $i; } $this->http_output->end($sum); } ``` 这里通过$this->http_input->get方法获取到了max的值。 访问的URL为http://localhost:8081/TestController/test?max=10 将输出45. * redirect Http重定向 * redirect404 重定向到404 * redirectController 重定向到控制器,这里的方法名不填前缀 ## TCP 列举下对应的配置 port.php ``` $config['ports'][] = [ 'socket_type' => PortManager::SOCK_TCP, 'socket_name' => '0.0.0.0', 'socket_port' => 9091, 'pack_tool' => 'LenJsonPack', 'route_tool' => 'NormalRoute', 'middlewares' => ['MonitorMiddleware'] ]; ``` 这里开启了9091端口作为TCP协议,封装器使用了LenJsonPack,路由器使用了NormalRoute,中间件为MonitorMiddleware,大家先不要对这么多名称感到恐惧,我们后面慢慢来了解。 HTTP协议是个完整的业务层协议所以他不需要封装器的支持,只需要进行路由,但TCP协议不同一般我们会在TCP协议上再构建一个私有的协议,这时我们就需要封装器了。具体的封装器我们以后详细说明,现在我们假定我们通过了路由将消息转发到了对应的控制器方法上了。 这里没有设置前缀所以我们直接路由到对应的方法上。 ``` /** * tcp的测试 */ public function testTcp() { var_dump($this->client_data->data); $this->send($this->client_data->data); } ``` 通过$this->client_data可以获取到数据流通过封装器解包后的完整数据,NormalRoute中我们定义client_data的结构如下 ``` stdClass{ controller_name:控制器名称 method_name:方法名称 path:路径(非必须) params:方法参数(非必须) *:等等其他自定义字段 } ``` client_data是个object不是一个array这点需要注意,数据流通过封装器解包后传递给路由器,路由器需要controller_name和method_name,如果满足条件将进行路由。 上面的代码将获取到的$this->client_data->data返回给了客户端。 返回的数据将又一次经过端口设置的封装器,封装成协议支持的格式返回给客户端。 ```php public function testTcp() { $this->send($this->client_data->data)); //发送第一条 $this->send($this->client_data->data)); //发送第二条 } ``` send系列方法被用于长连接(tcp,ws,wss)发送消息。 * send 向当前客户端发送消息 * sendToUid 向指定uid发送消息 * sendToUids 向指定uids发送消息 * sendToAll 向所有绑定uid的连接发送消息 * bindUid 为当前连接绑定uid * kickUid 踢用户下线 * addSub 添加订阅 * removeSub 移除订阅 * sendPub 发布订阅 * getFdInfo 获取fd的信息 ## WebSocket 和TCP基本一致,贴一下对应的配置 ``` $config['ports'][] = [ 'socket_type' => PortManager::SOCK_WS, 'socket_name' => '0.0.0.0', 'socket_port' => 8083, 'route_tool' => 'NormalRoute', 'pack_tool' => 'NonJsonPack', 'opcode' => PortManager::WEBSOCKET_OPCODE_TEXT, 'middlewares' => ['MonitorMiddleware', 'NormalHttpMiddleware'] ]; ``` opcode是WebSocket独有的,有2个选项 PortManager::WEBSOCKET_OPCODE_TEXT 文本流 PortManager::WEBSOCKET_OPCODE_BINARY 二进制流 其中还需要注意下封装器使用的是NonJsonPack,NonJsonPack和LenJsonPack区别在于NonJsonPack没有长度信息,因为ws协议也是一个相对上层的协议不要手动分割数据流。