🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 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`,则没必要处理。