欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Swoole 多协议 多端口 的应用

程序员文章站 2022-06-22 12:43:04
[TOC] 概述 这是关于 Swoole 学习的第五篇文章:Swoole 多协议 多端口 的应用。 "第四篇:Swoole HTTP 的应用" "第三篇:Swoole WebSocket 的应用" "第二篇:Swoole Task 的应用" "第一篇:Swoole Timer 的应用" 主要参考官方 ......

概述

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

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

  • 网络通信协议设计:
  • 多端口监听的使用:

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

网络通信协议设计

为什么需要通信协议?

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

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

eof结束符协议

Swoole 多协议 多端口 的应用

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

Swoole 多协议 多端口 的应用

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

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

Swoole 多协议 多端口 的应用

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

主要设置项如下:

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

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

示例代码如下:

server.php

<?php

class server
{
    private $serv;

    public function __construct() {
        $this->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

<?php

class client
{
    private $client;

    public function __construct() {
        $this->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();

固定包头+包体协议

Swoole 多协议 多端口 的应用

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

Swoole 多协议 多端口 的应用

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

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

Swoole 多协议 多端口 的应用

主要设置项如下:

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

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

示例代码如下:

server.php

<?php

class server
{
    private $serv;

    public function __construct() {
        $this->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

<?php

class client
{
    private $client;

    public function __construct() {
        $this->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();

多端口监听的使用

Swoole 多协议 多端口 的应用

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

  • 9501 onmessage 处理 websocket。
  • 9501 onrequest 处理 http。
  • 9502 onreceive 处理 tcp。
  • 9503 onpacket 处理 udp。

不多说,看下效果图:

Swoole 多协议 多端口 的应用

示例代码如下:

server.php

<?php

class server
{
    private $serv;

    public function __construct() {
        $this->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 = "<h1>你好 swoole.</h1>";
        $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服务,数据通讯用一个端口,统计界面用一个端口。

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