workerman源码分析之启动

作者: 白云飞 分类: workerman,workerman源码 发布时间: 2018-03-06 11:01 阅读:

分析workerman的源码,首先我们从一个简单的入门脚本开始,拿官方的一个 tcp server的例子,开启我们的旅程吧 server.php:

<?php
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';

// 创建一个Worker监听2347端口,不使用任何应用层协议
$tcp_worker = new Worker("tcp://0.0.0.0:2347");

// 启动4个进程对外提供服务
$tcp_worker->count = 4;

// 当客户端发来数据时
$tcp_worker->onMessage = function($connection, $data)
{
	$connection->maxSendBufferSize = 10;
    // 向客户端发送hello $data
    $connection->send('hello world');
};

// 运行worker
Worker::runAll();

 上面的例子,可以在官方手册中找到。

我们来一行一行的分析。

use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';

这两行基本没什么说头,应用命名空间(还不懂命名空间的各位,现在已经是php 7.2 了!),引入自动导入脚本。

// 创建一个Worker监听2347端口,不使用任何应用层协议
$tcp_worker = new Worker("tcp://0.0.0.0:2347");

这一段重点来了,我们创建一个tcp server。重点跟进 Worker.php 的 __construct 方法:

/**
     * Construct.
     *
     * @param string $socket_name
     * @param array  $context_option
     */
    public function __construct($socket_name = '', $context_option = array())
    {
        // Save all worker instances.
        $this->workerId                  = spl_object_hash($this);
        static::$_workers[$this->workerId] = $this;
        static::$_pidMap[$this->workerId]  = array();

        // Get autoload root path.
        $backtrace                = debug_backtrace();
        $this->_autoloadRootPath = dirname($backtrace[0]['file']);

        // Context for socket.
        if ($socket_name) {
            $this->_socketName = $socket_name;
            if (!isset($context_option['socket']['backlog'])) {
                $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG;
            }
            $this->_context = stream_context_create($context_option);
        }
    }

来大家一起来看看,这个 __construct 到底做了什么。

// Save all worker instances.
$this->workerId                  = spl_object_hash($this);
static::$_workers[$this->workerId] = $this;
static::$_pidMap[$this->workerId]  = array();

这边代码注释的还挺清晰的,首先将 主进程worker对象通过 spl_object_hash 处理之后,作为 主进程worker的标识,赋给 workerId 变量,然后将 主进程worker对象存入,$_workers 数组,以自己的id作为key,方便后面识别。再存储一个 $_pidMap数组,这个数组,存放的是所有worker的 进程ID,也是以worker的hash的值作为key,后面检索。

// Get autoload root path.
$backtrace                = debug_backtrace();
$this->_autoloadRootPath = dirname($backtrace[0]['file']);

获取当前脚本的信息,并且将当前脚本 (也就是我的 server.php) 的位置,赋值给 $_autoloadRootPath,方便后面的各种类库的加载,有了跟路径,都好说。

// Context for socket.
if ($socket_name) {
    $this->_socketName = $socket_name;
    if (!isset($context_option['socket']['backlog'])) {
        $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG;
    }
    $this->_context = stream_context_create($context_option);
}

此处是重点,首先判断了 $socket_name 是否定义,此处我们定义的是 tcp://0.0.0.0:2347 ,因此很显然,我们定义了。

正常的 0.0.0.0表示全部网络,指的是全部网络都能连到这台主机上

将我们定义的 $socket_name 赋值给 $_socketName 方便后面根据这个,选择合适的协议去解析 数据包 。然后又检测了 $context_option[‘socket’][‘backlog’] ,很显然,我们并没有给上下文数组设置这个值,因此,他会取默认的 static::DEFAULT_BACKLOG 的值,也就是 102400。相信 有些人,会像我一样,好奇,这个 backlog 到底是什么东西呢?来我们一起来看看,workerman中的注释:

Backlog is the maximum length of the queue of pending connections

我相信看了这段注释之后,你还是像我一样的一脸懵逼。那我们一定要 仔细的想清楚,这个到底是什么意思。我们翻开 《unix网络编程(卷一)》去寻找答案。简单的说:

listen之后内核为给定的监听套接字维护两个队列:未完成连接队列,已完成连接队列。未完成连接队列:已接受客户端的SYN分节,而且已经发送了第二个SYN分节和第一个SYN分节的ACK。已完成连接队列:在未连接基础上接受到了客户端的ACK响应,TCP3路握手完成(此时accept并没有参与),等待accept从该队列头返回。 backlog参数就是指已完成连接队列的个数

 

我们都知道,服务器建立的过程,create() —> bind() —> listen() —> accept() ,这里这个 backlog 参数是listen 参数的。我们看一下 php 的 socekt_listen() 的原型:

bool socket_listen ( resource $socket [, int $backlog = 0 ] )

 

这里为什么,我们采用上下文的形式,而不是直接在listen的时候再去用,原因就在于。workerman 是采用 stream 系列函数创建 server 的,就是说,这个函数一次性帮我们完成了 上面的 create() —> bind() —> listen() 这三个步骤。

最后,$this->_context = stream_context_create($context_option); 创建上下文,并记录下来。到此处,实例化worker部分就算是完成了。下面又经历了什么,下节再讲。

 

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!