• 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 SwooleProcessPool(2);
    //让每个OnWorkerStart回调都自动创建一个协程
    $pool->set(['enable_coroutine' => true]);
    $pool->on('workerStart', function ($pool, $id) {
        //每个进程都监听9501端口
        $server = new SwooleCoroutineServer('127.0.0.1', '9501' , false, true);
    
        //收到15信号关闭服务
        SwooleProcess::signal(SIGTERM, function () use ($server) {
            $server->shutdown();
        });
    
        //接收到新的连接请求 并自动创建一个协程
        $server->handle(function (SwooleCoroutineServerConnection $conn) {
            while (true) {
                //接收数据
                $data = $conn->recv();
                if (empty($data)) {
                    $conn->close();
                    break;
                }
    
                //发送数据
                $conn->send('hello');
    
                Co::sleep(1);
            }
        });
    
        //开始监听端口
        $server->start();
    });
    $pool->start();

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

    1、关于SwooleProcessPool

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

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

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

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

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

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

    2、SwooleCoroutineServer->__construct

    SwooleCoroutineServer->__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:是否开启端口重用

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

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

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

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

    SwooleCoroutineServer->handle(callable $fn);

    $fn:设置连接处理函数

    注意:

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

    示例:

    $server->handle(function (SwooleCoroutineServerConnection $conn) {
        while (true) {
            $data = $conn->recv();
        }
    });

    4、关于粘包处理SwooleCoroutineServer->set

    SwooleCoroutineServer->set(array $options);

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

    示例:

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

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

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

    ---------------------------  我是可爱的分割线  ----------------------------

    最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

  • 相关阅读:
    【野生程序员】:优先招聘
    C#-面向对象:争议TDD(测试驱动开发)
    培训班的同学,拜托不要把用人单位想得那么傻,好不好?!
    为什么要讲数据结构和算法?以及如何学习数据结构和算法
    关于办技术线下社区的一些思考
    做了十年的程序员,为什么我没有加班
    编程新手如何理解“面向对象”
    .NET程序员不加班——写在《华为工程师猝死,36岁,22月无休》之后
    “6年的程序员还不会写委托”,问题在哪?
    现身说法:37岁老码农找工作
  • 原文地址:https://www.cnblogs.com/ddcoder/p/13774214.html
Copyright © 2020-2023  润新知