title: EasySwoole Socket meta:

  • name: description content: php利用swoole实现自定义tcp协议,从而可以实现消息推送,和硬件消息交互
  • name: keywords content: swoole|swoole 拓展|swoole 框架|EasySwoole Socket|swoole socket|swoole websocket|swoole tcp|swoole udp|php websocket

EasySwoole Tcp服务

tcp 服务以及tcp客户端 demo

::: tip https://github.com/easy-swoole/demo/tree/3.x-subtcp :::

创建tcp服务

这里分为两种情况

  • 项目只提供TCP服务器
  • 项目单独一个端口开启TCP服务器

如果是第一种情况,我们可以直接在配置文件中,将Swoole的启动类型声明为EASYSWOOLE_SERVER

然后在EasySwooleEvent.php框架事件文件中mainServerCreate方法,进行TCP相关的回调注册

  1. public static function mainServerCreate(EventRegister $register)
  2. {
  3. $register->add($register::onReceive, function (\swoole_server $server, int $fd, int $reactor_id, string $data) {
  4. echo "fd:{$fd} 发送消息:{$data}\n";
  5. });
  6. }

单独端口开启TCP服务器,需要添加子服务。

通过EasySwooleEvent.php文件的mainServerCreate 事件,进行子服务监听,例如:

  1. <?php
  2. public static function mainServerCreate(EventRegister $register)
  3. {
  4. $server = ServerManager::getInstance()->getSwooleServer();
  5. $subPort1 = $server->addlistener('0.0.0.0', 9502, SWOOLE_TCP);
  6. $subPort1->set(
  7. [
  8. 'open_length_check' => false, //不验证数据包
  9. ]
  10. );
  11. $subPort1->on('connect', function (\swoole_server $server, int $fd, int $reactor_id) {
  12. echo "fd:{$fd} 已连接\n";
  13. $str = '恭喜你连接成功';
  14. $server->send($fd, $str);
  15. });
  16. $subPort1->on('close', function (\swoole_server $server, int $fd, int $reactor_id) {
  17. echo "fd:{$fd} 已关闭\n";
  18. });
  19. $subPort1->on('receive', function (\swoole_server $server, int $fd, int $reactor_id, string $data) {
  20. echo "fd:{$fd} 发送消息:{$data}\n";
  21. });
  22. }

tcp控制器实现

在TCP中,我们如何实现像Http请求一样的路由,从而将请求分发到不同的控制器。

EasySwoole提供了一个解析的方案参考。(非强制,可自行扩展修改为符合业务所需)

安装

引入 socket 包:

  1. composer require easyswoole/socket

::: danger 警告:请保证你安装的 easyswoole/socket 版本大 >= 1.0.7 否则会导致ws消息发送客户端无法解析的问题 :::

协议规则与解析

在本实例中,传输json数据 使用pack N进行二进制处理,json数据有3个参数,例如:

  1. {"controller":"Index","action":"index","param":{"name":"\u4ed9\u58eb\u53ef"}}

我们将在onReceive消息接收回调中,将数据转发给解析器

解析器将按json字段,类似下面去初始化并执行请求。

  1. $bean->setControllerClass($controller);
  2. $bean->setAction($action);
  3. $bean->setArgs($param);

TCP控制器需要继承EasySwoole\Socket\AbstractInterface\Controller

其他的一般将按普通控制器一样去编写即可! 可以在控制器中投递Task任务、转发给其他客户端、客户端下线等等…

::: tip 可以在github的demo中看到完整的实现和代码(复制下来即可在项目中使用) :::

实现解析器Parser.php

解析器接管服务 EasySwooleEvent.php

实现控制器Index.php

获取参数

  1. public function args()
  2. {
  3. $this->response()->setMessage('your args is:'.json_encode($this->caller()->getArgs()).PHP_EOL);
  4. }

回复数据

  1. public function index(){
  2. $this->response()->setMessage(time());
  3. }

获取当前fd

  1. public function who()
  2. {
  3. $this->caller()->getClient()->getFd()
  4. }

HTTP往TCP推送

在HTTP控制器,可以通过ServerManager获取Swoole实例,然后发送给指定FD客户端内容。

  1. function method(){
  2. // 传参fd
  3. $fd = intval($this->request()->getRequestParam('fd'));
  4. // 通过Swoole实例拿连接信息
  5. $info = ServerManager::getInstance()->getSwooleServer()->connection_info($fd);
  6. if(is_array($info)){
  7. ServerManager::getInstance()->getSwooleServer()->send($fd,'push in http at '.time());
  8. }else{
  9. $this->response()->write("fd {$fd} not exist");
  10. }
  11. }

::: warning 实际生产中,一般是用户TCP连接上来后,做验证,然后以userName=>fd的格式,存在redis中,需要http,或者是其他地方, :::

比如定时器往某个连接推送的时候,就是以userName去redis中取得对应的fd,再send。注意,通过addServer形式创建的子服务器,

::: warning 以再完全注册自己的网络事件,你可以注册onclose事件,然后在连接断开的时候,删除userName=>fd对应。 :::