ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Swoole 多协议 多端口 的应用

2021-04-27 17:57:30  阅读:241  来源: 互联网

标签:echo 协议 Swoole function 端口 EOL #### serv PHP


第 81 篇文章

这是关于 Swoole 学习的第五篇文章:Swoole 多协议 多端口 的应用。

概述

主要参考官方的这两篇文章,进行实现的 Demo。

  • 网络通信协议设计:

    https://wiki.swoole.com/wiki/page/484.html

  • 多端口监听的使用:

    https://wiki.swoole.com/wiki/page/161.html

希望通过我提供的 Demo,能够对文档有更加深刻的理解。

网络通信协议设计

为什么需要通信协议?

官方:TCP协议在底层机制上解决了UDP协议的顺序和丢包重传问题。但相比UDP又带来了新的问题,TCP协议是流式的,数据包没有边界。应用程序使用TCP通信就会面临这些难题。因为TCP通信是流式的,在接收1个大数据包时,可能会被拆分成多个数据包发送。多次Send底层也可能会合并成一次进行发送。这里就需要2个操作来解决:分包 和 合包,所以TCP网络通信时需要设定通信协议。

Swoole 支持了2种类型的自定义网络通信协议 :EOF结束符协议、固定包头+包体协议。

EOF结束符协议

640?wx_fmt=png

先看下,未设置协议的效果:

640?wx_fmt=gif

发送的每条数据长度都是 23,但在 onReceive 接收数据的时候每次接收的长度不一样,并没有按照想象的方式进行分包。

再看下,设置了EOF结束符协议的效果:

640?wx_fmt=gif

发送的每条数据长度都是 23,在 onReceive 接收数据的时候每次接收的也是 23 ,完美。

主要设置项如下:

'package_max_length' => '8192',	
'open_eof_split'     => true,	
'package_eof'        => "\r\n"

不做解释,官方文档已经写的很清楚。

示例代码如下:

server.php

serv = new swoole_server('0.0.0.0', 9501);	
        $this->serv->set([	
            'worker_num'      => 2, //开启2个worker进程	
            'max_request'     => 4, //每个worker进程 max_request设置为4次	
            'dispatch_mode'   => 2, //数据包分发策略 - 固定模式	
            //EOF结束符协议	
            'package_max_length' => '8192',	
            'open_eof_split'     => true,	
            'package_eof'        => "\r\n"	
        ]);	
        $this->serv->on('Start', [$this, 'onStart']);	
        $this->serv->on('Connect', [$this, 'onConnect']);	
        $this->serv->on("Receive", [$this, 'onReceive']);	
        $this->serv->on("Close", [$this, 'onClose']);	
        $this->serv->start();	
    }	
    public function onStart($serv) {	
        echo "#### onStart ####".PHP_EOL;	
        echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL;	
        echo "swoole_cpu_num:".swoole_cpu_num().PHP_EOL;	
        echo "master_pid: {$serv->master_pid}".PHP_EOL;	
        echo "manager_pid: {$serv->manager_pid}".PHP_EOL;	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onConnect($serv, $fd) {	
        echo "#### onConnect ####".PHP_EOL;	
        echo "客户端:".$fd." 已连接".PHP_EOL;	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onReceive($serv, $fd, $from_id, $data) {	
        echo "#### onReceive ####".PHP_EOL;	
        var_dump($data);	
    }	
    public function onClose($serv, $fd) {	
        echo "Client Close.".PHP_EOL;	
    }	
}	
$server = new Server();

client.php

client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);	
        $this->client->on('Connect', [$this, 'onConnect']);	
        $this->client->on('Close', [$this, 'onClose']);	
        $this->client->on('Error', [$this, 'onError']);	
    }	
    public function connect() {	
        if(!$fp = $this->client->connect("127.0.0.1", 9501)) {	
            echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL;	
            return;	
        }	
    }	
    public function onConnect() {	
        fwrite(STDOUT, "发送测试数据(Y or N):");	
        swoole_event_add(STDIN, function() {	
            $msg = trim(fgets(STDIN));	
            if ($msg == 'y') {	
                $this->send();	
            }	
            fwrite(STDOUT, "发送测试数据(Y or N):");	
        });	
    }	
    public function send() {	
        $msg_info =  "客户端发信息...\r\n";	
        $i = 0;	
        while ($i < 50) {	
            var_dump($msg_info);	
            $this->client->send($msg_info);	
            $i++;	
        }	
    }	
    public function onClose() {	
        echo "Client close connection".PHP_EOL;	
    }	
    public function onError() {	
    }	
}	
$client = new Client();	
$client->connect();

固定包头+包体协议

640?wx_fmt=png

先看下,未设置协议的效果:

640?wx_fmt=gif

很明显,在 onReceive 接收到的数据,是少的。

再看下,设置协议的效果:

640?wx_fmt=gif

主要设置项如下:

'open_length_check'     => true,	
'package_max_length'    => '8192',	
'package_length_type'   => 'N',	
'package_length_offset' => '0',	
'package_body_offset'   => '4',

不做解释,官方文档已经写的很清楚。

示例代码如下:

server.php

serv = new swoole_server('0.0.0.0', 9501);	
        $this->serv->set([	
            'worker_num'      => 2, //开启2个worker进程	
            'max_request'     => 4, //每个worker进程 max_request设置为4次	
            'dispatch_mode'   => 2, //数据包分发策略 - 固定模式	
            //固定包头+包体协议	
            'open_length_check'     => true,	
            'package_max_length'    => '8192',	
            'package_length_type'   => 'N',	
            'package_length_offset' => '0',	
            'package_body_offset'   => '4',	
        ]);	
        $this->serv->on('Start', [$this, 'onStart']);	
        $this->serv->on('Connect', [$this, 'onConnect']);	
        $this->serv->on("Receive", [$this, 'onReceive']);	
        $this->serv->on("Close", [$this, 'onClose']);	
        $this->serv->start();	
    }	
    public function onStart($serv) {	
        echo "#### onStart ####".PHP_EOL;	
        echo "swoole_cpu_num:".swoole_cpu_num().PHP_EOL;	
        echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL;	
        echo "master_pid: {$serv->master_pid}".PHP_EOL;	
        echo "manager_pid: {$serv->manager_pid}".PHP_EOL;	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onConnect($serv, $fd) {	
        echo "#### onConnect ####".PHP_EOL;	
        echo "客户端:".$fd." 已连接".PHP_EOL;	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onReceive($serv, $fd, $from_id, $data) {	
        echo "#### onReceive ####".PHP_EOL;	
        $length = unpack('N', $data)[1];	
        echo "Length:".$length.PHP_EOL;	
        $msg = substr($data, -$length);	
        echo "Msg:".$msg.PHP_EOL;	
    }	
    public function onClose($serv, $fd) {	
        echo "Client Close.".PHP_EOL;	
    }	
}	
$server = new Server();

client.php

client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);	
        $this->client->on('Connect', [$this, 'onConnect']);	
        $this->client->on('Close', [$this, 'onClose']);	
        $this->client->on('Error', [$this, 'onError']);	
    }	
    public function connect() {	
        if(!$fp = $this->client->connect("127.0.0.1", 9501, 1)) {	
            echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL;	
            return;	
        }	
    }	
    public function onConnect() {	
        fwrite(STDOUT, "发送测试数据(Y or N):");	
        swoole_event_add(STDIN, function() {	
            $msg = trim(fgets(STDIN));	
            if ($msg == 'y') {	
                $this->send();	
            }	
            fwrite(STDOUT, "发送测试数据(Y or N):");	
        });	
    }	
    public function send() {	
        $msg = '客户端发的信息...';	
        $msg_info = pack('N', strlen($msg)).$msg;	
        $i = 0;	
        while ($i < 50) {	
            var_dump($msg_info);	
            $this->client->send($msg_info);	
            $i++;	
        }	
    }	
    public function onClose() {	
        echo "Client close connection".PHP_EOL;	
    }	
    public function onError() {	
    }	
}	
$client = new Client();	
$client->connect();

多端口监听的使用

640?wx_fmt=png

上图,是示例代码中的端口监听:

  • 9501 onMessage 处理 WebSocket。

  • 9501 onRequest 处理 HTTP。

  • 9502 onReceive 处理 TCP。

  • 9503 onPacket 处理 UDP。

不多说,看下效果图:

640?wx_fmt=gif

示例代码如下:

server.php

serv = new swoole_websocket_server("0.0.0.0", 9501);	
        $this->serv->set([	
            'worker_num'      => 2, //开启2个worker进程	
            'max_request'     => 4, //每个worker进程 max_request设置为4次	
            'task_worker_num' => 4, //开启4个task进程	
            'dispatch_mode'   => 4, //数据包分发策略 - IP分配	
            'daemonize'       => false, //守护进程(true/false)	
        ]);	
        $this->serv->on('Start', [$this, 'onStart']);	
        $this->serv->on('Open', [$this, 'onOpen']);	
        $this->serv->on("Message", [$this, 'onMessage']);	
        $this->serv->on("Request", [$this, 'onRequest']);	
        $this->serv->on("Close", [$this, 'onClose']);	
        $this->serv->on("Task", [$this, 'onTask']);	
        $this->serv->on("Finish", [$this, 'onFinish']);	
        //监听 9502 端口	
        $tcp = $this->serv->listen("0.0.0.0", 9502, SWOOLE_SOCK_TCP);	
        $tcp->set([	
            'worker_num'      => 2, //开启2个worker进程	
            'max_request'     => 4, //每个worker进程 max_request设置为4次	
            'dispatch_mode'   => 2, //数据包分发策略 - 固定模式	
            //固定包头+包体协议	
            'open_length_check'     => true,	
            'package_max_length'    => '8192',	
            'package_length_type'   => 'N',	
            'package_length_offset' => '0',	
            'package_body_offset'   => '4',	
        ]);	
        $tcp->on("Receive", [$this, 'onReceive']);	
        //监听 9503 端口	
        $udp = $this->serv->listen("0.0.0.0", 9503, SWOOLE_SOCK_UDP);	
        $udp->set([	
            'worker_num'      => 2, //开启2个worker进程	
            'max_request'     => 4, //每个worker进程 max_request设置为4次	
            'dispatch_mode'   => 2, //数据包分发策略 - 固定模式	
        ]);	
        $udp->on("Packet", [$this, 'onPacket']);	
        $this->serv->start();	
    }	
    public function onStart($serv) {	
        echo "#### onStart ####".PHP_EOL;	
        echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL;	
        echo "master_pid: {$serv->master_pid}".PHP_EOL;	
        echo "manager_pid: {$serv->manager_pid}".PHP_EOL;	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onOpen($serv, $request) {	
        echo "#### onOpen ####".PHP_EOL;	
        echo "server: handshake success with fd{$request->fd}".PHP_EOL;	
        $serv->task([	
            'type' => 'login'	
        ]);	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onTask($serv, $task_id, $from_id, $data) {	
        echo "#### onTask ####".PHP_EOL;	
        echo "#{$serv->worker_id} onTask: [PID={$serv->worker_pid}]: task_id={$task_id}".PHP_EOL;	
        $msg = '';	
        switch ($data['type']) {	
            case 'login':	
                $msg = '我来了...';	
                break;	
            case 'speak':	
                $msg = $data['msg'];	
                break;	
        }	
        foreach ($serv->connections as $fd) {	
            $connectionInfo = $serv->connection_info($fd);	
            if (isset($connectionInfo['websocket_status']) && $connectionInfo['websocket_status'] == 3) {	
                $serv->push($fd, $msg); //长度最大不得超过2M	
            }	
        }	
        $serv->finish($data);	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onFinish($serv,$task_id, $data) {	
        echo "#### onFinish ####".PHP_EOL;	
        echo "Task {$task_id} 已完成".PHP_EOL;	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onClose($serv, $fd) {	
        echo "#### onClose ####".PHP_EOL;	
        echo "client {$fd} closed".PHP_EOL;	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onMessage($serv, $frame) {	
        echo "#### onMessage ####".PHP_EOL;	
        echo "receive from fd{$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}".PHP_EOL;	
        $serv->task(['type' => 'speak', 'msg' => $frame->data]);	
        echo "########".PHP_EOL.PHP_EOL;	
    }	
    public function onRequest($request, $response) {	
        echo "#### onRequest ####".PHP_EOL;	
        $response->header("Content-Type", "text/html; charset=utf-8");	
        $server = $request->server;	
        $path_info    = $server['path_info'];	
        $request_uri  = $server['request_uri'];	
        echo "PATH_INFO:".$path_info.PHP_EOL;	
        if ($path_info == '/favicon.ico' || $request_uri == '/favicon.ico') {	
            return $response->end();	
        }	
        $html = "你好 Swoole.";	
        $response->end($html);	
    }	
    public function onReceive($serv, $fd, $from_id, $data) {	
        echo "#### onReceive ####".PHP_EOL;	
        $length = unpack('N', $data)[1];	
        echo "Length:".$length.PHP_EOL;	
        $msg = substr($data, -$length);	
        echo "Msg:".$msg.PHP_EOL;	
    }	
    public function onPacket($serv, $data, $clientInfo) {	
        echo "#### onPacket ####".PHP_EOL;	
        $serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);	
        var_dump($clientInfo);	
    }	
}	
$server = new Server();

4 个客户端连接的代码分别是:

1、9501 onMessage 处理 WebSocket。可以参考原来文章 Swoole WebSocket 的应用 中的代码即可。

2、9501 onRequest 处理 HTTP。可以参考原来文章 Swoole HTTP 的应用 中的代码即可。

3、9502 onReceive 处理 TCP。可以参考原来文章 Swoole Task 的应用 中的代码即可。

4、9503 onPacket 处理 UDP。

示例代码:

netcat -u 10.211.55.4 9503

小结

一、多端口的应用场景是什么?

比如,开发一个直播网站,直播用一个端口,IM聊天用一个端口。

比如,开发一个RPC服务,数据通讯用一个端口,统计界面用一个端口。

推荐阅读

本文欢迎转发,转发请注明作者和出处,谢谢!

640?wx_fmt=png

标签:echo,协议,Swoole,function,端口,EOL,####,serv,PHP
来源: https://blog.51cto.com/u_15183360/2737293

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有