Swoole4x探究之多进程TCP协程服务实现

23次阅读

共计 2365 个字符,预计需要花费 6 分钟才能阅读完成。

有研究过 Workman 框架的同学就会发现,其实 workman 最核心的,就是用了 php socket 拓展加上 pcntl 拓展来实现其底层的网络服务和多进程调度。那我们今天就来探讨如何使用 Swoole 的 CoroutineSocket 模块来实现自己的 tcp 服务。
我们先编写一段小的测试代码,test.php 代码如下

$socket = new Co\Socket(AF_INET, SOCK_STREAM, 0);
$socket->bind('127.0.0.1', 9601);
$socket->listen(128);

go(function () use ($socket) {while(true) {$client = $socket->accept(-1);
        $data = $client->recv(64,10);
        var_dump('Recv:'.$data);
        $client->sendAll('reply at'.time());
        $client->close();}
});

我们执行

php test.php

并新建一个 cmd 控制台,用 telnet 模拟 tcp 客户端,可以看到如下结果:

telnet 127.0.0.1 9601
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
asd
reply at 1559713416
Connection closed by foreign host

以上说明我们已经成功建立了一个简单的 TCP 服务器。而有细心的同学就会发现,以上代码如果我在 recv 后,有一些数据库行为发生,那我该 TCP 服务器在同一时间就仅仅只能 accept 链接并处理一个链接的事情,并发能力接近于 1。因此我们可以做一点小小的改进,如下:

$socket = new Co\Socket(AF_INET, SOCK_STREAM, 0);
$socket->bind('127.0.0.1', 9601);
$socket->listen(128);

go(function () use ($socket) {while(true) {$client = $socket->accept(-1);
        go(function () use ($client){$data = $client->recv(64,10);
            var_dump('Recv:'.$data);
            // 模拟数据库耗时, 假定我们的数据库也是用协程 api
            \co::sleep(1);
            $client->sendAll('reply at'.time());
            $client->close();});
    }
});

我们利用协程,把连接 accept 后的逻辑,全部放到另外一个子协程当中处理,让我们的 TCP 服务器可以继续 accept 连接,也就提高了我们的并发能力。然而,在实际的编程中,我们不可能做到完全的百分百协程 API, 而且我的机器也是多核心的处理器,那么此刻我如何尽可能的利用我的 CPU 呢?因此我们可以利用端口复用的特性和 Swoole 的 Process 来构建一个多进程 TCP 协程服务器。

引入 Process

因为在本章节中,对 Swoole Process 的封装不是我们关心的,因此我们这里直接使用 EasySwoole 封装好的进程组件。

composer require easyswoole/component

实现我的 Process Class,代码如下

use Co\Socket;
use EasySwoole\Component\Process\AbstractProcess;

class Server extends AbstractProcess
{protected function run($arg)
    {$socket = new Socket(AF_INET, SOCK_STREAM, 0);
        // 关键在这里,允许复用
        $socket->setOption(SOL_SOCKET,SO_REUSEPORT,true);
        $socket->setOption(SOL_SOCKET,SO_REUSEADDR,true);
        $socket->bind('127.0.0.1', 9601);
        $socket->listen(128);
        go(function () use ($socket) {while(true) {$client = $socket->accept(-1);
                go(function () use ($client){$data = $client->recv(64,10);
                    var_dump('Recv:'.$data);
                    // 模拟数据库耗时, 假定我们的数据库也是用协程 api
                    \co::sleep(1);
                    $client->sendAll('reply at'.time());
                    $client->close();});
            }
        });
    }
}

我们在以上代码中,加入了允许端口复用的选项,否则在多进程模式下,会导致监听失败。我们在 cli 模式下,测试起五个进程

for ($i = 1;$i < 5;$i++){$p = new Server("TcpServer.{$i}");
    $p->getProcess()->start();
}
// 主进程要等待回收
while($ret = \Swoole\Process::wait()) {echo "PID={$ret['pid']}\n";
}

我们,这样就很简单的实现了一个多进程的 TCP 协程服务。总而言之,Swoole 4.x 的协程能力,还是很强大的,让 PHPer 可以以最小的代价来实现一个高性能的 TCP 服务,而不是需要去学习新的一门语言,若需要更多的完善代码,可以参考 http://easyswoole.com/ 这个框架的项目代码,如果有喜欢的同学,可以随意点个 star ,github 地址 https://github.com/easy-swool…

正文完
 0