# TCP路由
接上一节,TCP配置。
~~~
esd:
port:
tcp:
host: 0.0.0.0
port: 8084
open_http_protocol: false
open_websocket_protocol: false
sock_type: 1
package_eof: '\r\n'
open_eof_check: true
pack_tool: 'ESD\Plugins\Pack\PackTool\StreamPack'
~~~
> pack\_tool: 'ESD\\Plugins\\Pack\\PackTool\\StreamPack' 指向了包工具类
~~~
<?php
/**
* ESD framework
* @author tmtbe <896369042@qq.com>
* @author Bearlord <565364226@qq.com>
*/
namespace ESD\Plugins\Pack\PackTool;
use ESD\Core\Plugins\Logger\GetLogger;
use ESD\Core\Server\Config\PortConfig;
use ESD\Core\Server\Server;
use ESD\Plugins\Pack\ClientData;
use ESD\Yii\Helpers\Json;
use ESD\Yii\Yii;
/**
* Class StreamPack
* @package ESD\Plugins\Pack\PackTool
*/
class StreamPack extends AbstractPack
{
use GetLogger;
/**
* Packet encode
*
* @param $buffer
* @return string
*/
public function encode(string $buffer)
{
return $buffer . $this->portConfig->getPackageEof();
}
/**
* Packet decode
*
* @param $buffer
* @return string
*/
public function decode(string $buffer)
{
$data = str_replace($this->portConfig->getPackageEof(), '', $buffer);
return $data;
}
/**
* Data pack
*
* @param $data
* @param PortConfig $portConfig
* @param string|null $topic
* @return string
*/
public function pack($data, PortConfig $portConfig, ?string $topic = null)
{
$this->portConfig = $portConfig;
return $this->encode($data);
}
/**
* Packet unpack
*
* @param int $fd
* @param string $data
* @param PortConfig $portConfig
* @return mixed
* @throws \ESD\Core\Plugins\Config\ConfigException
*/
public function unPack(int $fd, string $data, PortConfig $portConfig): ?ClientData
{
$this->portConfig = $portConfig;
//Value can be empty
$value = $this->decode($data);
$clientData = new ClientData($fd, $portConfig->getBaseType(), 'onReceive', $value);
return $clientData;
}
}
~~~
`unPack`方法处理,服务端收到的TCP数据。
~~~
$clientData = new ClientData($fd, $portConfig->getBaseType(), 'onReceive', $value);
~~~
> 第1个参数,TCP连接标志,Swoole提供。
>
> 第2个参数 `$portConfig->getBaseType()` 的值是 tcp,
>
> 第3个参数 `onReceive`,指向AOP解析过后的路由路径,即指向了 `/onReceive`
>
> 所以,还需要一条路由信息。
## 通过AOP新增动态路由
~~~
<?php
namespace App\Controller;
use ESD\Core\Server\Server;
use ESD\Go\GoController;
use ESD\Plugins\EasyRoute\Annotation\RequestMapping;
use ESD\Plugins\EasyRoute\Annotation\TcpController;
use ESD\Plugins\Pack\GetBoostSend;
/**
* @TcpController()
* Class StreamController
* @package App\Controller
*/
class StreamController extends GoController
{
/**
* @RequestMapping("onReceive")
*/
public function actionOnReceive()
{
$fd = $this->clientData->getFd();
$receiveData = $this->clientData->getData();
var_dump($receiveData);
Server::$instance->send($fd, sprintf("Server receive data: %s\r\n", $receiveData));
switch ($receiveData) {
case 'hello':
Server::$instance->send($fd, "Hi!!!\r\n");
break;
case 'close':
Server::$instance->closeFd($fd);
break;
}
}
}
~~~
#### 1.类注释 @TcpController() 、方法注释 @RequestMapping() 必须。
#### 2.类名定义、方法名无要求。RequestMapping的地址onReceive,要与上述的
~~~
$clientData = new ClientData($fd, $portConfig->getBaseType(), 'onReceive', $value);
~~~
的第3个参数一致。
#### 3\. `ESD Framework` 启动时,经过 Annotations Scan,则会增加一条路由。
~~~
8084:TCP /onReceive -> App\Controller\StreamController::actionOnReceive
~~~
#### 上述例子,打印了服务端收到的数据,同时向客户端发动了一条数据。
如果客户输入`hello`,则发送 `Hi!!!\r\n`, `\r\n` 是换行符。
如果客户输入`close`,则关闭连接。
#### 测试结果如下:
~~~
[root@localhost ~]# telnet 192.168.108.131 8084
Trying 192.168.108.131...
Connected to 192.168.108.131.
Escape character is '^]'.
hi
Server receive data: hi
hello
Server receive data: hello
Hi!!!
close
Server receive data: close
Connection closed by foreign host.
~~~
## 结束符\\r\\n问题
上述例子,终端环境通过`Telnet`,连接TCP服务,进行TCP收发数据的测试。
客户端输入hi,回车发送。回车,对应着一个 `\r\n`(或者 `\n`, `\r`,Windows,Macos,Linux有差别)。
服务端接收客户端数据,经过了 `unpack` 解包 和 `decode` 解码,客户发送数据自带的 `\r\n` 被自动过滤处理。
服务端发送客户端数据,为了客户端能回车换行,末尾补充了 `\r\n`。否则不能自动换行。
~~~
Server::$instance->send($fd, "Hi!!!\r\n");
~~~
> Server::$instance->send() 不经过 `encode` 和 `pack` 处理的。所以需要人工加上\\r\\n。
>
> 避免每次末尾输入 `\r\n`,请使用 `autoBoostSend`。
修改过的代码如下:
~~~
<?php
namespace App\Controller;
use ESD\Core\Server\Server;
use ESD\Go\GoController;
use ESD\Plugins\EasyRoute\Annotation\RequestMapping;
use ESD\Plugins\EasyRoute\Annotation\TcpController;
use ESD\Plugins\Pack\GetBoostSend;
/**
* @TcpController()
* Class StreamController
* @package App\Controller
*/
class StreamController extends GoController
{
use GetBoostSend;
/**
* @RequestMapping("onReceive")
*/
public function actionOnReceive()
{
$fd = $this->clientData->getFd();
$receiveData = $this->clientData->getData();
var_dump($receiveData);
$this->autoBoostSend($fd, sprintf("Server receive data: %s", $receiveData));
switch ($receiveData) {
case 'hello':
$this->autoBoostSend($fd, "Hi!!!");
break;
case 'close':
Server::$instance->closeFd($fd);
break;
}
}
}
~~~
## encode、decode、pack、unpack怎么玩?
实际项目中,TCP包经常以【2进制】的方式传输,但数据本身是 【16进制】。
所以 :
`decode` 把 接收的【2进制】包 转成 【16进制】包,
`encode` 把 发送的【16进制】包 转为 【2进制】包。
~~~
/**
* Packet encode
*
* @param $buffer
* @return string
*/
public function encode(string $buffer)
{
return hex2bin($buffer);
}
/**
* Packet decode
*
* @param $buffer
* @return string
*/
public function decode(string $buffer)
{
return bin2hex($buffer);
}
~~~
Telnet的测试程序,是为了能正常回车换行,才加的 `\r\n`。如果项目协议,无要求必须末尾加`\r\n`,则没必要处理。
- 1 介绍
- 2 安装
- 2.1 环境
- 2.2 安装
- 3 配置
- 3.1 Server配置
- 3.2 端口配置
- 3.3 项目结构
- 3.4 内核优化
- 4 服务
- 4.1 HTTP服务
- 4.1.1 路由
- 4.1.1.1 静态路由
- 4.1.1.2 路由定义
- 4.1.1.3 路由方法
- 4.1.1.4 路由分组
- 4.1.1.5 资源路由
- 4.1.1.6 端口作用域
- 4.1.1.7 异常处理
- 4.1.1.8 跨域请求
- 4.1.1.9 路由缓存
- 4.1.2 控制器
- 4.1.2.1 控制器初始化
- 4.1.2.2 前置后置操作
- 4.1.2.3 跳转与重定向
- 4.1.2.4 异常处理
- 4.1.3 请求
- 4.1.3.1 请求对象
- 4.1.3.2 请求信息
- 4.1.3.3 REQUEST消息
- 4.1.3.4 RESPONSE消息
- 4.1.3.5 STREAM消息
- 4.1.3.6 URI信息
- 4.1.3.7 处理上传文件
- 4.1.3.8 验证器
- 4.2 Websocket服务
- 4.2.1 Websocket配置
- 4.2.2 Websocket路由
- 4.3 TCP服务
- 4.3.1 TCP配置
- 4.3.2 TCP路由
- 4.3.3 协程处理案例
- 5 插件
- 5.15 Yii-PDO插件
- 5.15.1 PDO 连接MySQL Mariadb
- 5.15.2 PDO连接PostgreSQL
- 5.15.3 PDO连接GreenPlum
- 5.15.4 PDO连接Oracle
- 5.15.5 PDO连接Cubrid
- 5.15.6 PDO连接SQL Server
- 6 概念
- 7. Yii- I18N国际化
- 8. 模型 Yii-Model
- 8.1 快速创建模型
- 8.2 快速创建多个模型
- 8.3 核心验证器 Core Validators
- 9. 配合数据库工作
- 9.1.数据库访问对象 Database Access Objects
- 9.2 查询构造器 Query Builder
- 9.3 活动记录 Active Record