Swoole从入门到入土(9)——TCP服务器[协程风格]

        上一篇,我们一起初步接触了协程。我相信只有一节的讨论,很多小伙伴对于“协程”与“线程”的区分可能还有点模糊。我们这里以两者的比较作为本篇开头,进行一番比较。

        首先,“协程”与“线程”的任务调度机制不一样。“协程”重在“协调”,“线程”重在“抢占”。

        举个例子:现在有一个任务,需要5个“子协程”同时进行处理,那么执行顺序是:子协程1执行->子协程1暂停->子协程2执行->子协程2暂停……顺序执行,直到结束。所以,协程的特点是某个协程让出时间片后,下一个协程会接上;如果这个协程还在阻塞中,会再次移交给下一个协程。这样往复执行,直到结束。

        同样的,如果现在有一个任务,需要5个“线程”同时执行,那么执行顺序是,主线程同时开辟启动5个线程,这5个线程哪一个先执行,当先执行的线程让出时间片后,接下来由哪一个线程接上,这些完全听天由命。所以线程的执行才叫做“抢占式”,直到任务结束。

        其次,“协程”与“线程”解决的事情不一样。“协程”解决的的是“并发”问题,“线程”解决的是“并行”问题。

        举个栗子,我们的服务器需要支撑500人同时访问,以大名鼎鼎的TOMCAT为例,正常情况下会开辟300个线程进行处理。某个线程一旦接收了请求就会进行阻塞,在处理完成之前就不会再接收第二个请求,这就决定了这时TOMCAT只能“并行”支持300人同时访问,另外的200人只能收到500的报错。但是这里存在一个问题,500人的请求业务并不是全都是阻塞行为(访问数据库、读写文件等),所以我们完全可以在一个线程中开辟N个协程,一个线程同时接受N个请求,对于有阻塞的业务请求进行等待直到业务完成,对于没有阻塞的请求,我们就可以立即响应,这样就可以将“并发”从300,提高到300*N(N>=2)。

        当然,从上面这个栗子,我们可以看出“协程”针对的是IO密集型的业务(多阻塞),而线程针对的是计算密集型的业务。但我们不能简单认为IO密集型业务,协程开得越多越好。因为对于IO的处理也需要IO资源,如果同时接受太多的需要IO请求,服务器对于IO的处理在超时时间范围内无法处理完,同样会出现超时错误。

        有了以上的比较,我们对于“协程”与“线程”的比较应该是更加明晰了。接下来,我们就一起进入协程风格的TCP服务器话题。先看以下这段代码:

//多进程管理模块
$pool = new Swoole\Process\Pool(2);
//让每个OnWorkerStart回调都自动创建一个协程
$pool->set(['enable_coroutine' => true]);
$pool->on('workerStart', function ($pool, $id) {
    //每个进程都监听9501端口
    $server = new Swoole\Coroutine\Server('127.0.0.1', '9501' , false, true);

    //收到15信号关闭服务
    Swoole\Process::signal(SIGTERM, function () use ($server) {
        $server->shutdown();
    });

    //接收到新的连接请求 并自动创建一个协程
    $server->handle(function (Swoole\Coroutine\Server\Connection $conn) {
        while (true) {
            //接收数据
            $data = $conn->recv();
            if (empty($data)) {
                $conn->close();
                break;
            }

            //发送数据
            $conn->send('hello');

            \Co::sleep(1);
        }
    });

    //开始监听端口
    $server->start();
});
$pool->start();

这是一段完整的协程风格TCP服务器的代码,对于这段代码涉及到的技术点进行以下讨论。

1、关于Swoole\Process\Pool

 对于协程代码,我们一般需要一个“协程容器”,来保证一组协程的完整性。在swoole中,创建“协程容器”的方法有两种(编者注:官网写的是三种,但这里不考虑将异步风格转为协程风格的方式):

1) 调用管理模块 Process 和 Process\Pool 的 start 方法,指定构造函数的 enable_coroutine 参数,此种启动方式会在进程启动的时候创建协程容器。

2) 手动创建协程容器,即调用Co\run() 函数,或者使用Coroutine\Scheduler(这两者是一样的,前者是后者的简易调用)。

对于TCP服务器,我们需要并发,需要多个进程进行管理,所以选择了Swoole\Process\Pool(这个进程池的使用,后续我们会详细讨论)。

对于Pool初使化后,在子进程创建时(onWorkerStart事件触发),内部实例化了协程化的TCP服务器。

当$pool->start()调用后,这时会创建出2个子进程(Pool构造函数中传入的子进程数量),所以创建后共有3个进程。主进程进入wait状态,对子进程进行管理,一旦子进程退出,就会再进行启动。这时我们可以把onWorkerStart事件的处理函数看成是一个大的协程容器。

2、Swoole\Coroutine\Server->__construct

Swoole\Coroutine\Server->__construct(string $host, int $port = 0, bool $ssl = false, bool $reuse_port = false);

$host:监听的地址,有三种格式:0.0.0.0/127.0.0.1: IPv4 地址  ||  ::/::1: IPv6 地址  ||  unix:/tmp/test.sock: UnixSocket 地址

$port:监听的端口【如果为 0 将由操作系统随机分配一个端口】

$ssl:是否开启 SSL 加密

$reuse_port:是否开启端口重用

Swoole\Coroutine\Server 是一个完全协程化的类,用于创建协程 TCP 服务器,支持 TCP 和 unixSocket 类型。它与 Server 模块不同之处在于以下两点:

1) 动态创建销毁,在运行时可以动态监听端口,也可以动态关闭服务器;

2) 处理连接的过程是完全同步的,程序可以顺序处理 Connect、Receive、Close 事件

3、Swoole\Coroutine\Server->handle:设置连接处理函数,必须在 start() 之前设置处理函数

Swoole\Coroutine\Server->handle(callable $fn);

$fn:设置连接处理函数

注意:

-服务器在 Accept(建立连接) 成功后,会自动创建协程并执行 $fn
-$fn 是在新的子协程空间内执行,因此在函数内无需再次创建协程
-$fn 接受一个参数,类型为 Swoole\Coroutine\Server\Connection 对象
-可以使用 exportSocket() 得到当前连接的 Socket 对象

示例:

$server->handle(function (Swoole\Coroutine\Server\Connection $conn) {
    while (true) {
        $data = $conn->recv();
    }
});

4、关于粘包处理Swoole\Coroutine\Server->set

Swoole\Coroutine\Server->set(array $options);

用法与异步风格的TCP服务器完全一致,如果已经遗忘的小伙伴,请点击这里

示例:

$server->set([
    'open_eof_split' => true,
    'package_eof' => "abc"
]);

如果客户端发送,1234abc5678abc,则服务端就会recv到两条数据,分别是1234abc和5678abc。

关于协程风格的TCP服务器就介绍到这里。如果想要了解协程风格的类Swoole\Coroutine\Server、Swoole\Coroutine\Server\Connection、Swoole\Process\Pool请阅读官网,这部分非常直观没有什么太多难点。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值