前面,我们利用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] // 从名字上来看是存的进程号.后面再来处理.