workerman源码-workerman初始化流程

前面,我们利用composer安装了workerman. 接下来我们就开始用一个简单的demo来初步了解它的流程.

简单demo

// http.php
require __DIR__ . '/vendor/autoload.php';

use Workerman\Worker;

// 创建一个Worker监听2345端口,使用http协议通讯
$http_worker = new Worker("http://0.0.0.0:2345");

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

// 接收到浏览器发送的数据时回复hello world给浏览器
$http_worker->onMessage = function($connection, $data) {
    // 向浏览器发送hello world
    $connection->send('hello world');
};

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

从官网抄的.先直接拿来用.出处.可以使用如下命令来运行

php http.php start
// or win
php http.php

构造函数

上面的示例代码中,创建了一个worker类,并设置监听2345端口. 然后设置了回调信息.就执行运行了.如果不算引入,所有的一切都是从new开始.我们先来看看new的时候干了什么.先贴上源码.一步步来分析.

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']);

    if (static::$_OS === OS_TYPE_LINUX && version_compare(PHP_VERSION,'7.0.0', 'ge')) {
        $php_uname = strtolower(php_uname('s'));
        // If not Mac OS then turn reusePort on.
        if ($php_uname !== 'darwin') {
            $this->reusePort = true;
        }
    }

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

首先.从进入构造函数第一步就是给自己声明的了ID,由对象而来的ID.spl_object_hash可以直接为对象生成一个唯一的ID.你可以理解为uuid的概念.然后下一步,准备开始懵圈吧.

static::$_workers[$this->workerId] = $this;
static::$_pidMap[$this->workerId]  = array();

将自己放入到Worker::$_workers的静态变量中,然后将自己的pidMap也初始话. 至于为何要这么放.后面来说.然后调用了debug_backtrace来获取命令运行的根目录,紧接着接下来就判断自己是否是linux来开启端口复用.

if (static::$_OS === OS_TYPE_LINUX && version_compare(PHP_VERSION,'7.0.0', 'ge')) {
    $php_uname = strtolower(php_uname('s'));
    if ($php_uname !== 'darwin') {
        $this->reusePort = true;    // 开始端口复用
    }
}

这个端口复用在linux和windows上有本地的区别.先过.然后就开始解析我们的协议.协议解析过后.就设置了一个stream_context_create的上下文对象. 至此初始化完毕. 下面我们来看协议解析

协议解析

// parseSocketAddress
protected function parseSocketAddress() {
    if (!$this->_socketName) {
        return;
    }
    // Get the application layer communication protocol and listening address.
    list($scheme, $address) = \explode(':', $this->_socketName, 2);
    // Check application layer protocol class.
    if (!isset(static::$_builtinTransports[$scheme])) {
        $scheme         = \ucfirst($scheme);
        $this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : '\\Protocols\\' . $scheme;
        if (!\class_exists($this->protocol)) {
            $this->protocol = "\\Workerman\\Protocols\\$scheme";
            if (!\class_exists($this->protocol)) {
                throw new Exception("class \\Protocols\\$scheme not exist");
            }
        }

        if (!isset(static::$_builtinTransports[$this->transport])) {
            throw new \Exception('Bad worker->transport ' . \var_export($this->transport, true));
        }
    } else {
        $this->transport = $scheme;
    }

    $local_socket = static::$_builtinTransports[$this->transport] . ":" . $address;
    return $local_socket;
}

在这之前,我们要了解我们的协议.这里如果单开,就又要开一篇文章了.所以我们只了解格式即可.一个套接字.有三部分构成. 第一就是协议类型,http,tcp...等等.第二个就是监听地址.0.0.0.0就是监听本地地址.第三个就是我们的端口.这里我们的端口就是2345.

// http协议
// 0.0.0.0 监听地址
// 2345 监听端口.
http://0.0.0.0:2345

这里我们就监听本地的2345端口用来做一个http协议的服务器. 下面我们来看解析协议这个函数.第一步根据我们提供的socketName来得到对应的协议类型(http)和地址.

list($scheme, $address) = \explode(':', $this->_socketName, 2);

然后就去Worker::$_builtinTransports中去查找协议类型.这里面包含了我们最基础的协议.tcp,udp,unix,和ssl.如果是最基础的就直接保存到$this->transport中.如果不是就稍微麻烦点.

$scheme         = \ucfirst($scheme);    // 将http 转为Http
$this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : '\\Protocols\\' . $scheme;    // 如果第一个是\则会直接存给protocol.如果不是,就是\Protocols\$scheme,显然我们是Protocols\Http.
if (!\class_exists($this->protocol)) {  // 如果不存在类, 就去查看自Workerman自己本身己定义的协议.
    $this->protocol = "\\Workerman\\Protocols\\$scheme";
    if (!\class_exists($this->protocol)) {  // 如果都找不到就报错.
        throw new Exception("class \\Protocols\\$scheme not exist");
    }
}

// 如果不存在,就报错.$this->transport默认为tcp
if (!isset(static::$_builtinTransports[$this->transport])) {
    throw new \Exception('Bad worker->transport ' . \var_export($this->transport, true));
}

从上面代码可以看出,先查找php本身支持或者自己写的协议,如果都没有就找workerman本身的协议.如果是自己定义的协议.都是基于tcp协议来创建自己的协议.这里有几个点,第一.Protocols这个命名空间是什么?这个是php自己定义的协议,包含如下

file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

当然,以上协议php都未实现对应的Protocols.所以都会去查找WorkermanProtocols下的解析.至此初始化就彻底完成了.

未处理的点

以上,初始化的疑问. 在后续的过程中慢慢来了解.

$this->reusePort = true;    // 端口复用,只要系统不是mac和windows.这个值都为true.
Worker::$_workers           // 这里保存了所有的worker
Worker::$_pidMap[$this->workerId]    // 从名字上来看是存的进程号.后面再来处理.

你可能感兴趣的:(php,workerman)