swoole实现socket服务器初探

来源:互联网 发布:淘宝原价现价怎么设置 编辑:程序博客网 时间:2024/05/09 00:28

目标

因为项目中需要实时获取一些行情数据,以前的做法是使用ajax做定时请求,众所周知的原因这样性能会比较差,想用websocket来重新实现。
我这里想要实现的并不是简单的“客户端请求->服务端返回”这样的逻辑,这种实现起来会很简单,我要实现的是在服务器端维护一个列表,当有客户端在这个列表中注册的话,服务端便统一地定时地向这个列表中的用户推送数据,这样要比客户端主动定时请求性能更好一些。

环境

swoole2.0.6
php7.0.14
CentOS7

感想

swoole的工作机制有点打破一般php开发者的思维习惯,如果平时没有去了解一下php底层的服务实现的话,可能对swoole的工作方式会感觉到迷惑。因为php通常是隐藏了其作为服务器一些细节,程序员只需要关心一次请求里所要完成的工作即可。而swoole却把底层的服务与进程机制、线程机制、网络通信这些东西暴露了出来,如果学校里学到的东西都记不清的话,需要花不少时间来进行学习。

与nodejs的一些对比

最初使用nodejs做socket服务器的时候,感觉特别简单,至少对我所需要解决的问题而言,很容易就能达到我的要求。而swoole却不太一样,它提供了更多的技术细节出来,也就使得它在用起来的时候开发者需要去了解和处理很多这些细节上的问题。

上实例

        $serv = new Swoole\Websocket\Server("127.0.0.1", 9502);        $serv->on('Open', function($server, $req) {            echo "connection open: ".$req->fd . "\n";        });        $serv->on('Message', function($server, $req) {            if ($req->data == 'allcoin') {                # 将客户端id加入到广播队列中                Db_Redis::hset(SwooleController::QU_ALLCOIN, $req->fd, 0);                # 首次请求先返回一次数据                if ($allcoin = Db_Redis::hget('market', 'allcoin')) {                    $server->push($req->fd, SwooleController::parseData('allcoin', $allcoin));                }            } else if ($req->data == 'trends') {                # 立即返回数据                if ($trends = Db_Redis::hget('market', 'trends')) {                    $server->push($req->fd, SwooleController::parseData('trends', $trends));                }            }        });        $serv->on('Close', function($server, $fd) {            # 连接断开时将客户端id从队列中删除            Db_Redis::hdel(SwooleController::QU_ALLCOIN, $fd);        });        $serv->on('WorkerStart', function ($server, $workerId) {            echo "worker started: {$workerId}\n";            if ($workerId == 0) {                # 定时推送数据                $server->tick(1000, function() use ($server) {                    if (!$allcoin = Db_Redis::hget('market', 'allcoin')) return;                    foreach ((array)Db_Redis::hkeys(SwooleController::QU_ALLCOIN) as $fd) {                        # 防止意外情况,对队列中的客户端id检查是否存活                        if (!$server->exist($fd)) {                            Db_Redis::hdel(SwooleController::QU_ALLCOIN, $fd);                            continue;                        }                        # 推数据数据 todo 优化,当数据没有更新时,不需要反复推送。                        $server->push($fd, SwooleController::parseData('allcoin', $allcoin));                    }                });            }        });        $serv->start();

简单来说,这段程序实现了一个定时器。swoole的定时器并不像nodejs的定显示器那样可以直接写在server进程里,而是必须写在worker启动之后。如果试图在worker启动之前就添加定时器会报错。而按照swoole的运行机制,worker是会启动很多个的,而我实际上只需要一个服务进程进行数据广播,一开始总是在纠结这么多worker,如果都注册一个定时器,那就相当于多个进程在进行广播,这是没必要的。后来看到一个博客给了我一点启发,那就是判断worker的id,只向其中的一个worker注册定时器。
很简单的一种思路,却让我茅塞顿开,于是就有了上面的代码。
开始的时候,想尝试使用swoole.table来维护广播用户列表,以及要推送的数据,发现table并不适用,因为它实际上只是一个key=>value的缓存而已,而且存储大小有限制,尝试了一下心想还不如直接使用redis。于是就直接把这些数据维护在redis里了。
与nodejs相比,swoole还有一个不方便的地方在于客户端只有一个onmessage,要想让客户端了解传递的是什么数据的话,必须把数据类型封装在消息里,然后让它自己去判断。而nodejs在这方面的实现要自由一些,它可以自定义一些信号,在收到不同的信号时分别处理,如下所示:

socket.on('coins', function(msg){        //code here;});

单就使用方便来说,还是nodejs更好用些。

以上。

参考文章:
1. swoole官方文档
2. Swoole入门-异步毫秒定时器

0 0