前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >wokerman启动分析

wokerman启动分析

作者头像
用户2825413
发布2019-07-15 17:55:53
6340
发布2019-07-15 17:55:53
举报

workerman官方描述

Workerman是一款纯PHP开发的开源高性能异步PHP socket框架。支持高并发,超高稳定性,被广泛的用于手机app、移动通讯,微信小程序,手游服务端、网络游戏、PHP聊天室、硬件通讯、智能家居、车联网、物联网等领域的开发。 支持TCP长连接,支持Websocket、HTTP等协议,支持自定义协议。拥有异步Mysql、异步Redis、异步Http、MQTT物联网客户端、异步消息队列等众多高性能组件。

正如标题,我们把范围缩小.来看下启动wokerman时候源码涉及到的知识点:

如何启动一个服务

代码语言:javascript
复制
require_once "Autoloader.php";

$http_worker = new \Workerman\Worker("http://0.0.0.0:2347");
$http_worker->count = 2;

$http_worker->onMessage = function ($connection, $data) {
    $connection->send('hello baby');
};

$http_worker->runAll();

上面是一个最简单的一个例子,Wokerman类初始化时候传递了协议类型和服务地址【http类型】, 然后设置了进程数量为2,绑定了事件回调处理【onMessage】,最后核心的一步是启动这个服务

下面一步一步看下内部的实现:

调用:

代码语言:javascript
复制
$http_worker = new \Workerman\Worker("http://0.0.0.0:2347");
  1. 初始化
代码语言:javascript
复制
// Save all worker instances.
$this->workerId                    = spl_object_hash($this);
static::$_workers[$this->workerId] = $this;
static::$_pidMap[$this->workerId]  = array();

spl_object_hash 将对象生成一个hash值,初始化<pre>不能识别此Latex公式: _workers和</pre>_pidMap数组

  1. 设置自动加载目录
代码语言:javascript
复制
// Get autoload root path.
  $backtrace                = debug_backtrace();
  $this->_autoloadRootPath = dirname($backtrace[0]['file']);

注意debug_backtrace 产生一条 PHP 的回溯跟踪,此处只是获取执行脚本的目录,如果兼容5.3.6以下版本,建议debug_backtrace(false)或更高版本使用debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS),忽略 "args" 的索引,能够节省内存占用

  1. 创建资源流上下文 stream_context_create 这个是php流操作里面的一个函数,具体作用如下
代码语言:javascript
复制
$opts = array(
    'http' => array(
        'method'  => "GET",
        'header'  => "Accept-language: en\r\n" .
            "Cookie: foo=bar\r\n",
        'timeout' => 1,
    )
);

$context = stream_context_create($opts);

$text = file_get_contents('https://facebook.com',false,$context);
//同样适用fopen
var_dump($text); //false

上面的是个简单的stream_context_create的示例, http协议设置了请求方法 、header头、超时时间,file_get_contents请求的是一个墙外的地址,第三个参数是需要一个资源对象,这里过1s钟后如果请求不到将会返回false. 流是一个很大的话题,可以做很多有意思的事情,这里不再展开,对此感兴趣可以参考http://www.php.net/manual/zh/ref.stream.php

下面继续回到程序主逻辑初始化后:

1.设置进程数[准确的讲应该成为worker进程数]

代码语言:javascript
复制
$http_worker->count = 2; //此处后期再讲

2.绑定事件

代码语言:javascript
复制
$http_worker->onMessage = function ($connection, $data) {
    $connection->send('hello baby');
};

上述表示当接收到一个消息的时候,触发onMessage绑定的function函数。 <pre>不能识别此Latex公式: connection表示为连接对象.用于操作客户端连接,发送数据 关闭连接等。 </pre>connection->send() 发送数据给客户端。 \$data 表示接收的数据。

3.启动服务

代码语言:javascript
复制
$http_worker->runAll();

此处为该启动流程分析的核心,下面我们一起看下这里面执行了哪些操作.

代码语言:javascript
复制
protected static function checkSapiEnv()
{
    // Only for cli.
    if (php_sapi_name() != "cli") {
        exit("only run in command line mode \n");
    }
    if (DIRECTORY_SEPARATOR === '\\') {
        self::$_OS = OS_TYPE_WINDOWS;
    }
}

php_sapi_name函数获取运行模式。 DIRECTORY_SEPARATOR 根据系统分隔符判断是否是windows操作系统

代码语言:javascript
复制
foreach (static::$_workers as $worker_id => $worker) {
    $new_id_map = array();
    $worker->count = $worker->count <= 0 ? 1 : $worker->count;
    for($key = 0; $key < $worker->count; $key++) {
        $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0;
    }
    static::$_idMap[$worker_id] = $new_id_map;
}

注意我们从开始设置的$worker->count参数,此处绑定woker_id => 一组count数量的进程,此处我们只有一个。

我们再来看看如何注册信号量的

代码语言:javascript
复制
protected static function installSignal()
{
    if (static::$_OS !== OS_TYPE_LINUX) {
        return;
    }
    // stop
    pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false);
    // graceful stop
    pcntl_signal(SIGTERM, array('\Workerman\Worker', 'signalHandler'), false);
    // reload
    pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false);
    // graceful reload
    pcntl_signal(SIGQUIT, array('\Workerman\Worker', 'signalHandler'), false);
    // status
    pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false);
    // connection status
    pcntl_signal(SIGIO, array('\Workerman\Worker', 'signalHandler'), false);
    // ignore
    pcntl_signal(SIGPIPE, SIG_IGN, false);
}

首先信号量只能运行在linux环境下。 核心函数 pcntl_signal,安装一个信号处理器

代码语言:javascript
复制
bool pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls = true ] )

保存pid

代码语言:javascript
复制
static::saveMasterPid();

绘制命令端界面

代码语言:javascript
复制
tatic::displayUI();

根据上面存储的static::<pre>不能识别此Latex公式: _workers和static::</pre>_pidMap 循环fork进程

代码语言:javascript
复制
while (count(static::$_pidMap[$worker->workerId]) < $worker->count) {
    static::forkOneWorkerForLinux($worker);
}

核心函数 pcntl_fork()

在当前进程当前位置产生分支(子进程)。译注:fork是创建了一个子进程,父进程和子进程 都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程号,而子进程得到的是0.

调用listen 根据调用选择引入Protocols下协议文件

-------写的我好想去死,我先休息下再继续写---------

2018-09-16 天气晴

代码语言:javascript
复制
$pid = pcntl_fork();
//父进程和子进程都会执行下面代码
if ($pid == -1) {
    //错误处理:创建子进程失败时返回-1.
     die('could not fork');
} else if ($pid) {
     //父进程会得到子进程号,所以这里是父进程执行的逻辑
     pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
} else {
     //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}

一部分信号表:

代码语言:javascript
复制
SIGHUP 1 A 终端挂起或者控制进程终止 
SIGINT 2 A 键盘中断(如break键被按下) 
SIGQUIT 3 C 键盘的退出键被按下 
SIGILL 4 C 非法指令 
SIGABRT 6 C 由abort(3)发出的退出指令 
SIGFPE 8 C 浮点异常 
SIGKILL 9 AEF Kill信号 
SIGSEGV 11 C 无效的内存引用 
SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道 
SIGALRM 14 A 由alarm(2)发出的信号 
SIGTERM 15 A 终止信号 
SIGUSR1 30,10,16 A 用户自定义信号1 
SIGUSR2 31,12,17 A 用户自定义信号2 
SIGCHLD 20,17,18 B 子进程结束信号 
SIGCONT 19,18,25 进程继续(曾被停止的进程) 
SIGSTOP 17,19,23 DEF 终止进程 
SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键 
SIGTTIN 21,21,26 D 后台进程企图从控制终端读 
SIGTTOU 22,22,27 D 后台进程企图从控制终端写 

捕获信号小例子

代码语言:javascript
复制
<?php
#为了pcntl能够截获信号
//declare(ticks = 1);

class SignalManage
{
    /**
     * 截获信号处理
     *
     * @param $signal
     */
    public static function signalHandler($signal)
    {
        echo $signal.'退出了'.PHP_EOL;
    }
}

//注册信号量
pcntl_signal(SIGINT, ['SignalManage', 'signalHandler'],false);

$pid = pcntl_fork();
if ($pid) {
    pcntl_wait($status, WUNTRACED);
    echo "pcntl_wait return\n";
} else {
    sleep(1000);
}

//向当前进程发送SIGUSR1信号
//posix_kill(posix_getpid(), SIGUSR1);

pcntl_signal_dispatch();

output:

代码语言:javascript
复制
^C
pcntl_wait return
2退出了
2退出了

declare(ticks = 1)效率低下,每一行都检查信号发生,建议使用pcntl_signal_dispatch 捕获

多进程执行

代码语言:javascript
复制
// 3个子进程处理任务
for ($i = 0; $i < 3; $i++){
    $pid = pcntl_fork();

    if ($pid == -1) {
        die("could not fork");

    } elseif ($pid) {
        echo "I'm the Parent $i\n";

    } else {// 子进程处理

        sleep(5);
        echo "success\n";
        exit($i);// 一定要注意退出子进程,否则pcntl_fork() 会被子进程再fork,带来处理上的影响。
    }
}

// 等待子进程执行结束
while (pcntl_waitpid(0, $status) != -1) {
    $status = pcntl_wexitstatus($status);
    echo "Child $status completed\n";
}

output:

代码语言:javascript
复制
I'm the Parent 0
I'm the Parent 1
I'm the Parent 2
success
success
success
Child 0 completed
Child 1 completed
Child 2 completed

上面的代码实现了并行的运行。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 呆呆熊的技术路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • workerman官方描述
相关产品与服务
物联网
腾讯连连是腾讯云物联网全新商业品牌,它涵盖一站式物联网平台 IoT Explorer,连连官方微信小程序和配套的小程序 SDK、插件和开源 App,并整合腾讯云内优势产品能力,如大数据、音视频、AI等。同时,它打通腾讯系 C 端内容资源,如QQ音乐、微信支付、微保、微众银行、医疗健康等生态应用入口。提供覆盖“云-管-边-端”的物联网基础设施,面向“消费物联”和 “产业物联”两大赛道提供全方位的物联网产品和解决方案,助力企业高效实现数字化转型。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档