手撸原生swoole完整版(http、websocket服务端、代码热更新等) | php 技术论坛-江南app体育官方入口

在上一篇文章中,我们介绍了swoole的安装与基本使用,
在这篇文章中,完整的演示了swoole的使用,
http服务端、websocket服务端、代码热更新,
接收到请求时,转发到真正的逻辑文件进行执行,
在逻辑文件中,如何完成数据交互与执行,
在后期文件中,我们将结合redis队列实现商品的抢购

新建文件 swoole.phpfeng/index.php

swoole.php


/**
 * @author: [feng] <[email protected]>
 * @date:   2024-08-20 13:26:14
 * @last modified by:   [feng] <[email protected]>
 * @last modified time: 2024-08-31 17:38:36
 */
use \swoole\server\helper;
use \swoole\websocket\frame;
use \swoole\websocket\server as websocketserver;
use \swoole\server;
use \swoole\timer;
use \swoole\process;
/**
 * swoole websocket server 命令行服务类
 * 此文件的修改不支持热更新,请于更新后重启swoole-websocket服务
 */
class websocket
{
    protected $monitor;
    protected $md5file;
    // swoole对象
    public $swoole;
    // socket的类型
    protected $socktype = swoole_sock_tcp;
    // 运行模式
    protected $mode = swoole_process;
    // swoolecommon 类实例
    protected $swoolecommon;
    /**
     * swoolecommon 类实例
     */
    protected $config = [
        "wss_switch"    => "0",
        // "ssl_cert_file" => "/www/wwwroot/yourdomain/cert/im.pem",
        // "ssl_key_file"  => "/www/wwwroot/yourdomain/cert/im.key",
        "websocket_port" => "9501",
        "worker_num"    => "2",
        "task_worker_num" => "2",
        "reactor_num"   => "2",
        "max_connections" => "20480",
        'daemonize' => false,
        'max_request' => 100000,
        'task_enable_coroutine' => true, // 关闭task协程
        'max_wait_time' => 10,
        'reload_async' => true, // 异步模式下启动管理进程
    ];
    /**
     * 支持的响应事件
     * @var array
     */
    protected $event = [
        'start',
        'shutdown',
        'workerstart',
        'workerstop',
        'workerexit',
        'receive',
        'close',
        'task',
        'finish',
        'pipemessage',
        'workererror',
        'managerstart',
        'managerstop',
        'open',
        'request',
        'message',
        'handshake',
    ];
    function __construct()
    {
        if ($this->config['wss_switch']) {
            if (file_exists($this->config['ssl_cert_file']) && file_exists($this->config['ssl_key_file'])) {
                $this->socktype = swoole_sock_tcp | swoole_ssl;
            } else {
                throw new \exception('ssl certificate file does not exist!');
            }
        }
        $this->swoole = new websocketserver('127.0.0.1', $this->config['websocket_port'], $this->mode, $this->socktype);
        // 设置文件监控(监控时间秒,监控目录)(正式环境勿开,很占cpu)
        $this->setmonitor(1000, ['./feng/']);
        // 初始化swoole配置
        $this->option($this->config);
        $agreement = $this->config['wss_switch'] ? 'wss://' : 'ws://';
        $address   = $agreement . "127.0.0.1:" . $this->config['websocket_port'];
        echo "swoole websocket server started: <" . $address . ">\n";
        echo "you can exit with `ctrl-c`\n";
        $this->swoole->start();
    }
    /**
     * worker 进程启动
     * @param  $server
     * @param  $worker_id
     */
    public function onworkerstart($server, $worker_id)
    {
        if (0 == $worker_id && $this->monitor) {
            // print_r(get_included_files());// 查看不支持热更新的文件列表
            $this->monitor($server);
        }
    }
    /**
     * 链接握手成功
     * @param  $server
     * @param  $frame
     */
    public function onopen($server, $frame)
    {
        $server->push($frame->fd, json_encode([
            'type' => 'init',
            'client_id' => $frame->fd,
        ]));
    }
    /**
     * request回调
     * 当请求过来时,会调用此方法,然后转发到真正的逻辑文件执行
     * @param $request
     * @param $response
     */
    public function onrequest($request, $response) {
        $data = $request->post ?? ($request->get ?? []);
        // array_walk_recursive($data, ['app\worker\logic\common', 'checkvariable']);
        $server = $this->swoole;//调用外部的server
        $client_id = $request->fd;
        if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
            $response->status(404);
            $response->end();
            return;
        }
        $uri = array_filter(explode('/', ltrim($request->server['request_uri'], '/wss')));
        [$c, $a] = (count($uri) == 1) ? [$uri[0], ''] : ($uri ?: [$data['c'] ?? '', $data['a'] ?? '']);
        $response->header('content-type', 'application/json; charset=utf-8');
        // 检查要访问的类是否存在
        if (empty($data)) {
            $response->end(msg(0, '参数缺失,请填写完整参数'));
            return;
        }
        $filename = __dir__ . '/feng/' . ucfirst($c) . '.php';
        if (file_exists($filename) && !empty($c)) { // 判定文件是否存在
            if (is_readable($filename)) {
                require_once $filename;
            }
            $classname = "\\feng\\" . ucfirst($c);
            // 检查要访问的类是否存在
            if (!class_exists($classname, false)) {
                $response->end(msg(0, '访问的控制器不存在!'));
                return;
            }
        } else {
            $response->end(msg(0, '访问的控制器文件不存在!'));
            return;
        }
        $o = new $classname([$server, $response, $this->swoolecommon]); // 新建对象
        if (!empty($a) && !method_exists($o, $a)) {
            $response->end(msg(0, '访问的方法不存在!'));
            return;
        }
        if (isset($o) && $a) {
            $result = call_user_func_array([$o, $a], [$data]); //调用对象$o($c)里的方法$a
            if ($result) {
                $json = is_array($result) ? msg($result) : $result;
                $response->end($json);
            }
        } else {
            $json = msg(0, '参数缺失,请填写要执行的操作');
            $response->end($json);
        }
    }
    /**
     * 收到数据帧
     * 与request请求类似,转发到真正的逻辑文件执行
     * @param  $server
     * @param  $frame
     */
    public function onmessage($server, $frame)
    {
        $message = json_decode($frame->data, true) ?: $frame->data;
        // 安全检查过i滤
        // array_walk_recursive($message, ['app\worker\logic\common', 'checkvariable']);
        $data = $message['data'] ?? [];
        $client_id = $frame->fd;
        if (!is_array($message) || !isset($message['c']) || !isset($message['a'])) {
            $server->push($client_id, msg('show_msg', '参数缺失,请填写完整参数'));
            return;
        }
        $filename = __dir__ . '/feng/' . ucfirst($message['c']) . '.php';
        if (file_exists($filename)) { // 判定文件是否存在
            if (is_readable($filename)) {
                require_once $filename;
            }
            $classname = "\\feng\\" . ucfirst($message['c']);
            // 检查要访问的类是否存在
            if (!class_exists($classname, false)) {
                $server->push($client_id, msg('show_msg', '访问的控制器不存在!'));
                $server->close($client_id);
                return;
            }
        } else {
            $server->push($client_id, msg('show_msg', '错误的请求2!'));
            $server->close($client_id);
            return;
        }
        $o = new $classname([$server, $frame, $this->swoolecommon]); // 实例化类
        $a = $message['a']; // 方法名称
        if (!method_exists($o, $message['a'])) {
            $server->push($client_id, msg('show_msg', '访问的方法不存在!'));
            return;
        }
        $result = call_user_func_array([$o, $a], [$data]); //调用对象$o($c)里的方法$a
        $json = is_array($result) ? msg($result) : $result;
        $server->push($client_id, $json); // 发送消息
    }
    /**
     * 链接关闭
     */
    public function onclose($server, $fd, $reactorid)
    {
        // 解除所有绑定关系
        // $this->swoolecommon->unbindfd($fd);
    }
    /**
     * @param $serv
     * @param $taskid
     * @param $workerid
     * @param $data
     */
    public function ontask($server, $taskid, $workerid, $data)
    {
        // 分发 task 任务机制,让不同的任务 走不同的逻辑
        // $obj = new app\common\lib\task\task;
        // $method = $data['method'];
        // timer::tick(5000, function() use ($taskid){
        //     echo "taskid  " . $taskid . "  timeout " . rand(1111,9999) . "\n";
        //     $flag = rand(1111,9999);
        //     return $flag; // 告诉worker
        // });
        $flag = rand(1111,9999);
        return $flag; // 告诉worker
    }
    /**
     * @param $server
     * @param $taskid
     * @param $data
     */
    public function onfinish($server, $taskid, $data) {
        echo "taskid:{$taskid}\n";
        echo "finish-data-sucess:{$data}\n";
    }
    public function option(array $option)
    {
        if (!empty($option)) {
            $this->swoole->set($this->checkoptions($option));
        }
        // 注册回调
        foreach ($this->event as $event) {
            if (method_exists($this, 'on' . $event)) {
                $this->swoole->on($event, [$this, 'on' . $event]);
            }
        }
        // 实例化swoolecommon类
        // $this->swoolecommon = new \app\worker\library\common($option['max_connections'], $this->swoole);
    }
    protected function checkoptions(array $options)
    {
        if (class_exists(helper::class)) {
            $constoptions = helper::global_options  helper::server_options  helper::port_options  helper::helper_options;
            foreach ($options as $k => $v) {
                if (!array_key_exists(strtolower($k), $constoptions)) {
                    unset($options[$k]);
                }
            }
        }
        return $options;
    }
    public function setmonitor($interval = 2, $path = [])
    {
        $this->monitor['interval'] = $interval;
        $this->monitor['path']     = (array)$path;
    }
    /**
     * 文件监控
     * @param $server
     */
    public function monitor($server)
    {
        if ($this->monitor['path']) {
            $serverid = $server->tick($this->monitor['interval'], function ($serverid) use ($server) {
                $md5arr=[];
                foreach ($this->monitor['path'] as $path) {
                    $files = glob(rtrim($path, '/') . "/*.php");
                    foreach ($files as $file){
                        $md5arr[] = md5_file($file);
                    }
                }
                $md5value = md5(implode('',$md5arr));
                if ($this->md5file==''){
                    $this->md5file = $md5value;
                    return;
                }
                //文件有改动
                if (strcmp($this->md5file, $md5value)!==0){
                    $this->md5file = $md5value;
                    echo "[update] the service folder has changed and is currently reloading...\n";
                    $server->cleartimer($serverid);
                    process::kill($server->master_pid, sigusr1); // 重启服务
                    return;
                }
            });
            // // 创建一个inotify实例(linux下安装inotify服务,pecl install inotify)
            // $fd = inotify_init();
            // // 添加需要监控的事件
            // $mask = in_delete | in_create | in_moved_from | in_moved_to | in_close_write;
            // foreach ($this->monitor['path'] as $path) {
            //     inotify_add_watch($fd, $path, $mask); // 添加监控目录
            // }
            // // 循环读取inotify事件
            // $serverid = $server->tick($this->monitor['interval'], function ($serverid) use ($server, $fd) {
            //     $events = inotify_read($fd);
            //     if ($events) {
            //         foreach ($events as $event) {
            //             // 检测到文件更改,重启服务
            //             echo "[update] the service folder has changed and is currently reloading...\n";
            //             $server->cleartimer($serverid);
            //             process::kill($server->master_pid, sigusr1); // 重启服务
            //             return;
            //         }
            //     }
            // });
        }
    }
    /**
     * 魔术方法 有不存在的操作的时候执行
     * @access public
     * @param string $method 方法名
     * @param array $args 参数
     * @return mixed
     */
    public function __call($method, $args)
    {
        call_user_func_array([$this->swoole, $method], $args);
    }
}
new websocket();
/**
 * [result 返回状态数组]
 * @param  [type] $code [错误码]
 * @param  string $data [具体数据]
 * @return [type]       [description]
 */
function msg($code, $msg=false, $data=false)
{
    $code = is_string($code) ? trim($code) : $code;
    if (is_string($code) && preg_match('/^[a-za-z_-] $/', $code) === 1) {
        $result = ['type'=>$code, 'data'=>$msg];
    } else {
        if (is_numeric($code)) {
            $result = ['code'=>$code, 'msg'=>$msg, 'data'=>$data, 'time'=>time()];
        } else {
            $msg || $msg = '操作成功';
            $data = $data ?: $code;
            $result = ['code'=>1, 'msg'=>$msg, 'data'=>$code, 'time'=>time()];
        }
    }
    return json_encode($result, json_unescaped_unicode);
}

feng/index.php


/**
 * @author: [feng] <[email protected]>
 * @date:   2024-08-28 13:26:14
 * @last modified by:   [feng] <[email protected]>
 * @last modified time: 2024-08-31 17:49:07
 */
namespace feng;
class index
{
    public $userinfo = [];
    protected $server;
    protected $reids;
    protected $frame;
    protected $request;
    protected $swoolecommon;
    public function __construct($base = [])
    {
        if ($base) {
            [$this->server, $this->frame, $this->swoolecommon] = $base;
        }
        // $this->redis = (new \redis())->init($config); //实例化redis
    }
    public function ceshi($message=[])
    {
        $data = [
            'content'=>'这是一段消息',
            'date' => date('y-m-d h:i:s'),
        ];
        return array_merge($data, $message);
    }
    public function index($value='')
    {
        return 'ceshi消息';
    }
}

开启swoole服务

(windows下使用 swoole swoole.php 开启服务)

php swoole.php

访问http服务接口 http://127.0.0.1:9501/index/ceshi?id=1

本作品采用《cc 协议》,转载必须注明作者和本文链接
讨论数量: 3

: 1:

1个月前

我正在参加豆包marscode活动,需要你的助力,快点击链接帮我助力一下吧:

1个月前

这是采用js的书写格式进行php的代码书写。。有意思。。看来php也是兼容性很强的代码了。。

1个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
网站地图