更新 机器人
parent
f545cfe73a
commit
b058de4a21
@ -0,0 +1,6 @@
|
||||
APP_NAME=子千
|
||||
TIMEZONE=Asia/Shanghai
|
||||
DB_HOST=127.0.0.1
|
||||
DB_DATABASE=xxx
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=xxxxxx
|
||||
@ -0,0 +1,5 @@
|
||||
.idea
|
||||
.DS_Store
|
||||
logs/workerman.log
|
||||
logs/workerman.pid
|
||||
.env
|
||||
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Lib;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
$bot_loop = new Worker();
|
||||
$bot_loop->count = 1;
|
||||
$bot_loop->name = 'ComboChange';
|
||||
function ComboChange($db, $table_name, $task, $content)
|
||||
{
|
||||
/*** status
|
||||
* 1-待执行
|
||||
* 2-执行中
|
||||
* 3-已完成
|
||||
* 4-执行报错
|
||||
* 5-已删除
|
||||
*/
|
||||
Tool::log("任务进度[999/100]", 2);
|
||||
|
||||
$message = '处理完成';
|
||||
Tool::log("{$message}[{$task['id']}]", 2);
|
||||
Db2::u($db, $table_name, [
|
||||
'message' => $message,
|
||||
'status' => 3,
|
||||
], 'where id = ?', [$task['id']]);
|
||||
}
|
||||
|
||||
function ComboChangeFunc()
|
||||
{
|
||||
$db = Db::get();
|
||||
$table_name = 'bot_task';
|
||||
$task = $db->getRow("select * from $table_name where status = ? or status = ? order by id asc", [1, 2]);
|
||||
if (!!$task) {
|
||||
Tool::log("检测到任务[{$task['id']}]", 2);
|
||||
if (strtotime($task['create_time']) - time() < 60 * 60 * 24 * 3) {
|
||||
$message = '文件超时';
|
||||
Tool::log("{$message}[{$task['id']}]", 5);
|
||||
Db2::u($db, $table_name, [
|
||||
'message' => $message,
|
||||
'status' => 4,
|
||||
], 'where id = ?', [$task['id']]);
|
||||
} else {
|
||||
Db2::u($db, $table_name, [
|
||||
'start_time' => date('Y-m-d H:i:s'),
|
||||
'status' => 2,
|
||||
], 'where id = ?', [$task['id']]);
|
||||
}
|
||||
if (!!is_file($task['path'])) {
|
||||
$file_content = file_get_contents($task['path']);
|
||||
if (!!json_decode($file_content, true)) {
|
||||
ComboChange($db, $table_name, $task, json_decode($file_content, true));
|
||||
} else {
|
||||
$message = '解析JSON失败';
|
||||
Tool::log("{$message}[{$task['id']}]", 5);
|
||||
Db2::u($db, $table_name, [
|
||||
'message' => $message,
|
||||
'status' => 4,
|
||||
], 'where id = ?', [$task['id']]);
|
||||
}
|
||||
} else {
|
||||
$message = '文件不存在';
|
||||
Tool::log("{$message}[{$task['id']}]", 5);
|
||||
Db2::u($db, $table_name, [
|
||||
'message' => $message,
|
||||
'status' => 4,
|
||||
], 'where id = ?', [$task['id']]);
|
||||
}
|
||||
} else {
|
||||
Tool::log('未检测到任务');
|
||||
}
|
||||
}
|
||||
|
||||
$bot_loop->onWorkerStart = function () {
|
||||
ComboChangeFunc();
|
||||
Timer::add(10, function () {
|
||||
ComboChangeFunc();
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Lib;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
$bot_loop = new Worker();
|
||||
$bot_loop->count = 1;
|
||||
$bot_loop->name = 'DeleteChangeFile';
|
||||
|
||||
function DeleteChangeFileFunc()
|
||||
{
|
||||
$db = Db::get();
|
||||
$table_name = 'bot_task';
|
||||
$task = $db->getRow("select * from $table_name where status = ? order by id asc", [3]);
|
||||
if (!!$task) {
|
||||
Tool::log("检测到删除任务[{$task['id']}]", 2);
|
||||
if (!!is_file($task['path'])) {
|
||||
unlink($task['path']);
|
||||
}
|
||||
Db2::u($db, $table_name, [
|
||||
'status' => 5,
|
||||
], 'where id = ?', [$task['id']]);
|
||||
} else {
|
||||
Tool::log('未检测到删除任务');
|
||||
}
|
||||
}
|
||||
|
||||
$bot_loop->onWorkerStart = function () {
|
||||
DeleteChangeFileFunc();
|
||||
Timer::add(10, function () {
|
||||
DeleteChangeFileFunc();
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* //TODO
|
||||
* DateTime: 2019/8/28 13:52
|
||||
* File: start.php
|
||||
*/
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
require_once "workerman/Autoloader.php";
|
||||
\Workerman\Autoloader::setRootPath(__DIR__);
|
||||
$config = [];
|
||||
$config['workerman']['pidFile'] = __DIR__ . '/logs/workerman.pid';
|
||||
$config['workerman']['logFile'] = __DIR__ . '/logs/workerman.log';
|
||||
Worker::$pidFile = $config['workerman']['pidFile'];
|
||||
Worker::$logFile = $config['workerman']['logFile'];
|
||||
foreach (glob(__DIR__ . '/bot/bot_*.php') as $start_file) {
|
||||
require_once $start_file;
|
||||
}
|
||||
Worker::runAll();
|
||||
@ -0,0 +1,5 @@
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
.idea
|
||||
.DS_Store
|
||||
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman;
|
||||
|
||||
/**
|
||||
* Autoload.
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
/**
|
||||
* Autoload root path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_autoloadRootPath = '';
|
||||
|
||||
/**
|
||||
* Set autoload root path.
|
||||
*
|
||||
* @param string $root_path
|
||||
* @return void
|
||||
*/
|
||||
public static function setRootPath($root_path)
|
||||
{
|
||||
self::$_autoloadRootPath = $root_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load files by namespace.
|
||||
*
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function loadByNamespace($name)
|
||||
{
|
||||
$class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name);
|
||||
if (strpos($name, 'Workerman\\') === 0) {
|
||||
$class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php';
|
||||
} else {
|
||||
if (self::$_autoloadRootPath) {
|
||||
$class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php';
|
||||
}
|
||||
if (empty($class_file) || !is_file($class_file)) {
|
||||
$class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php";
|
||||
}
|
||||
}
|
||||
|
||||
if (is_file($class_file)) {
|
||||
require_once($class_file);
|
||||
if (class_exists($name, false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
spl_autoload_register('\Workerman\Autoloader::loadByNamespace');
|
||||
@ -0,0 +1,372 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Connection;
|
||||
|
||||
use Workerman\Events\EventInterface;
|
||||
use Workerman\Lib\Timer;
|
||||
use Workerman\Worker;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* AsyncTcpConnection.
|
||||
*/
|
||||
class AsyncTcpConnection extends TcpConnection
|
||||
{
|
||||
/**
|
||||
* Emitted when socket connection is successfully established.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
public $onConnect = null;
|
||||
|
||||
/**
|
||||
* Transport layer protocol.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $transport = 'tcp';
|
||||
|
||||
/**
|
||||
* Status.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_status = self::STATUS_INITIAL;
|
||||
|
||||
/**
|
||||
* Remote host.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_remoteHost = '';
|
||||
|
||||
/**
|
||||
* Remote port.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_remotePort = 80;
|
||||
|
||||
/**
|
||||
* Connect start time.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_connectStartTime = 0;
|
||||
|
||||
/**
|
||||
* Remote URI.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_remoteURI = '';
|
||||
|
||||
/**
|
||||
* Context option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_contextOption = null;
|
||||
|
||||
/**
|
||||
* Reconnect timer.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_reconnectTimer = null;
|
||||
|
||||
|
||||
/**
|
||||
* PHP built-in protocols.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_builtinTransports = array(
|
||||
'tcp' => 'tcp',
|
||||
'udp' => 'udp',
|
||||
'unix' => 'unix',
|
||||
'ssl' => 'ssl',
|
||||
'sslv2' => 'sslv2',
|
||||
'sslv3' => 'sslv3',
|
||||
'tls' => 'tls'
|
||||
);
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param string $remote_address
|
||||
* @param array $context_option
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($remote_address, $context_option = null)
|
||||
{
|
||||
$address_info = parse_url($remote_address);
|
||||
if (!$address_info) {
|
||||
list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2);
|
||||
if (!$this->_remoteAddress) {
|
||||
Worker::safeEcho(new \Exception('bad remote_address'));
|
||||
}
|
||||
} else {
|
||||
if (!isset($address_info['port'])) {
|
||||
$address_info['port'] = 80;
|
||||
}
|
||||
if (!isset($address_info['path'])) {
|
||||
$address_info['path'] = '/';
|
||||
}
|
||||
if (!isset($address_info['query'])) {
|
||||
$address_info['query'] = '';
|
||||
} else {
|
||||
$address_info['query'] = '?' . $address_info['query'];
|
||||
}
|
||||
$this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}";
|
||||
$this->_remoteHost = $address_info['host'];
|
||||
$this->_remotePort = $address_info['port'];
|
||||
$this->_remoteURI = "{$address_info['path']}{$address_info['query']}";
|
||||
$scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp';
|
||||
}
|
||||
|
||||
$this->id = $this->_id = self::$_idRecorder++;
|
||||
if(PHP_INT_MAX === self::$_idRecorder){
|
||||
self::$_idRecorder = 0;
|
||||
}
|
||||
// Check application layer protocol class.
|
||||
if (!isset(self::$_builtinTransports[$scheme])) {
|
||||
$scheme = ucfirst($scheme);
|
||||
$this->protocol = '\\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");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->transport = self::$_builtinTransports[$scheme];
|
||||
}
|
||||
|
||||
// For statistics.
|
||||
self::$statistics['connection_count']++;
|
||||
$this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
|
||||
$this->_contextOption = $context_option;
|
||||
static::$connections[$this->_id] = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do connect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING &&
|
||||
$this->_status !== self::STATUS_CLOSED) {
|
||||
return;
|
||||
}
|
||||
$this->_status = self::STATUS_CONNECTING;
|
||||
$this->_connectStartTime = microtime(true);
|
||||
if ($this->transport !== 'unix') {
|
||||
// Open socket connection asynchronously.
|
||||
if ($this->_contextOption) {
|
||||
$context = stream_context_create($this->_contextOption);
|
||||
$this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
|
||||
$errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT, $context);
|
||||
} else {
|
||||
$this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
|
||||
$errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT);
|
||||
}
|
||||
} else {
|
||||
$this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0,
|
||||
STREAM_CLIENT_ASYNC_CONNECT);
|
||||
}
|
||||
// If failed attempt to emit onError callback.
|
||||
if (!$this->_socket || !is_resource($this->_socket)) {
|
||||
$this->emitError(WORKERMAN_CONNECT_FAIL, $errstr);
|
||||
if ($this->_status === self::STATUS_CLOSING) {
|
||||
$this->destroy();
|
||||
}
|
||||
if ($this->_status === self::STATUS_CLOSED) {
|
||||
$this->onConnect = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Add socket to global event loop waiting connection is successfully established or faild.
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection'));
|
||||
// For windows.
|
||||
if(DIRECTORY_SEPARATOR === '\\') {
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect.
|
||||
*
|
||||
* @param int $after
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect($after = 0)
|
||||
{
|
||||
$this->_status = self::STATUS_INITIAL;
|
||||
static::$connections[$this->_id] = $this;
|
||||
if ($this->_reconnectTimer) {
|
||||
Timer::del($this->_reconnectTimer);
|
||||
}
|
||||
if ($after > 0) {
|
||||
$this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false);
|
||||
return;
|
||||
}
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* CancelReconnect.
|
||||
*/
|
||||
public function cancelReconnect()
|
||||
{
|
||||
if ($this->_reconnectTimer) {
|
||||
Timer::del($this->_reconnectTimer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteHost()
|
||||
{
|
||||
return $this->_remoteHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteURI()
|
||||
{
|
||||
return $this->_remoteURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to emit onError callback.
|
||||
*
|
||||
* @param int $code
|
||||
* @param string $msg
|
||||
* @return void
|
||||
*/
|
||||
protected function emitError($code, $msg)
|
||||
{
|
||||
$this->_status = self::STATUS_CLOSING;
|
||||
if ($this->onError) {
|
||||
try {
|
||||
call_user_func($this->onError, $this, $code, $msg);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check connection is successfully established or faild.
|
||||
*
|
||||
* @param resource $socket
|
||||
* @return void
|
||||
*/
|
||||
public function checkConnection()
|
||||
{
|
||||
// Remove EV_EXPECT for windows.
|
||||
if(DIRECTORY_SEPARATOR === '\\') {
|
||||
Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT);
|
||||
}
|
||||
|
||||
// Remove write listener.
|
||||
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
|
||||
|
||||
if ($this->_status != self::STATUS_CONNECTING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check socket state.
|
||||
if ($address = stream_socket_get_name($this->_socket, true)) {
|
||||
// Nonblocking.
|
||||
stream_set_blocking($this->_socket, 0);
|
||||
// Compatible with hhvm
|
||||
if (function_exists('stream_set_read_buffer')) {
|
||||
stream_set_read_buffer($this->_socket, 0);
|
||||
}
|
||||
// Try to open keepalive for tcp and disable Nagle algorithm.
|
||||
if (function_exists('socket_import_stream') && $this->transport === 'tcp') {
|
||||
$raw_socket = socket_import_stream($this->_socket);
|
||||
socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1);
|
||||
socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1);
|
||||
}
|
||||
|
||||
// SSL handshake.
|
||||
if ($this->transport === 'ssl') {
|
||||
$this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket);
|
||||
if ($this->_sslHandshakeCompleted === false) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// There are some data waiting to send.
|
||||
if ($this->_sendBuffer) {
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
|
||||
}
|
||||
}
|
||||
|
||||
// Register a listener waiting read event.
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
|
||||
|
||||
$this->_status = self::STATUS_ESTABLISHED;
|
||||
$this->_remoteAddress = $address;
|
||||
|
||||
// Try to emit onConnect callback.
|
||||
if ($this->onConnect) {
|
||||
try {
|
||||
call_user_func($this->onConnect, $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
// Try to emit protocol::onConnect
|
||||
if (method_exists($this->protocol, 'onConnect')) {
|
||||
try {
|
||||
call_user_func(array($this->protocol, 'onConnect'), $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Connection failed.
|
||||
$this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds');
|
||||
if ($this->_status === self::STATUS_CLOSING) {
|
||||
$this->destroy();
|
||||
}
|
||||
if ($this->_status === self::STATUS_CLOSED) {
|
||||
$this->onConnect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Connection;
|
||||
|
||||
use Workerman\Events\EventInterface;
|
||||
use Workerman\Worker;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* AsyncTcpConnection.
|
||||
*/
|
||||
class AsyncUdpConnection extends UdpConnection
|
||||
{
|
||||
/**
|
||||
* Emitted when socket connection is successfully established.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
public $onConnect = null;
|
||||
|
||||
/**
|
||||
* Emitted when socket connection closed.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
public $onClose = null;
|
||||
|
||||
/**
|
||||
* Connected or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $connected = false;
|
||||
|
||||
/**
|
||||
* Context option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_contextOption = null;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param string $remote_address
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($remote_address, $context_option = null)
|
||||
{
|
||||
// Get the application layer communication protocol and listening address.
|
||||
list($scheme, $address) = explode(':', $remote_address, 2);
|
||||
// Check application layer protocol class.
|
||||
if ($scheme !== 'udp') {
|
||||
$scheme = ucfirst($scheme);
|
||||
$this->protocol = '\\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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_remoteAddress = substr($address, 2);
|
||||
$this->_contextOption = $context_option;
|
||||
}
|
||||
|
||||
/**
|
||||
* For udp package.
|
||||
*
|
||||
* @param resource $socket
|
||||
* @return bool
|
||||
*/
|
||||
public function baseRead($socket)
|
||||
{
|
||||
$recv_buffer = stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address);
|
||||
if (false === $recv_buffer || empty($remote_address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->onMessage) {
|
||||
if ($this->protocol) {
|
||||
$parser = $this->protocol;
|
||||
$recv_buffer = $parser::decode($recv_buffer, $this);
|
||||
}
|
||||
ConnectionInterface::$statistics['total_request']++;
|
||||
try {
|
||||
call_user_func($this->onMessage, $this, $recv_buffer);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data on the connection.
|
||||
*
|
||||
* @param string $send_buffer
|
||||
* @param bool $raw
|
||||
* @return void|boolean
|
||||
*/
|
||||
public function send($send_buffer, $raw = false)
|
||||
{
|
||||
if (false === $raw && $this->protocol) {
|
||||
$parser = $this->protocol;
|
||||
$send_buffer = $parser::encode($send_buffer, $this);
|
||||
if ($send_buffer === '') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if ($this->connected === false) {
|
||||
$this->connect();
|
||||
}
|
||||
return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close connection.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param bool $raw
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function close($data = null, $raw = false)
|
||||
{
|
||||
if ($data !== null) {
|
||||
$this->send($data, $raw);
|
||||
}
|
||||
Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ);
|
||||
fclose($this->_socket);
|
||||
$this->connected = false;
|
||||
// Try to emit onClose callback.
|
||||
if ($this->onClose) {
|
||||
try {
|
||||
call_user_func($this->onClose, $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
$this->onConnect = $this->onMessage = $this->onClose = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if ($this->connected === true) {
|
||||
return;
|
||||
}
|
||||
if ($this->_contextOption) {
|
||||
$context = stream_context_create($this->_contextOption);
|
||||
$this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg,
|
||||
30, STREAM_CLIENT_CONNECT, $context);
|
||||
} else {
|
||||
$this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg);
|
||||
}
|
||||
|
||||
if (!$this->_socket) {
|
||||
Worker::safeEcho(new \Exception($errmsg));
|
||||
return;
|
||||
}
|
||||
|
||||
stream_set_blocking($this->_socket, false);
|
||||
|
||||
if ($this->onMessage) {
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
|
||||
}
|
||||
$this->connected = true;
|
||||
// Try to emit onConnect callback.
|
||||
if ($this->onConnect) {
|
||||
try {
|
||||
call_user_func($this->onConnect, $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Connection;
|
||||
|
||||
/**
|
||||
* ConnectionInterface.
|
||||
*/
|
||||
abstract class ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Statistics for status command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $statistics = array(
|
||||
'connection_count' => 0,
|
||||
'total_request' => 0,
|
||||
'throw_exception' => 0,
|
||||
'send_fail' => 0,
|
||||
);
|
||||
|
||||
/**
|
||||
* Emitted when data is received.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
public $onMessage = null;
|
||||
|
||||
/**
|
||||
* Emitted when the other end of the socket sends a FIN packet.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
public $onClose = null;
|
||||
|
||||
/**
|
||||
* Emitted when an error occurs with connection.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
public $onError = null;
|
||||
|
||||
/**
|
||||
* Sends data on the connection.
|
||||
*
|
||||
* @param mixed $send_buffer
|
||||
* @return void|boolean
|
||||
*/
|
||||
abstract public function send($send_buffer);
|
||||
|
||||
/**
|
||||
* Get remote IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getRemoteIp();
|
||||
|
||||
/**
|
||||
* Get remote port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function getRemotePort();
|
||||
|
||||
/**
|
||||
* Get remote address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Get local IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getLocalIp();
|
||||
|
||||
/**
|
||||
* Get local port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function getLocalPort();
|
||||
|
||||
/**
|
||||
* Get local address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getLocalAddress();
|
||||
|
||||
/**
|
||||
* Is ipv4.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isIPv4();
|
||||
|
||||
/**
|
||||
* Is ipv6.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isIPv6();
|
||||
|
||||
/**
|
||||
* Close connection.
|
||||
*
|
||||
* @param $data
|
||||
* @return void
|
||||
*/
|
||||
abstract public function close($data = null);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Connection;
|
||||
|
||||
/**
|
||||
* UdpConnection.
|
||||
*/
|
||||
class UdpConnection extends ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Application layer protocol.
|
||||
* The format is like this Workerman\\Protocols\\Http.
|
||||
*
|
||||
* @var \Workerman\Protocols\ProtocolInterface
|
||||
*/
|
||||
public $protocol = null;
|
||||
|
||||
/**
|
||||
* Udp socket.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $_socket = null;
|
||||
|
||||
/**
|
||||
* Remote address.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_remoteAddress = '';
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param resource $socket
|
||||
* @param string $remote_address
|
||||
*/
|
||||
public function __construct($socket, $remote_address)
|
||||
{
|
||||
$this->_socket = $socket;
|
||||
$this->_remoteAddress = $remote_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data on the connection.
|
||||
*
|
||||
* @param string $send_buffer
|
||||
* @param bool $raw
|
||||
* @return void|boolean
|
||||
*/
|
||||
public function send($send_buffer, $raw = false)
|
||||
{
|
||||
if (false === $raw && $this->protocol) {
|
||||
$parser = $this->protocol;
|
||||
$send_buffer = $parser::encode($send_buffer, $this);
|
||||
if ($send_buffer === '') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteIp()
|
||||
{
|
||||
$pos = strrpos($this->_remoteAddress, ':');
|
||||
if ($pos) {
|
||||
return trim(substr($this->_remoteAddress, 0, $pos), '[]');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRemotePort()
|
||||
{
|
||||
if ($this->_remoteAddress) {
|
||||
return (int)substr(strrchr($this->_remoteAddress, ':'), 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteAddress()
|
||||
{
|
||||
return $this->_remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalIp()
|
||||
{
|
||||
$address = $this->getLocalAddress();
|
||||
$pos = strrpos($address, ':');
|
||||
if (!$pos) {
|
||||
return '';
|
||||
}
|
||||
return substr($address, 0, $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLocalPort()
|
||||
{
|
||||
$address = $this->getLocalAddress();
|
||||
$pos = strrpos($address, ':');
|
||||
if (!$pos) {
|
||||
return 0;
|
||||
}
|
||||
return (int)substr(strrchr($address, ':'), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalAddress()
|
||||
{
|
||||
return (string)@stream_socket_get_name($this->_socket, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is ipv4.
|
||||
*
|
||||
* return bool.
|
||||
*/
|
||||
public function isIpV4()
|
||||
{
|
||||
if ($this->transport === 'unix') {
|
||||
return false;
|
||||
}
|
||||
return strpos($this->getRemoteIp(), ':') === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is ipv6.
|
||||
*
|
||||
* return bool.
|
||||
*/
|
||||
public function isIpV6()
|
||||
{
|
||||
if ($this->transport === 'unix') {
|
||||
return false;
|
||||
}
|
||||
return strpos($this->getRemoteIp(), ':') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close connection.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param bool $raw
|
||||
* @return bool
|
||||
*/
|
||||
public function close($data = null, $raw = false)
|
||||
{
|
||||
if ($data !== null) {
|
||||
$this->send($data, $raw);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author 有个鬼<42765633@qq.com>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* ev eventloop
|
||||
*/
|
||||
class Ev implements EventInterface
|
||||
{
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [func, args, event, flag, time_interval]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* Timer id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $_timerId = 1;
|
||||
|
||||
/**
|
||||
* Add a timer.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = null)
|
||||
{
|
||||
$callback = function ($event, $socket) use ($fd, $func) {
|
||||
try {
|
||||
call_user_func($func, $fd);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
};
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
$event = new \EvSignal($fd, $callback);
|
||||
$this->_eventSignal[$fd] = $event;
|
||||
return true;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd;
|
||||
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
|
||||
$event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param);
|
||||
$this->_eventTimer[self::$_timerId] = $event;
|
||||
return self::$_timerId++;
|
||||
default :
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE;
|
||||
$event = new \EvIo($fd, $real_flag, $callback);
|
||||
$this->_allEvents[$fd_key][$flag] = $event;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a timer.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
$this->_allEvents[$fd_key][$flag]->stop();
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
$this->_eventSignal[$fd_key]->stop();
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
$this->_eventTimer[$fd]->stop();
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer callback.
|
||||
*
|
||||
* @param \EvWatcher $event
|
||||
*/
|
||||
public function timerCallback($event)
|
||||
{
|
||||
$param = $event->data;
|
||||
$timer_id = $param[4];
|
||||
if ($param[2] === self::EV_TIMER_ONCE) {
|
||||
$this->_eventTimer[$timer_id]->stop();
|
||||
unset($this->_eventTimer[$timer_id]);
|
||||
}
|
||||
try {
|
||||
call_user_func_array($param[0], $param[1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all timers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach ($this->_eventTimer as $event) {
|
||||
$event->stop();
|
||||
}
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @see EventInterface::loop()
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
\Ev::run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
foreach ($this->_allEvents as $event) {
|
||||
$event->stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author 有个鬼<42765633@qq.com>
|
||||
* @copyright 有个鬼<42765633@qq.com>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* libevent eventloop
|
||||
*/
|
||||
class Event implements EventInterface
|
||||
{
|
||||
/**
|
||||
* Event base.
|
||||
* @var object
|
||||
*/
|
||||
protected $_eventBase = null;
|
||||
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [func, args, event, flag, time_interval]
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* Timer id.
|
||||
* @var int
|
||||
*/
|
||||
protected static $_timerId = 1;
|
||||
|
||||
/**
|
||||
* construct
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (class_exists('\\\\EventBase', false)) {
|
||||
$class_name = '\\\\EventBase';
|
||||
} else {
|
||||
$class_name = '\EventBase';
|
||||
}
|
||||
$this->_eventBase = new $class_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EventInterface::add()
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args=array())
|
||||
{
|
||||
if (class_exists('\\\\Event', false)) {
|
||||
$class_name = '\\\\Event';
|
||||
} else {
|
||||
$class_name = '\Event';
|
||||
}
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
|
||||
$fd_key = (int)$fd;
|
||||
$event = $class_name::signal($this->_eventBase, $fd, $func);
|
||||
if (!$event||!$event->add()) {
|
||||
return false;
|
||||
}
|
||||
$this->_eventSignal[$fd_key] = $event;
|
||||
return true;
|
||||
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
|
||||
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
|
||||
$event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param);
|
||||
if (!$event||!$event->addTimer($fd)) {
|
||||
return false;
|
||||
}
|
||||
$this->_eventTimer[self::$_timerId] = $event;
|
||||
return self::$_timerId++;
|
||||
|
||||
default :
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST;
|
||||
$event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd);
|
||||
if (!$event||!$event->add()) {
|
||||
return false;
|
||||
}
|
||||
$this->_allEvents[$fd_key][$flag] = $event;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Events\EventInterface::del()
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
$this->_allEvents[$fd_key][$flag]->del();
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
$this->_eventSignal[$fd_key]->del();
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
$this->_eventTimer[$fd]->del();
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer callback.
|
||||
* @param null $fd
|
||||
* @param int $what
|
||||
* @param int $timer_id
|
||||
*/
|
||||
public function timerCallback($fd, $what, $param)
|
||||
{
|
||||
$timer_id = $param[4];
|
||||
|
||||
if ($param[2] === self::EV_TIMER_ONCE) {
|
||||
$this->_eventTimer[$timer_id]->del();
|
||||
unset($this->_eventTimer[$timer_id]);
|
||||
}
|
||||
|
||||
try {
|
||||
call_user_func_array($param[0], $param[1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Events\EventInterface::clearAllTimer()
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach ($this->_eventTimer as $event) {
|
||||
$event->del();
|
||||
}
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see EventInterface::loop()
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
$this->_eventBase->loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
foreach ($this->_eventSignal as $event) {
|
||||
$event->del();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
interface EventInterface
|
||||
{
|
||||
/**
|
||||
* Read event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_READ = 1;
|
||||
|
||||
/**
|
||||
* Write event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_WRITE = 2;
|
||||
|
||||
/**
|
||||
* Except event
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_EXCEPT = 3;
|
||||
|
||||
/**
|
||||
* Signal event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_SIGNAL = 4;
|
||||
|
||||
/**
|
||||
* Timer event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_TIMER = 8;
|
||||
|
||||
/**
|
||||
* Timer once event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_TIMER_ONCE = 16;
|
||||
|
||||
/**
|
||||
* Add event listener to event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @param callable $func
|
||||
* @param mixed $args
|
||||
* @return bool
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = null);
|
||||
|
||||
/**
|
||||
* Remove event listener from event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @return bool
|
||||
*/
|
||||
public function del($fd, $flag);
|
||||
|
||||
/**
|
||||
* Remove all timers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer();
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loop();
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function destroy();
|
||||
|
||||
/**
|
||||
* Get Timer count.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTimerCount();
|
||||
}
|
||||
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* libevent eventloop
|
||||
*/
|
||||
class Libevent implements EventInterface
|
||||
{
|
||||
/**
|
||||
* Event base.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $_eventBase = null;
|
||||
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [func, args, event, flag, time_interval]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* construct
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventBase = event_base_new();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array())
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = EV_SIGNAL | EV_PERSIST;
|
||||
$this->_eventSignal[$fd_key] = event_new();
|
||||
if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) {
|
||||
return false;
|
||||
}
|
||||
if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
if (!event_add($this->_eventSignal[$fd_key])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$event = event_new();
|
||||
$timer_id = (int)$event;
|
||||
if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!event_base_set($event, $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time_interval = $fd * 1000000;
|
||||
if (!event_add($event, $time_interval)) {
|
||||
return false;
|
||||
}
|
||||
$this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval);
|
||||
return $timer_id;
|
||||
|
||||
default :
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST;
|
||||
|
||||
$event = event_new();
|
||||
|
||||
if (!event_set($event, $fd, $real_flag, $func, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!event_base_set($event, $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!event_add($event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_allEvents[$fd_key][$flag] = $event;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
event_del($this->_allEvents[$fd_key][$flag]);
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
event_del($this->_eventSignal[$fd_key]);
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
// 这里 fd 为timerid
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
event_del($this->_eventTimer[$fd][2]);
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer callback.
|
||||
*
|
||||
* @param mixed $_null1
|
||||
* @param int $_null2
|
||||
* @param mixed $timer_id
|
||||
*/
|
||||
protected function timerCallback($_null1, $_null2, $timer_id)
|
||||
{
|
||||
if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) {
|
||||
event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]);
|
||||
}
|
||||
try {
|
||||
call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) {
|
||||
$this->del($timer_id, self::EV_TIMER_ONCE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach ($this->_eventTimer as $task_data) {
|
||||
event_del($task_data[2]);
|
||||
}
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
event_base_loop($this->_eventBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
foreach ($this->_eventSignal as $event) {
|
||||
event_del($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,262 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events\React;
|
||||
use Workerman\Events\EventInterface;
|
||||
use React\EventLoop\TimerInterface;
|
||||
|
||||
/**
|
||||
* Class StreamSelectLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class Base implements \React\EventLoop\LoopInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_timerIdMap = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $_timerIdIndex = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_signalHandlerMap = array();
|
||||
|
||||
/**
|
||||
* @var \React\EventLoop\LoopInterface
|
||||
*/
|
||||
protected $_eventLoop = null;
|
||||
|
||||
/**
|
||||
* Base constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\StreamSelectLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listener to event loop.
|
||||
*
|
||||
* @param $fd
|
||||
* @param $flag
|
||||
* @param $func
|
||||
* @param array $args
|
||||
* @return bool
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array())
|
||||
{
|
||||
$args = (array)$args;
|
||||
switch ($flag) {
|
||||
case EventInterface::EV_READ:
|
||||
return $this->addReadStream($fd, $func);
|
||||
case EventInterface::EV_WRITE:
|
||||
return $this->addWriteStream($fd, $func);
|
||||
case EventInterface::EV_SIGNAL:
|
||||
if (isset($this->_signalHandlerMap[$fd])) {
|
||||
$this->removeSignal($fd, $this->_signalHandlerMap[$fd]);
|
||||
}
|
||||
$this->_signalHandlerMap[$fd] = $func;
|
||||
return $this->addSignal($fd, $func);
|
||||
case EventInterface::EV_TIMER:
|
||||
$timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) {
|
||||
call_user_func_array($func, $args);
|
||||
});
|
||||
$this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj;
|
||||
return $this->_timerIdIndex;
|
||||
case EventInterface::EV_TIMER_ONCE:
|
||||
$index = ++$this->_timerIdIndex;
|
||||
$timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) {
|
||||
$this->del($index,EventInterface::EV_TIMER_ONCE);
|
||||
call_user_func_array($func, $args);
|
||||
});
|
||||
$this->_timerIdMap[$index] = $timer_obj;
|
||||
return $this->_timerIdIndex;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove event listener from event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @return bool
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case EventInterface::EV_READ:
|
||||
return $this->removeReadStream($fd);
|
||||
case EventInterface::EV_WRITE:
|
||||
return $this->removeWriteStream($fd);
|
||||
case EventInterface::EV_SIGNAL:
|
||||
if (!isset($this->_eventLoop[$fd])) {
|
||||
return false;
|
||||
}
|
||||
$func = $this->_eventLoop[$fd];
|
||||
unset($this->_eventLoop[$fd]);
|
||||
return $this->removeSignal($fd, $func);
|
||||
|
||||
case EventInterface::EV_TIMER:
|
||||
case EventInterface::EV_TIMER_ONCE:
|
||||
if (isset($this->_timerIdMap[$fd])){
|
||||
$timer_obj = $this->_timerIdMap[$fd];
|
||||
unset($this->_timerIdMap[$fd]);
|
||||
$this->cancelTimer($timer_obj);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
$this->run();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return count($this->_timerIdMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
return $this->_eventLoop->addReadStream($stream, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
return $this->_eventLoop->addWriteStream($stream, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
*/
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
return $this->_eventLoop->removeReadStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
*/
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
return $this->_eventLoop->removeWriteStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|int $interval
|
||||
* @param callable $callback
|
||||
* @return \React\EventLoop\Timer\Timer|TimerInterface
|
||||
*/
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
return $this->_eventLoop->addTimer($interval, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|int $interval
|
||||
* @param callable $callback
|
||||
* @return \React\EventLoop\Timer\Timer|TimerInterface
|
||||
*/
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
return $this->_eventLoop->addPeriodicTimer($interval, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimerInterface $timer
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
return $this->_eventLoop->cancelTimer($timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function futureTick($listener)
|
||||
{
|
||||
return $this->_eventLoop->futureTick($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
return $this->_eventLoop->addSignal($signal, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
return $this->_eventLoop->removeSignal($signal, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run.
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
return $this->_eventLoop->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop.
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
return $this->_eventLoop->stop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events\React;
|
||||
|
||||
/**
|
||||
* Class ExtEventLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class ExtEventLoop extends Base
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\ExtEventLoop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events\React;
|
||||
use Workerman\Events\EventInterface;
|
||||
|
||||
/**
|
||||
* Class ExtLibEventLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class ExtLibEventLoop extends Base
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\ExtLibeventLoop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events\React;
|
||||
|
||||
/**
|
||||
* Class StreamSelectLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class StreamSelectLoop extends Base
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\StreamSelectLoop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
/**
|
||||
* select eventloop
|
||||
*/
|
||||
class Select implements EventInterface
|
||||
{
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $_signalEvents = array();
|
||||
|
||||
/**
|
||||
* Fds waiting for read event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_readFds = array();
|
||||
|
||||
/**
|
||||
* Fds waiting for write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_writeFds = array();
|
||||
|
||||
/**
|
||||
* Fds waiting for except event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_exceptFds = array();
|
||||
|
||||
/**
|
||||
* Timer scheduler.
|
||||
* {['data':timer_id, 'priority':run_timestamp], ..}
|
||||
*
|
||||
* @var \SplPriorityQueue
|
||||
*/
|
||||
protected $_scheduler = null;
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [[func, args, flag, timer_interval], ..]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* Timer id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_timerId = 1;
|
||||
|
||||
/**
|
||||
* Select timeout.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_selectTimeout = 100000000;
|
||||
|
||||
/**
|
||||
* Paired socket channels
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $channel = array();
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling.
|
||||
$this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET,
|
||||
STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
|
||||
if($this->channel) {
|
||||
stream_set_blocking($this->channel[0], 0);
|
||||
$this->_readFds[0] = $this->channel[0];
|
||||
}
|
||||
// Init SplPriorityQueue.
|
||||
$this->_scheduler = new \SplPriorityQueue();
|
||||
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array())
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$count = $flag === self::EV_READ ? count($this->_readFds) : count($this->_writeFds);
|
||||
if ($count >= 1024) {
|
||||
echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n";
|
||||
} else if (DIRECTORY_SEPARATOR !== '/' && $count >= 256) {
|
||||
echo "Warning: system call select exceeded the maximum number of connections 256.\n";
|
||||
}
|
||||
$fd_key = (int)$fd;
|
||||
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
|
||||
if ($flag === self::EV_READ) {
|
||||
$this->_readFds[$fd_key] = $fd;
|
||||
} else {
|
||||
$this->_writeFds[$fd_key] = $fd;
|
||||
}
|
||||
break;
|
||||
case self::EV_EXCEPT:
|
||||
$fd_key = (int)$fd;
|
||||
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
|
||||
$this->_exceptFds[$fd_key] = $fd;
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
// Windows not support signal.
|
||||
if(DIRECTORY_SEPARATOR !== '/') {
|
||||
return false;
|
||||
}
|
||||
$fd_key = (int)$fd;
|
||||
$this->_signalEvents[$fd_key][$flag] = array($func, $fd);
|
||||
pcntl_signal($fd, array($this, 'signalHandler'));
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$timer_id = $this->_timerId++;
|
||||
$run_time = microtime(true) + $fd;
|
||||
$this->_scheduler->insert($timer_id, -$run_time);
|
||||
$this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd);
|
||||
$select_timeout = ($run_time - microtime(true)) * 1000000;
|
||||
if( $this->_selectTimeout > $select_timeout ){
|
||||
$this->_selectTimeout = $select_timeout;
|
||||
}
|
||||
return $timer_id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal handler.
|
||||
*
|
||||
* @param int $signal
|
||||
*/
|
||||
public function signalHandler($signal)
|
||||
{
|
||||
call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
$fd_key = (int)$fd;
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]);
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
return true;
|
||||
case self::EV_WRITE:
|
||||
unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]);
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
return true;
|
||||
case self::EV_EXCEPT:
|
||||
unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]);
|
||||
if(empty($this->_allEvents[$fd_key]))
|
||||
{
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
return true;
|
||||
case self::EV_SIGNAL:
|
||||
if(DIRECTORY_SEPARATOR !== '/') {
|
||||
return false;
|
||||
}
|
||||
unset($this->_signalEvents[$fd_key]);
|
||||
pcntl_signal($fd, SIG_IGN);
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE;
|
||||
unset($this->_eventTimer[$fd_key]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick for timer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function tick()
|
||||
{
|
||||
while (!$this->_scheduler->isEmpty()) {
|
||||
$scheduler_data = $this->_scheduler->top();
|
||||
$timer_id = $scheduler_data['data'];
|
||||
$next_run_time = -$scheduler_data['priority'];
|
||||
$time_now = microtime(true);
|
||||
$this->_selectTimeout = ($next_run_time - $time_now) * 1000000;
|
||||
if ($this->_selectTimeout <= 0) {
|
||||
$this->_scheduler->extract();
|
||||
|
||||
if (!isset($this->_eventTimer[$timer_id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// [func, args, flag, timer_interval]
|
||||
$task_data = $this->_eventTimer[$timer_id];
|
||||
if ($task_data[2] === self::EV_TIMER) {
|
||||
$next_run_time = $time_now + $task_data[3];
|
||||
$this->_scheduler->insert($timer_id, -$next_run_time);
|
||||
}
|
||||
call_user_func_array($task_data[0], $task_data[1]);
|
||||
if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
|
||||
$this->del($timer_id, self::EV_TIMER_ONCE);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->_selectTimeout = 100000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
$this->_scheduler = new \SplPriorityQueue();
|
||||
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
while (1) {
|
||||
if(DIRECTORY_SEPARATOR === '/') {
|
||||
// Calls signal handlers for pending signals
|
||||
pcntl_signal_dispatch();
|
||||
}
|
||||
|
||||
$read = $this->_readFds;
|
||||
$write = $this->_writeFds;
|
||||
$except = $this->_exceptFds;
|
||||
|
||||
// Waiting read/write/signal/timeout events.
|
||||
set_error_handler(function(){});
|
||||
$ret = stream_select($read, $write, $except, 0, $this->_selectTimeout);
|
||||
restore_error_handler();
|
||||
|
||||
|
||||
if (!$this->_scheduler->isEmpty()) {
|
||||
$this->tick();
|
||||
}
|
||||
|
||||
if (!$ret) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($read) {
|
||||
foreach ($read as $fd) {
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][self::EV_READ])) {
|
||||
call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0],
|
||||
array($this->_allEvents[$fd_key][self::EV_READ][1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($write) {
|
||||
foreach ($write as $fd) {
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) {
|
||||
call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0],
|
||||
array($this->_allEvents[$fd_key][self::EV_WRITE][1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($except) {
|
||||
foreach($except as $fd) {
|
||||
$fd_key = (int) $fd;
|
||||
if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) {
|
||||
call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0],
|
||||
array($this->_allEvents[$fd_key][self::EV_EXCEPT][1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,221 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author Ares<aresrr#qq.com>
|
||||
* @link http://www.workerman.net/
|
||||
* @link https://github.com/ares333/Workerman
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Swoole\Event;
|
||||
use Swoole\Timer;
|
||||
|
||||
class Swoole implements EventInterface
|
||||
{
|
||||
|
||||
protected $_timer = array();
|
||||
|
||||
protected $_timerOnceMap = array();
|
||||
|
||||
protected $mapId = 0;
|
||||
|
||||
protected $_fd = array();
|
||||
|
||||
// milisecond
|
||||
public static $signalDispatchInterval = 200;
|
||||
|
||||
protected $_hasSignal = false;
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::add()
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = null)
|
||||
{
|
||||
if (! isset($args)) {
|
||||
$args = array();
|
||||
}
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
$res = pcntl_signal($fd, $func, false);
|
||||
if (! $this->_hasSignal && $res) {
|
||||
Timer::tick(static::$signalDispatchInterval,
|
||||
function () {
|
||||
pcntl_signal_dispatch();
|
||||
});
|
||||
$this->_hasSignal = true;
|
||||
}
|
||||
return $res;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$method = self::EV_TIMER == $flag ? 'tick' : 'after';
|
||||
if ($this->mapId > PHP_INT_MAX) {
|
||||
$this->mapId = 0;
|
||||
}
|
||||
$mapId = $this->mapId++;
|
||||
$timer_id = Timer::$method($fd * 1000,
|
||||
function ($timer_id = null) use ($func, $args, $mapId) {
|
||||
call_user_func_array($func, $args);
|
||||
// EV_TIMER_ONCE
|
||||
if (! isset($timer_id)) {
|
||||
// may be deleted in $func
|
||||
if (array_key_exists($mapId, $this->_timerOnceMap)) {
|
||||
$timer_id = $this->_timerOnceMap[$mapId];
|
||||
unset($this->_timer[$timer_id],
|
||||
$this->_timerOnceMap[$mapId]);
|
||||
}
|
||||
}
|
||||
});
|
||||
if ($flag == self::EV_TIMER_ONCE) {
|
||||
$this->_timerOnceMap[$mapId] = $timer_id;
|
||||
$this->_timer[$timer_id] = $mapId;
|
||||
} else {
|
||||
$this->_timer[$timer_id] = null;
|
||||
}
|
||||
return $timer_id;
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int) $fd;
|
||||
if (! isset($this->_fd[$fd_key])) {
|
||||
if ($flag == self::EV_READ) {
|
||||
$res = Event::add($fd, $func, null, SWOOLE_EVENT_READ);
|
||||
$fd_type = SWOOLE_EVENT_READ;
|
||||
} else {
|
||||
$res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE);
|
||||
$fd_type = SWOOLE_EVENT_WRITE;
|
||||
}
|
||||
if ($res) {
|
||||
$this->_fd[$fd_key] = $fd_type;
|
||||
}
|
||||
} else {
|
||||
$fd_val = $this->_fd[$fd_key];
|
||||
$res = true;
|
||||
if ($flag == self::EV_READ) {
|
||||
if (($fd_val & SWOOLE_EVENT_READ) != SWOOLE_EVENT_READ) {
|
||||
$res = Event::set($fd, $func, null,
|
||||
SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE);
|
||||
$this->_fd[$fd_key] |= SWOOLE_EVENT_READ;
|
||||
}
|
||||
} else {
|
||||
if (($fd_val & SWOOLE_EVENT_WRITE) != SWOOLE_EVENT_WRITE) {
|
||||
$res = Event::set($fd, null, $func,
|
||||
SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE);
|
||||
$this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::del()
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
return pcntl_signal($fd, SIG_IGN, false);
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
// already remove in EV_TIMER_ONCE callback.
|
||||
if (! array_key_exists($fd, $this->_timer)) {
|
||||
return true;
|
||||
}
|
||||
$res = Timer::clear($fd);
|
||||
if ($res) {
|
||||
$mapId = $this->_timer[$fd];
|
||||
if (isset($mapId)) {
|
||||
unset($this->_timerOnceMap[$mapId]);
|
||||
}
|
||||
unset($this->_timer[$fd]);
|
||||
}
|
||||
return $res;
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int) $fd;
|
||||
if (isset($this->_fd[$fd_key])) {
|
||||
$fd_val = $this->_fd[$fd_key];
|
||||
if ($flag == self::EV_READ) {
|
||||
$flag_remove = ~ SWOOLE_EVENT_READ;
|
||||
} else {
|
||||
$flag_remove = ~ SWOOLE_EVENT_WRITE;
|
||||
}
|
||||
$fd_val &= $flag_remove;
|
||||
if (0 === $fd_val) {
|
||||
$res = Event::del($fd);
|
||||
if ($res) {
|
||||
unset($this->_fd[$fd_key]);
|
||||
}
|
||||
} else {
|
||||
$res = Event::set($fd, null, null, $fd_val);
|
||||
if ($res) {
|
||||
$this->_fd[$fd_key] = $fd_val;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$res = true;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::clearAllTimer()
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach (array_keys($this->_timer) as $v) {
|
||||
Timer::clear($v);
|
||||
}
|
||||
$this->_timer = array();
|
||||
$this->_timerOnceMap = array();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::loop()
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
Event::wait();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::destroy()
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
//Event::exit();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::getTimerCount()
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return count($this->_timer);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
// Display errors.
|
||||
ini_set('display_errors', 'on');
|
||||
// Reporting all.
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// Reset opcache.
|
||||
if (function_exists('opcache_reset')) {
|
||||
opcache_reset();
|
||||
}
|
||||
|
||||
// For onError callback.
|
||||
define('WORKERMAN_CONNECT_FAIL', 1);
|
||||
// For onError callback.
|
||||
define('WORKERMAN_SEND_FAIL', 2);
|
||||
|
||||
// Define OS Type
|
||||
define('OS_TYPE_LINUX', 'linux');
|
||||
define('OS_TYPE_WINDOWS', 'windows');
|
||||
|
||||
// Compatible with php7
|
||||
if(!class_exists('Error'))
|
||||
{
|
||||
class Error extends Exception
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Lib;
|
||||
class Db
|
||||
{
|
||||
|
||||
public $db;
|
||||
private static $instance = array();
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$dsn = "mysql:dbname=" . $data['dbname'] . ";host=" . $data['dbhost'];
|
||||
$this->db = new \PDO($dsn, $data['dbuser'], $data['dbpassword']);
|
||||
$this->db->query('set character set utf8mb4;');
|
||||
$this->db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
|
||||
}
|
||||
|
||||
public static function get($key = ''): Db
|
||||
{
|
||||
$database['dbname'] = Tool::ini($key . 'DB_DATABASE');
|
||||
$database['dbuser'] = Tool::ini($key . 'DB_USERNAME');
|
||||
$database['dbpassword'] = Tool::ini($key . 'DB_PASSWORD');
|
||||
$database['dbhost'] = Tool::ini($key . 'DB_HOST');
|
||||
if (!isset(self::$instance[$database['dbname']]) || !self::$instance[$database['dbname']] instanceof self) {
|
||||
self::$instance[$database['dbname']] = new Db($database);
|
||||
} else {
|
||||
try {
|
||||
//断线重连
|
||||
self::$instance[$database['dbname']]->db->getAttribute(\PDO::ATTR_SERVER_INFO);
|
||||
} catch (\PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
|
||||
self::$instance[$database['dbname']] = new Db($database);
|
||||
}
|
||||
}
|
||||
}
|
||||
return self::$instance[$database['dbname']];
|
||||
}
|
||||
|
||||
|
||||
/*********PDO**********/
|
||||
public function count($sql, $parameters = null): int
|
||||
{
|
||||
return $this->exeupdate($sql, $parameters);
|
||||
}
|
||||
|
||||
public function querysql($sql, $parameters = null)
|
||||
{
|
||||
return $this->exeupdate($sql, $parameters);
|
||||
}
|
||||
|
||||
|
||||
public function querysqlinsertid($sql, $parameters = null)
|
||||
{
|
||||
if ($this->exeupdate($sql, $parameters)) {
|
||||
return $this->db->lastInsertId();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getRow($sql, $parameters = null)
|
||||
{
|
||||
$res = $this->exequery($sql, $parameters);
|
||||
if (count($res) > 0) {
|
||||
|
||||
return $res[0];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
public function getAll($sql, $parameters = null)
|
||||
{
|
||||
$res = $this->exequery($sql, $parameters);
|
||||
if (count($res) > 0) {
|
||||
return $res;
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
public function beginTransaction()
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
}
|
||||
|
||||
public function rollback()
|
||||
{
|
||||
$this->db->rollback();
|
||||
}
|
||||
|
||||
public function commit()
|
||||
{
|
||||
$this->db->commit();
|
||||
}
|
||||
|
||||
public function exequery($sql, $parameters = null)
|
||||
{
|
||||
$conn = $this->db;
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->execute($parameters);
|
||||
$rs = $stmt->fetchall(\PDO::FETCH_ASSOC);
|
||||
$stmt = null;
|
||||
$conn = null;
|
||||
return $rs;
|
||||
}
|
||||
|
||||
public function exeupdate($sql, $parameters = null)
|
||||
{
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($parameters);
|
||||
$affectedrows = $stmt->rowcount();
|
||||
$stmt = null;
|
||||
$conn = null;
|
||||
return $affectedrows;
|
||||
}
|
||||
|
||||
public function checklink()
|
||||
{
|
||||
$res = $this->db->getAttribute(\PDO::ATTR_SERVER_INFO);
|
||||
if (strpos($res, 'server has gone away') !== false) {
|
||||
$this->db = null;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function getinsertid()
|
||||
{
|
||||
return $this->db->lastInsertId();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
return $this->db = null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Lib;
|
||||
class Db2
|
||||
{
|
||||
public static function i($db, $table, $array)
|
||||
{
|
||||
$insertArr = array();
|
||||
$sql = "insert into `{$table}`(";
|
||||
$sql1 = 'values(';
|
||||
foreach ($array as $key => $value) {
|
||||
$sql .= "`{$key}`,";
|
||||
$sql1 .= "?,";
|
||||
$insertArr[] = $value;
|
||||
}
|
||||
$sql = trim($sql, ',');
|
||||
$sql1 = trim($sql1, ',');
|
||||
$sql .= ")" . $sql1 . ")";
|
||||
$db->querysql($sql, $insertArr);
|
||||
return $db->getinsertid();
|
||||
}
|
||||
|
||||
public static function u($db, $table, $array, $where, $where_a = [])
|
||||
{
|
||||
$updateArr = array();
|
||||
$sql = "update `{$table}` set ";
|
||||
foreach ($array as $key => $value) {
|
||||
$sql .= "`{$key}`=?,";
|
||||
$updateArr[] = $value;
|
||||
}
|
||||
$sql = trim($sql, ',');
|
||||
$where = ' ' . $where;
|
||||
$sql .= $where;
|
||||
$updateArr = array_merge($updateArr, $where_a);
|
||||
return $db->querysql($sql, $updateArr);
|
||||
}
|
||||
|
||||
public static function d($db, $table, $where, $where_a = [])
|
||||
{
|
||||
$sql = "delete from `{$table}` " . $where;
|
||||
$db->querysql($sql, $where_a);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Lib;
|
||||
|
||||
use Workerman\Events\EventInterface;
|
||||
use Workerman\Worker;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Timer.
|
||||
*
|
||||
* example:
|
||||
* Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..));
|
||||
*/
|
||||
class Timer
|
||||
{
|
||||
/**
|
||||
* Tasks that based on ALARM signal.
|
||||
* [
|
||||
* run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
|
||||
* run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
|
||||
* ..
|
||||
* ]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_tasks = array();
|
||||
|
||||
/**
|
||||
* event
|
||||
*
|
||||
* @var \Workerman\Events\EventInterface
|
||||
*/
|
||||
protected static $_event = null;
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @param \Workerman\Events\EventInterface $event
|
||||
* @return void
|
||||
*/
|
||||
public static function init($event = null)
|
||||
{
|
||||
if ($event) {
|
||||
self::$_event = $event;
|
||||
} else {
|
||||
if (function_exists('pcntl_signal')) {
|
||||
pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ALARM signal handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function signalHandle()
|
||||
{
|
||||
if (!self::$_event) {
|
||||
pcntl_alarm(1);
|
||||
self::tick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a timer.
|
||||
*
|
||||
* @param float $time_interval
|
||||
* @param callable $func
|
||||
* @param mixed $args
|
||||
* @param bool $persistent
|
||||
* @return int/false
|
||||
*/
|
||||
public static function add($time_interval, $func, $args = array(), $persistent = true)
|
||||
{
|
||||
if ($time_interval <= 0) {
|
||||
Worker::safeEcho(new Exception("bad time_interval"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self::$_event) {
|
||||
return self::$_event->add($time_interval,
|
||||
$persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args);
|
||||
}
|
||||
|
||||
if (!is_callable($func)) {
|
||||
Worker::safeEcho(new Exception("not callable"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty(self::$_tasks)) {
|
||||
pcntl_alarm(1);
|
||||
}
|
||||
|
||||
$time_now = time();
|
||||
$run_time = $time_now + $time_interval;
|
||||
if (!isset(self::$_tasks[$run_time])) {
|
||||
self::$_tasks[$run_time] = array();
|
||||
}
|
||||
self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tick.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function tick()
|
||||
{
|
||||
if (empty(self::$_tasks)) {
|
||||
pcntl_alarm(0);
|
||||
return;
|
||||
}
|
||||
|
||||
$time_now = time();
|
||||
foreach (self::$_tasks as $run_time => $task_data) {
|
||||
if ($time_now >= $run_time) {
|
||||
foreach ($task_data as $index => $one_task) {
|
||||
$task_func = $one_task[0];
|
||||
$task_args = $one_task[1];
|
||||
$persistent = $one_task[2];
|
||||
$time_interval = $one_task[3];
|
||||
try {
|
||||
call_user_func_array($task_func, $task_args);
|
||||
} catch (\Exception $e) {
|
||||
Worker::safeEcho($e);
|
||||
}
|
||||
if ($persistent) {
|
||||
self::add($time_interval, $task_func, $task_args);
|
||||
}
|
||||
}
|
||||
unset(self::$_tasks[$run_time]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a timer.
|
||||
*
|
||||
* @param mixed $timer_id
|
||||
* @return bool
|
||||
*/
|
||||
public static function del($timer_id)
|
||||
{
|
||||
if (self::$_event) {
|
||||
return self::$_event->del($timer_id, EventInterface::EV_TIMER);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all timers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delAll()
|
||||
{
|
||||
self::$_tasks = array();
|
||||
pcntl_alarm(0);
|
||||
if (self::$_event) {
|
||||
self::$_event->clearAllTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Lib;
|
||||
class Tool
|
||||
{
|
||||
public static $env = null;
|
||||
|
||||
// region 获取 UUID
|
||||
public static function uuid($break = '-'): string
|
||||
{
|
||||
$chars = md5(uniqid(mt_rand(), true));
|
||||
$chars_arr = [
|
||||
substr($chars, 0, 8),
|
||||
substr($chars, 8, 4),
|
||||
substr($chars, 12, 4),
|
||||
substr($chars, 16, 4),
|
||||
substr($chars, 20, 12),
|
||||
];
|
||||
return implode($break, $chars_arr);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 发送POST请求
|
||||
public static function post($url, $data = [], $decode = true, $type = 'json')
|
||||
{
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
if ($type === 'data') {
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
}
|
||||
if ($type === 'json') {
|
||||
$data_string = json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json; charset=utf-8',
|
||||
'Content-Length: ' . strlen($data_string)
|
||||
]);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
|
||||
}
|
||||
$r = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
if ($decode) {
|
||||
return json_decode($r, true);
|
||||
} else {
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 读取 Config ini
|
||||
public static function ini($key, $default = false)
|
||||
{
|
||||
if (!$key) return $default;
|
||||
if (!self::$env) {
|
||||
$config_file_path = dirname(__DIR__, 2) . '/.env';
|
||||
$env_content = file_get_contents($config_file_path);
|
||||
$env_ini = parse_ini_string($env_content);
|
||||
self::$env = $env_ini;
|
||||
}
|
||||
return (isset(self::$env[$key])) ? self::$env[$key] : $default;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 10位时间戳 格式化
|
||||
public static function date($time = false, $format = "Y-m-d H:i:s")
|
||||
{
|
||||
if (!$time) $time = time();
|
||||
return date($format, $time);
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
||||
// region 去除空格
|
||||
public static function ge($str)
|
||||
{
|
||||
return preg_replace("/\s+/", ' ', $str);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 毫秒时间戳
|
||||
public static function time()
|
||||
{
|
||||
return floor(microtime(true) * 1000);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 运行日志
|
||||
public static function log($content, $level = 1)
|
||||
{
|
||||
if ($level == 0) return;
|
||||
$path = dirname(__DIR__, 2) . '/logs/' . date('Ymd');
|
||||
if (!is_dir($path)) mkdir($path, 0777, true);
|
||||
$filename = $path . '/level' . $level . '.txt';
|
||||
$file = fopen($filename, "a");
|
||||
if ($file === false) return;
|
||||
fwrite($file, '[' . date('Y-m-d H:i:s') . ']' . $content . PHP_EOL);
|
||||
fclose($file);
|
||||
if ($level > 0) {
|
||||
self::log($content, $level - 1);
|
||||
}
|
||||
var_dump($content);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2009-2015 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/workerman/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
/**
|
||||
* Frame Protocol.
|
||||
*/
|
||||
class Frame
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, TcpConnection $connection)
|
||||
{
|
||||
if (strlen($buffer) < 4) {
|
||||
return 0;
|
||||
}
|
||||
$unpack_data = unpack('Ntotal_length', $buffer);
|
||||
return $unpack_data['total_length'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer)
|
||||
{
|
||||
return substr($buffer, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer)
|
||||
{
|
||||
$total_length = 4 + strlen($buffer);
|
||||
return pack('N', $total_length) . $buffer;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,701 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* http protocol
|
||||
*/
|
||||
class Http
|
||||
{
|
||||
/**
|
||||
* The supported HTTP methods
|
||||
* @var array
|
||||
*/
|
||||
public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS');
|
||||
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($recv_buffer, TcpConnection $connection)
|
||||
{
|
||||
if (!strpos($recv_buffer, "\r\n\r\n")) {
|
||||
// Judge whether the package length exceeds the limit.
|
||||
if (strlen($recv_buffer) >= $connection->maxPackageSize) {
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
list($header,) = explode("\r\n\r\n", $recv_buffer, 2);
|
||||
$method = substr($header, 0, strpos($header, ' '));
|
||||
|
||||
if(in_array($method, static::$methods)) {
|
||||
return static::getRequestSize($header, $method);
|
||||
}else{
|
||||
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whole size of the request
|
||||
* includes the request headers and request body.
|
||||
* @param string $header The request headers
|
||||
* @param string $method The request method
|
||||
* @return integer
|
||||
*/
|
||||
protected static function getRequestSize($header, $method)
|
||||
{
|
||||
if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') {
|
||||
return strlen($header) + 4;
|
||||
}
|
||||
$match = array();
|
||||
if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) {
|
||||
$content_length = isset($match[1]) ? $match[1] : 0;
|
||||
return $content_length + strlen($header) + 4;
|
||||
}
|
||||
return $method === 'DELETE' ? strlen($header) + 4 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse $_POST、$_GET、$_COOKIE.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return array
|
||||
*/
|
||||
public static function decode($recv_buffer, TcpConnection $connection)
|
||||
{
|
||||
// Init.
|
||||
$_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array();
|
||||
$GLOBALS['HTTP_RAW_POST_DATA'] = '';
|
||||
// Clear cache.
|
||||
HttpCache::$header = array('Connection' => 'Connection: keep-alive');
|
||||
HttpCache::$instance = new HttpCache();
|
||||
// $_SERVER
|
||||
$_SERVER = array(
|
||||
'QUERY_STRING' => '',
|
||||
'REQUEST_METHOD' => '',
|
||||
'REQUEST_URI' => '',
|
||||
'SERVER_PROTOCOL' => '',
|
||||
'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION,
|
||||
'SERVER_NAME' => '',
|
||||
'HTTP_HOST' => '',
|
||||
'HTTP_USER_AGENT' => '',
|
||||
'HTTP_ACCEPT' => '',
|
||||
'HTTP_ACCEPT_LANGUAGE' => '',
|
||||
'HTTP_ACCEPT_ENCODING' => '',
|
||||
'HTTP_COOKIE' => '',
|
||||
'HTTP_CONNECTION' => '',
|
||||
'CONTENT_TYPE' => '',
|
||||
'REMOTE_ADDR' => '',
|
||||
'REMOTE_PORT' => '0',
|
||||
'REQUEST_TIME' => time()
|
||||
);
|
||||
|
||||
// Parse headers.
|
||||
list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2);
|
||||
$header_data = explode("\r\n", $http_header);
|
||||
|
||||
list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ',
|
||||
$header_data[0]);
|
||||
|
||||
$http_post_boundary = '';
|
||||
unset($header_data[0]);
|
||||
foreach ($header_data as $content) {
|
||||
// \r\n\r\n
|
||||
if (empty($content)) {
|
||||
continue;
|
||||
}
|
||||
list($key, $value) = explode(':', $content, 2);
|
||||
$key = str_replace('-', '_', strtoupper($key));
|
||||
$value = trim($value);
|
||||
$_SERVER['HTTP_' . $key] = $value;
|
||||
switch ($key) {
|
||||
// HTTP_HOST
|
||||
case 'HOST':
|
||||
$tmp = explode(':', $value);
|
||||
$_SERVER['SERVER_NAME'] = $tmp[0];
|
||||
if (isset($tmp[1])) {
|
||||
$_SERVER['SERVER_PORT'] = $tmp[1];
|
||||
}
|
||||
break;
|
||||
// cookie
|
||||
case 'COOKIE':
|
||||
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
|
||||
break;
|
||||
// content-type
|
||||
case 'CONTENT_TYPE':
|
||||
if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) {
|
||||
if ($pos = strpos($value, ';')) {
|
||||
$_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos);
|
||||
} else {
|
||||
$_SERVER['CONTENT_TYPE'] = $value;
|
||||
}
|
||||
} else {
|
||||
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
|
||||
$http_post_boundary = '--' . $match[1];
|
||||
}
|
||||
break;
|
||||
case 'CONTENT_LENGTH':
|
||||
$_SERVER['CONTENT_LENGTH'] = $value;
|
||||
break;
|
||||
case 'UPGRADE':
|
||||
if($value=='websocket'){
|
||||
$connection->protocol = "\\Workerman\\Protocols\\Websocket";
|
||||
return \Workerman\Protocols\Websocket::input($recv_buffer,$connection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE){
|
||||
HttpCache::$gzip = true;
|
||||
}
|
||||
// Parse $_POST.
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_SERVER['CONTENT_TYPE'])) {
|
||||
switch ($_SERVER['CONTENT_TYPE']) {
|
||||
case 'multipart/form-data':
|
||||
self::parseUploadFiles($http_body, $http_post_boundary);
|
||||
break;
|
||||
case 'application/json':
|
||||
$_POST = json_decode($http_body, true);
|
||||
break;
|
||||
case 'application/x-www-form-urlencoded':
|
||||
parse_str($http_body, $_POST);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse other HTTP action parameters
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != "POST") {
|
||||
$data = array();
|
||||
if ($_SERVER['CONTENT_TYPE'] === "application/x-www-form-urlencoded") {
|
||||
parse_str($http_body, $data);
|
||||
} elseif ($_SERVER['CONTENT_TYPE'] === "application/json") {
|
||||
$data = json_decode($http_body, true);
|
||||
}
|
||||
$_REQUEST = array_merge($_REQUEST, $data);
|
||||
}
|
||||
|
||||
// HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA
|
||||
$GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body;
|
||||
|
||||
// QUERY_STRING
|
||||
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
|
||||
if ($_SERVER['QUERY_STRING']) {
|
||||
// $GET
|
||||
parse_str($_SERVER['QUERY_STRING'], $_GET);
|
||||
} else {
|
||||
$_SERVER['QUERY_STRING'] = '';
|
||||
}
|
||||
|
||||
if (is_array($_POST)) {
|
||||
// REQUEST
|
||||
$_REQUEST = array_merge($_GET, $_POST, $_REQUEST);
|
||||
} else {
|
||||
// REQUEST
|
||||
$_REQUEST = array_merge($_GET, $_REQUEST);
|
||||
}
|
||||
|
||||
// REMOTE_ADDR REMOTE_PORT
|
||||
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
|
||||
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
|
||||
|
||||
return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Http encode.
|
||||
*
|
||||
* @param string $content
|
||||
* @param TcpConnection $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($content, TcpConnection $connection)
|
||||
{
|
||||
// Default http-code.
|
||||
if (!isset(HttpCache::$header['Http-Code'])) {
|
||||
$header = "HTTP/1.1 200 OK\r\n";
|
||||
} else {
|
||||
$header = HttpCache::$header['Http-Code'] . "\r\n";
|
||||
unset(HttpCache::$header['Http-Code']);
|
||||
}
|
||||
|
||||
// Content-Type
|
||||
if (!isset(HttpCache::$header['Content-Type'])) {
|
||||
$header .= "Content-Type: text/html;charset=utf-8\r\n";
|
||||
}
|
||||
|
||||
// other headers
|
||||
foreach (HttpCache::$header as $key => $item) {
|
||||
if ('Set-Cookie' === $key && is_array($item)) {
|
||||
foreach ($item as $it) {
|
||||
$header .= $it . "\r\n";
|
||||
}
|
||||
} else {
|
||||
$header .= $item . "\r\n";
|
||||
}
|
||||
}
|
||||
if(HttpCache::$gzip && isset($connection->gzip) && $connection->gzip){
|
||||
$header .= "Content-Encoding: gzip\r\n";
|
||||
$content = gzencode($content,$connection->gzip);
|
||||
}
|
||||
// header
|
||||
$header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n";
|
||||
|
||||
// save session
|
||||
self::sessionWriteClose();
|
||||
|
||||
// the whole http package
|
||||
return $header . $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置http头
|
||||
*
|
||||
* @return bool|void
|
||||
*/
|
||||
public static function header($content, $replace = true, $http_response_code = 0)
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace);
|
||||
}
|
||||
if (strpos($content, 'HTTP') === 0) {
|
||||
$key = 'Http-Code';
|
||||
} else {
|
||||
$key = strstr($content, ":", true);
|
||||
if (empty($key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ('location' === strtolower($key) && !$http_response_code) {
|
||||
return self::header($content, true, 302);
|
||||
}
|
||||
|
||||
if (isset(HttpCache::$codes[$http_response_code])) {
|
||||
HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code];
|
||||
if ($key === 'Http-Code') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($key === 'Set-Cookie') {
|
||||
HttpCache::$header[$key][] = $content;
|
||||
} else {
|
||||
HttpCache::$header[$key] = $content;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove header.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public static function headerRemove($name)
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
header_remove($name);
|
||||
return;
|
||||
}
|
||||
unset(HttpCache::$header[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cookie.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param integer $maxage
|
||||
* @param string $path
|
||||
* @param string $domain
|
||||
* @param bool $secure
|
||||
* @param bool $HTTPOnly
|
||||
* @return bool|void
|
||||
*/
|
||||
public static function setcookie(
|
||||
$name,
|
||||
$value = '',
|
||||
$maxage = 0,
|
||||
$path = '',
|
||||
$domain = '',
|
||||
$secure = false,
|
||||
$HTTPOnly = false
|
||||
) {
|
||||
if (PHP_SAPI != 'cli') {
|
||||
return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly);
|
||||
}
|
||||
return self::header(
|
||||
'Set-Cookie: ' . $name . '=' . rawurlencode($value)
|
||||
. (empty($domain) ? '' : '; Domain=' . $domain)
|
||||
. (empty($maxage) ? '' : '; Max-Age=' . $maxage)
|
||||
. (empty($path) ? '' : '; Path=' . $path)
|
||||
. (!$secure ? '' : '; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionCreateId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sessionCreateId()
|
||||
{
|
||||
mt_srand();
|
||||
return bin2hex(pack('d', microtime(true)) . pack('N',mt_rand(0, 2147483647)));
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionId
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function sessionId($id = null)
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
return $id ? session_id($id) : session_id();
|
||||
}
|
||||
if (static::sessionStarted() && HttpCache::$instance->sessionFile) {
|
||||
return str_replace('ses_', '', basename(HttpCache::$instance->sessionFile));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionName
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sessionName($name = null)
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
return $name ? session_name($name) : session_name();
|
||||
}
|
||||
$session_name = HttpCache::$sessionName;
|
||||
if ($name && ! static::sessionStarted()) {
|
||||
HttpCache::$sessionName = $name;
|
||||
}
|
||||
return $session_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionSavePath
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function sessionSavePath($path = null)
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
return $path ? session_save_path($path) : session_save_path();
|
||||
}
|
||||
if ($path && is_dir($path) && is_writable($path) && !static::sessionStarted()) {
|
||||
HttpCache::$sessionPath = $path;
|
||||
}
|
||||
return HttpCache::$sessionPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionStarted
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sessionStarted()
|
||||
{
|
||||
if (!HttpCache::$instance) return false;
|
||||
|
||||
return HttpCache::$instance->sessionStarted;
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionStart
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sessionStart()
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
return session_start();
|
||||
}
|
||||
|
||||
self::tryGcSessions();
|
||||
|
||||
if (HttpCache::$instance->sessionStarted) {
|
||||
Worker::safeEcho("already sessionStarted\n");
|
||||
return true;
|
||||
}
|
||||
HttpCache::$instance->sessionStarted = true;
|
||||
// Generate a SID.
|
||||
if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName])) {
|
||||
// Create a unique session_id and the associated file name.
|
||||
while (true) {
|
||||
$session_id = static::sessionCreateId();
|
||||
if (!is_file($file_name = HttpCache::$sessionPath . '/ses_' . $session_id)) break;
|
||||
}
|
||||
HttpCache::$instance->sessionFile = $file_name;
|
||||
return self::setcookie(
|
||||
HttpCache::$sessionName
|
||||
, $session_id
|
||||
, ini_get('session.cookie_lifetime')
|
||||
, ini_get('session.cookie_path')
|
||||
, ini_get('session.cookie_domain')
|
||||
, ini_get('session.cookie_secure')
|
||||
, ini_get('session.cookie_httponly')
|
||||
);
|
||||
}
|
||||
if (!HttpCache::$instance->sessionFile) {
|
||||
HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName];
|
||||
}
|
||||
// Read session from session file.
|
||||
if (HttpCache::$instance->sessionFile) {
|
||||
$raw = file_get_contents(HttpCache::$instance->sessionFile);
|
||||
if ($raw) {
|
||||
$_SESSION = unserialize($raw);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save session.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sessionWriteClose()
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
return session_write_close();
|
||||
}
|
||||
if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) {
|
||||
$session_str = serialize($_SESSION);
|
||||
if ($session_str && HttpCache::$instance->sessionFile) {
|
||||
return file_put_contents(HttpCache::$instance->sessionFile, $session_str);
|
||||
}
|
||||
}
|
||||
return empty($_SESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* End, like call exit in php-fpm.
|
||||
*
|
||||
* @param string $msg
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function end($msg = '')
|
||||
{
|
||||
if (PHP_SAPI != 'cli') {
|
||||
exit($msg);
|
||||
}
|
||||
if ($msg) {
|
||||
echo $msg;
|
||||
}
|
||||
throw new \Exception('jump_exit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mime types.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMimeTypesFile()
|
||||
{
|
||||
return __DIR__ . '/Http/mime.types';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse $_FILES.
|
||||
*
|
||||
* @param string $http_body
|
||||
* @param string $http_post_boundary
|
||||
* @return void
|
||||
*/
|
||||
protected static function parseUploadFiles($http_body, $http_post_boundary)
|
||||
{
|
||||
$http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4));
|
||||
$boundary_data_array = explode($http_post_boundary . "\r\n", $http_body);
|
||||
if ($boundary_data_array[0] === '') {
|
||||
unset($boundary_data_array[0]);
|
||||
}
|
||||
$key = -1;
|
||||
foreach ($boundary_data_array as $boundary_data_buffer) {
|
||||
list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2);
|
||||
// Remove \r\n from the end of buffer.
|
||||
$boundary_value = substr($boundary_value, 0, -2);
|
||||
$key ++;
|
||||
foreach (explode("\r\n", $boundary_header_buffer) as $item) {
|
||||
list($header_key, $header_value) = explode(": ", $item);
|
||||
$header_key = strtolower($header_key);
|
||||
switch ($header_key) {
|
||||
case "content-disposition":
|
||||
// Is file data.
|
||||
if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) {
|
||||
// Parse $_FILES.
|
||||
$_FILES[$key] = array(
|
||||
'name' => $match[1],
|
||||
'file_name' => $match[2],
|
||||
'file_data' => $boundary_value,
|
||||
'file_size' => strlen($boundary_value),
|
||||
);
|
||||
break;
|
||||
} // Is post field.
|
||||
else {
|
||||
// Parse $_POST.
|
||||
if (preg_match('/name="(.*?)"$/', $header_value, $match)) {
|
||||
$_POST[$match[1]] = $boundary_value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "content-type":
|
||||
// add file_type
|
||||
$_FILES[$key]['file_type'] = trim($header_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try GC sessions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function tryGcSessions()
|
||||
{
|
||||
if (HttpCache::$sessionGcProbability <= 0 ||
|
||||
HttpCache::$sessionGcDivisor <= 0 ||
|
||||
rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) {
|
||||
return;
|
||||
}
|
||||
|
||||
$time_now = time();
|
||||
foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) {
|
||||
if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Http cache for the current http response.
|
||||
*/
|
||||
class HttpCache
|
||||
{
|
||||
public static $codes = array(
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
306 => '(Unused)',
|
||||
307 => 'Temporary Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Request Entity Too Large',
|
||||
414 => 'Request-URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Requested Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var HttpCache
|
||||
*/
|
||||
public static $instance = null;
|
||||
public static $header = array();
|
||||
public static $gzip = false;
|
||||
public static $sessionPath = '';
|
||||
public static $sessionName = '';
|
||||
public static $sessionGcProbability = 1;
|
||||
public static $sessionGcDivisor = 1000;
|
||||
public static $sessionGcMaxLifeTime = 1440;
|
||||
public $sessionStarted = false;
|
||||
public $sessionFile = '';
|
||||
|
||||
public static function init()
|
||||
{
|
||||
if (!self::$sessionName) {
|
||||
self::$sessionName = ini_get('session.name');
|
||||
}
|
||||
|
||||
if (!self::$sessionPath) {
|
||||
self::$sessionPath = @session_save_path();
|
||||
}
|
||||
|
||||
if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) {
|
||||
self::$sessionPath = sys_get_temp_dir();
|
||||
}
|
||||
|
||||
if ($gc_probability = ini_get('session.gc_probability')) {
|
||||
self::$sessionGcProbability = $gc_probability;
|
||||
}
|
||||
|
||||
if ($gc_divisor = ini_get('session.gc_divisor')) {
|
||||
self::$sessionGcDivisor = $gc_divisor;
|
||||
}
|
||||
|
||||
if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) {
|
||||
self::$sessionGcMaxLifeTime = $gc_max_life_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpCache::init();
|
||||
@ -0,0 +1,80 @@
|
||||
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/x-javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
image/svg+xml svg svgz;
|
||||
image/webp webp;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream eot;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Protocol interface
|
||||
*/
|
||||
interface ProtocolInterface
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
* Please return the length of package.
|
||||
* If length is unknow please return 0 that mean wating more data.
|
||||
* If the package has something wrong please return false the connection will be closed.
|
||||
*
|
||||
* @param ConnectionInterface $connection
|
||||
* @param string $recv_buffer
|
||||
* @return int|false
|
||||
*/
|
||||
public static function input($recv_buffer, ConnectionInterface $connection);
|
||||
|
||||
/**
|
||||
* Decode package and emit onMessage($message) callback, $message is the result that decode returned.
|
||||
*
|
||||
* @param ConnectionInterface $connection
|
||||
* @param string $recv_buffer
|
||||
* @return mixed
|
||||
*/
|
||||
public static function decode($recv_buffer, ConnectionInterface $connection);
|
||||
|
||||
/**
|
||||
* Encode package brefore sending to client.
|
||||
*
|
||||
* @param ConnectionInterface $connection
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($data, ConnectionInterface $connection);
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
/**
|
||||
* Text Protocol.
|
||||
*/
|
||||
class Text
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, TcpConnection $connection)
|
||||
{
|
||||
// Judge whether the package length exceeds the limit.
|
||||
if (strlen($buffer) >= $connection->maxPackageSize) {
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
// Find the position of "\n".
|
||||
$pos = strpos($buffer, "\n");
|
||||
// No "\n", packet length is unknown, continue to wait for the data so return 0.
|
||||
if ($pos === false) {
|
||||
return 0;
|
||||
}
|
||||
// Return the current package length.
|
||||
return $pos + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer)
|
||||
{
|
||||
// Add "\n"
|
||||
return $buffer . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer)
|
||||
{
|
||||
// Remove "\n"
|
||||
return rtrim($buffer, "\r\n");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,503 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* WebSocket protocol.
|
||||
*/
|
||||
class Websocket implements \Workerman\Protocols\ProtocolInterface
|
||||
{
|
||||
/**
|
||||
* Websocket blob type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_BLOB = "\x81";
|
||||
|
||||
/**
|
||||
* Websocket arraybuffer type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_ARRAYBUFFER = "\x82";
|
||||
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
// Receive length.
|
||||
$recv_len = strlen($buffer);
|
||||
// We need more data.
|
||||
if ($recv_len < 6) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Has not yet completed the handshake.
|
||||
if (empty($connection->websocketHandshake)) {
|
||||
return static::dealHandshake($buffer, $connection);
|
||||
}
|
||||
|
||||
// Buffer websocket frame data.
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
// We need more frame data.
|
||||
if ($connection->websocketCurrentFrameLength > $recv_len) {
|
||||
// Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$firstbyte = ord($buffer[0]);
|
||||
$secondbyte = ord($buffer[1]);
|
||||
$data_len = $secondbyte & 127;
|
||||
$is_fin_frame = $firstbyte >> 7;
|
||||
$masked = $secondbyte >> 7;
|
||||
|
||||
if (!$masked) {
|
||||
Worker::safeEcho("frame not masked so close the connection\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
$opcode = $firstbyte & 0xf;
|
||||
switch ($opcode) {
|
||||
case 0x0:
|
||||
break;
|
||||
// Blob type.
|
||||
case 0x1:
|
||||
break;
|
||||
// Arraybuffer type.
|
||||
case 0x2:
|
||||
break;
|
||||
// Close package.
|
||||
case 0x8:
|
||||
// Try to emit onWebSocketClose callback.
|
||||
if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) {
|
||||
try {
|
||||
call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
} // Close connection.
|
||||
else {
|
||||
$connection->close("\x88\x02\x27\x10", true);
|
||||
}
|
||||
return 0;
|
||||
// Ping package.
|
||||
case 0x9:
|
||||
break;
|
||||
// Pong package.
|
||||
case 0xa:
|
||||
break;
|
||||
// Wrong opcode.
|
||||
default :
|
||||
Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate packet length.
|
||||
$head_len = 6;
|
||||
if ($data_len === 126) {
|
||||
$head_len = 8;
|
||||
if ($head_len > $recv_len) {
|
||||
return 0;
|
||||
}
|
||||
$pack = unpack('nn/ntotal_len', $buffer);
|
||||
$data_len = $pack['total_len'];
|
||||
} else {
|
||||
if ($data_len === 127) {
|
||||
$head_len = 14;
|
||||
if ($head_len > $recv_len) {
|
||||
return 0;
|
||||
}
|
||||
$arr = unpack('n/N2c', $buffer);
|
||||
$data_len = $arr['c1']*4294967296 + $arr['c2'];
|
||||
}
|
||||
}
|
||||
$current_frame_length = $head_len + $data_len;
|
||||
|
||||
$total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length;
|
||||
if ($total_package_size > $connection->maxPackageSize) {
|
||||
Worker::safeEcho("error package. package_length=$total_package_size\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($is_fin_frame) {
|
||||
if ($opcode === 0x9) {
|
||||
if ($recv_len >= $current_frame_length) {
|
||||
$ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
|
||||
$connection->consumeRecvBuffer($current_frame_length);
|
||||
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) {
|
||||
try {
|
||||
call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection, $ping_data);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
} else {
|
||||
$connection->send($ping_data);
|
||||
}
|
||||
$connection->websocketType = $tmp_connection_type;
|
||||
if ($recv_len > $current_frame_length) {
|
||||
return static::input(substr($buffer, $current_frame_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else if ($opcode === 0xa) {
|
||||
if ($recv_len >= $current_frame_length) {
|
||||
$pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
|
||||
$connection->consumeRecvBuffer($current_frame_length);
|
||||
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
// Try to emit onWebSocketPong callback.
|
||||
if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) {
|
||||
try {
|
||||
call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection, $pong_data);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
$connection->websocketType = $tmp_connection_type;
|
||||
if ($recv_len > $current_frame_length) {
|
||||
return static::input(substr($buffer, $current_frame_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return $current_frame_length;
|
||||
} else {
|
||||
$connection->websocketCurrentFrameLength = $current_frame_length;
|
||||
}
|
||||
}
|
||||
|
||||
// Received just a frame length data.
|
||||
if ($connection->websocketCurrentFrameLength === $recv_len) {
|
||||
static::decode($buffer, $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
return 0;
|
||||
} // The length of the received data is greater than the length of a frame.
|
||||
elseif ($connection->websocketCurrentFrameLength < $recv_len) {
|
||||
static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$current_frame_length = $connection->websocketCurrentFrameLength;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
// Continue to read next frame.
|
||||
return static::input(substr($buffer, $current_frame_length), $connection);
|
||||
} // The length of the received data is less than the length of a frame.
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
if (!is_scalar($buffer)) {
|
||||
throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. ");
|
||||
}
|
||||
$len = strlen($buffer);
|
||||
if (empty($connection->websocketType)) {
|
||||
$connection->websocketType = static::BINARY_TYPE_BLOB;
|
||||
}
|
||||
|
||||
$first_byte = $connection->websocketType;
|
||||
|
||||
if ($len <= 125) {
|
||||
$encode_buffer = $first_byte . chr($len) . $buffer;
|
||||
} else {
|
||||
if ($len <= 65535) {
|
||||
$encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer;
|
||||
} else {
|
||||
$encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// Handshake not completed so temporary buffer websocket data waiting for send.
|
||||
if (empty($connection->websocketHandshake)) {
|
||||
if (empty($connection->tmpWebsocketData)) {
|
||||
$connection->tmpWebsocketData = '';
|
||||
}
|
||||
// If buffer has already full then discard the current package.
|
||||
if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) {
|
||||
if ($connection->onError) {
|
||||
try {
|
||||
call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
$connection->tmpWebsocketData .= $encode_buffer;
|
||||
// Check buffer is full.
|
||||
if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) {
|
||||
if ($connection->onBufferFull) {
|
||||
try {
|
||||
call_user_func($connection->onBufferFull, $connection);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return empty string.
|
||||
return '';
|
||||
}
|
||||
|
||||
return $encode_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
$len = ord($buffer[1]) & 127;
|
||||
if ($len === 126) {
|
||||
$masks = substr($buffer, 4, 4);
|
||||
$data = substr($buffer, 8);
|
||||
} else {
|
||||
if ($len === 127) {
|
||||
$masks = substr($buffer, 10, 4);
|
||||
$data = substr($buffer, 14);
|
||||
} else {
|
||||
$masks = substr($buffer, 2, 4);
|
||||
$data = substr($buffer, 6);
|
||||
}
|
||||
}
|
||||
$dataLength = strlen($data);
|
||||
$masks = str_repeat($masks, floor($dataLength / 4)) . substr($masks, 0, $dataLength % 4);
|
||||
$decoded = $data ^ $masks;
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
$connection->websocketDataBuffer .= $decoded;
|
||||
return $connection->websocketDataBuffer;
|
||||
} else {
|
||||
if ($connection->websocketDataBuffer !== '') {
|
||||
$decoded = $connection->websocketDataBuffer . $decoded;
|
||||
$connection->websocketDataBuffer = '';
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket handshake.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param \Workerman\Connection\TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
protected static function dealHandshake($buffer, $connection)
|
||||
{
|
||||
// HTTP protocol.
|
||||
if (0 === strpos($buffer, 'GET')) {
|
||||
// Find \r\n\r\n.
|
||||
$heder_end_pos = strpos($buffer, "\r\n\r\n");
|
||||
if (!$heder_end_pos) {
|
||||
return 0;
|
||||
}
|
||||
$header_length = $heder_end_pos + 4;
|
||||
|
||||
// Get Sec-WebSocket-Key.
|
||||
$Sec_WebSocket_Key = '';
|
||||
if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) {
|
||||
$Sec_WebSocket_Key = $match[1];
|
||||
} else {
|
||||
$connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n<div style=\"text-align:center\"><h1>Websocket</h1><hr>powerd by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>",
|
||||
true);
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
// Calculation websocket key.
|
||||
$new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
|
||||
// Handshake response data.
|
||||
$handshake_message = "HTTP/1.1 101 Switching Protocols\r\n";
|
||||
$handshake_message .= "Upgrade: websocket\r\n";
|
||||
$handshake_message .= "Sec-WebSocket-Version: 13\r\n";
|
||||
$handshake_message .= "Connection: Upgrade\r\n";
|
||||
$handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n";
|
||||
|
||||
// Websocket data buffer.
|
||||
$connection->websocketDataBuffer = '';
|
||||
// Current websocket frame length.
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
// Current websocket frame data.
|
||||
$connection->websocketCurrentFrameBuffer = '';
|
||||
// Consume handshake data.
|
||||
$connection->consumeRecvBuffer($header_length);
|
||||
|
||||
// blob or arraybuffer
|
||||
if (empty($connection->websocketType)) {
|
||||
$connection->websocketType = static::BINARY_TYPE_BLOB;
|
||||
}
|
||||
|
||||
$has_server_header = false;
|
||||
|
||||
// Try to emit onWebSocketConnect callback.
|
||||
if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) {
|
||||
static::parseHttpHeader($buffer);
|
||||
try {
|
||||
call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) {
|
||||
$connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION);
|
||||
}
|
||||
$_GET = $_SERVER = $_SESSION = $_COOKIE = array();
|
||||
|
||||
if (isset($connection->headers)) {
|
||||
if (is_array($connection->headers)) {
|
||||
foreach ($connection->headers as $header) {
|
||||
if (strpos($header, 'Server:') === 0) {
|
||||
$has_server_header = true;
|
||||
}
|
||||
$handshake_message .= "$header\r\n";
|
||||
}
|
||||
} else {
|
||||
$handshake_message .= "$connection->headers\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$has_server_header) {
|
||||
$handshake_message .= "Server: workerman/".Worker::VERSION."\r\n";
|
||||
}
|
||||
$handshake_message .= "\r\n";
|
||||
// Send handshake response.
|
||||
$connection->send($handshake_message, true);
|
||||
// Mark handshake complete..
|
||||
$connection->websocketHandshake = true;
|
||||
// There are data waiting to be sent.
|
||||
if (!empty($connection->tmpWebsocketData)) {
|
||||
$connection->send($connection->tmpWebsocketData, true);
|
||||
$connection->tmpWebsocketData = '';
|
||||
}
|
||||
if (strlen($buffer) > $header_length) {
|
||||
return static::input(substr($buffer, $header_length), $connection);
|
||||
}
|
||||
return 0;
|
||||
} // Is flash policy-file-request.
|
||||
elseif (0 === strpos($buffer, '<polic')) {
|
||||
$policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>' . "\0";
|
||||
$connection->send($policy_xml, true);
|
||||
$connection->consumeRecvBuffer(strlen($buffer));
|
||||
return 0;
|
||||
}
|
||||
// Bad websocket handshake request.
|
||||
$connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n<div style=\"text-align:center\"><h1>Websocket</h1><hr>powerd by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>",
|
||||
true);
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse http header.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return void
|
||||
*/
|
||||
protected static function parseHttpHeader($buffer)
|
||||
{
|
||||
// Parse headers.
|
||||
list($http_header, ) = explode("\r\n\r\n", $buffer, 2);
|
||||
$header_data = explode("\r\n", $http_header);
|
||||
|
||||
if ($_SERVER) {
|
||||
$_SERVER = array();
|
||||
}
|
||||
|
||||
list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ',
|
||||
$header_data[0]);
|
||||
|
||||
unset($header_data[0]);
|
||||
foreach ($header_data as $content) {
|
||||
// \r\n\r\n
|
||||
if (empty($content)) {
|
||||
continue;
|
||||
}
|
||||
list($key, $value) = explode(':', $content, 2);
|
||||
$key = str_replace('-', '_', strtoupper($key));
|
||||
$value = trim($value);
|
||||
$_SERVER['HTTP_' . $key] = $value;
|
||||
switch ($key) {
|
||||
// HTTP_HOST
|
||||
case 'HOST':
|
||||
$tmp = explode(':', $value);
|
||||
$_SERVER['SERVER_NAME'] = $tmp[0];
|
||||
if (isset($tmp[1])) {
|
||||
$_SERVER['SERVER_PORT'] = $tmp[1];
|
||||
}
|
||||
break;
|
||||
// cookie
|
||||
case 'COOKIE':
|
||||
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// QUERY_STRING
|
||||
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
|
||||
if ($_SERVER['QUERY_STRING']) {
|
||||
// $GET
|
||||
parse_str($_SERVER['QUERY_STRING'], $_GET);
|
||||
} else {
|
||||
$_SERVER['QUERY_STRING'] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,471 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Lib\Timer;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
/**
|
||||
* Websocket protocol for client.
|
||||
*/
|
||||
class Ws
|
||||
{
|
||||
/**
|
||||
* Websocket blob type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_BLOB = "\x81";
|
||||
|
||||
/**
|
||||
* Websocket arraybuffer type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_ARRAYBUFFER = "\x82";
|
||||
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, $connection)
|
||||
{
|
||||
if (empty($connection->handshakeStep)) {
|
||||
Worker::safeEcho("recv data before handshake. Buffer:" . bin2hex($buffer) . "\n");
|
||||
return false;
|
||||
}
|
||||
// Recv handshake response
|
||||
if ($connection->handshakeStep === 1) {
|
||||
return self::dealHandshake($buffer, $connection);
|
||||
}
|
||||
$recv_len = strlen($buffer);
|
||||
if ($recv_len < 2) {
|
||||
return 0;
|
||||
}
|
||||
// Buffer websocket frame data.
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
// We need more frame data.
|
||||
if ($connection->websocketCurrentFrameLength > $recv_len) {
|
||||
// Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
|
||||
$firstbyte = ord($buffer[0]);
|
||||
$secondbyte = ord($buffer[1]);
|
||||
$data_len = $secondbyte & 127;
|
||||
$is_fin_frame = $firstbyte >> 7;
|
||||
$masked = $secondbyte >> 7;
|
||||
|
||||
if ($masked) {
|
||||
Worker::safeEcho("frame masked so close the connection\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
$opcode = $firstbyte & 0xf;
|
||||
|
||||
switch ($opcode) {
|
||||
case 0x0:
|
||||
break;
|
||||
// Blob type.
|
||||
case 0x1:
|
||||
break;
|
||||
// Arraybuffer type.
|
||||
case 0x2:
|
||||
break;
|
||||
// Close package.
|
||||
case 0x8:
|
||||
// Try to emit onWebSocketClose callback.
|
||||
if (isset($connection->onWebSocketClose)) {
|
||||
try {
|
||||
call_user_func($connection->onWebSocketClose, $connection);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
} // Close connection.
|
||||
else {
|
||||
$connection->close();
|
||||
}
|
||||
return 0;
|
||||
// Ping package.
|
||||
case 0x9:
|
||||
break;
|
||||
// Pong package.
|
||||
case 0xa:
|
||||
break;
|
||||
// Wrong opcode.
|
||||
default :
|
||||
Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
// Calculate packet length.
|
||||
if ($data_len === 126) {
|
||||
if (strlen($buffer) < 4) {
|
||||
return 0;
|
||||
}
|
||||
$pack = unpack('nn/ntotal_len', $buffer);
|
||||
$current_frame_length = $pack['total_len'] + 4;
|
||||
} else if ($data_len === 127) {
|
||||
if (strlen($buffer) < 10) {
|
||||
return 0;
|
||||
}
|
||||
$arr = unpack('n/N2c', $buffer);
|
||||
$current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10;
|
||||
} else {
|
||||
$current_frame_length = $data_len + 2;
|
||||
}
|
||||
|
||||
$total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length;
|
||||
if ($total_package_size > $connection->maxPackageSize) {
|
||||
Worker::safeEcho("error package. package_length=$total_package_size\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($is_fin_frame) {
|
||||
if ($opcode === 0x9) {
|
||||
if ($recv_len >= $current_frame_length) {
|
||||
$ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
|
||||
$connection->consumeRecvBuffer($current_frame_length);
|
||||
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
if (isset($connection->onWebSocketPing)) {
|
||||
try {
|
||||
call_user_func($connection->onWebSocketPing, $connection, $ping_data);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
} else {
|
||||
$connection->send($ping_data);
|
||||
}
|
||||
$connection->websocketType = $tmp_connection_type;
|
||||
if ($recv_len > $current_frame_length) {
|
||||
return static::input(substr($buffer, $current_frame_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
} else if ($opcode === 0xa) {
|
||||
if ($recv_len >= $current_frame_length) {
|
||||
$pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection);
|
||||
$connection->consumeRecvBuffer($current_frame_length);
|
||||
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
// Try to emit onWebSocketPong callback.
|
||||
if (isset($connection->onWebSocketPong)) {
|
||||
try {
|
||||
call_user_func($connection->onWebSocketPong, $connection, $pong_data);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
$connection->websocketType = $tmp_connection_type;
|
||||
if ($recv_len > $current_frame_length) {
|
||||
return static::input(substr($buffer, $current_frame_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return $current_frame_length;
|
||||
} else {
|
||||
$connection->websocketCurrentFrameLength = $current_frame_length;
|
||||
}
|
||||
}
|
||||
// Received just a frame length data.
|
||||
if ($connection->websocketCurrentFrameLength === $recv_len) {
|
||||
self::decode($buffer, $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
return 0;
|
||||
} // The length of the received data is greater than the length of a frame.
|
||||
elseif ($connection->websocketCurrentFrameLength < $recv_len) {
|
||||
self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$current_frame_length = $connection->websocketCurrentFrameLength;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
// Continue to read next frame.
|
||||
return self::input(substr($buffer, $current_frame_length), $connection);
|
||||
} // The length of the received data is less than the length of a frame.
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($payload, $connection)
|
||||
{
|
||||
if (empty($connection->websocketType)) {
|
||||
$connection->websocketType = self::BINARY_TYPE_BLOB;
|
||||
}
|
||||
$payload = (string)$payload;
|
||||
if (empty($connection->handshakeStep)) {
|
||||
static::sendHandshake($connection);
|
||||
}
|
||||
$mask = 1;
|
||||
$mask_key = "\x00\x00\x00\x00";
|
||||
|
||||
$pack = '';
|
||||
$length = $length_flag = strlen($payload);
|
||||
if (65535 < $length) {
|
||||
$pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF);
|
||||
$length_flag = 127;
|
||||
} else if (125 < $length) {
|
||||
$pack = pack('n*', $length);
|
||||
$length_flag = 126;
|
||||
}
|
||||
|
||||
$head = ($mask << 7) | $length_flag;
|
||||
$head = $connection->websocketType . chr($head) . $pack;
|
||||
|
||||
$frame = $head . $mask_key;
|
||||
// append payload to frame:
|
||||
$mask_key = str_repeat($mask_key, floor($length / 4)) . substr($mask_key, 0, $length % 4);
|
||||
$frame .= $payload ^ $mask_key;
|
||||
if ($connection->handshakeStep === 1) {
|
||||
// If buffer has already full then discard the current package.
|
||||
if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) {
|
||||
if ($connection->onError) {
|
||||
try {
|
||||
call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
$connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame;
|
||||
// Check buffer is full.
|
||||
if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) {
|
||||
if ($connection->onBufferFull) {
|
||||
try {
|
||||
call_user_func($connection->onBufferFull, $connection);
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($bytes, $connection)
|
||||
{
|
||||
$data_length = ord($bytes[1]);
|
||||
|
||||
if ($data_length === 126) {
|
||||
$decoded_data = substr($bytes, 4);
|
||||
} else if ($data_length === 127) {
|
||||
$decoded_data = substr($bytes, 10);
|
||||
} else {
|
||||
$decoded_data = substr($bytes, 2);
|
||||
}
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
$connection->websocketDataBuffer .= $decoded_data;
|
||||
return $connection->websocketDataBuffer;
|
||||
} else {
|
||||
if ($connection->websocketDataBuffer !== '') {
|
||||
$decoded_data = $connection->websocketDataBuffer . $decoded_data;
|
||||
$connection->websocketDataBuffer = '';
|
||||
}
|
||||
return $decoded_data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send websocket handshake data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function onConnect($connection)
|
||||
{
|
||||
static::sendHandshake($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean
|
||||
*
|
||||
* @param $connection
|
||||
*/
|
||||
public static function onClose($connection)
|
||||
{
|
||||
$connection->handshakeStep = null;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
$connection->tmpWebsocketData = '';
|
||||
$connection->websocketDataBuffer = '';
|
||||
if (!empty($connection->websocketPingTimer)) {
|
||||
Timer::del($connection->websocketPingTimer);
|
||||
$connection->websocketPingTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send websocket handshake.
|
||||
*
|
||||
* @param \Workerman\Connection\TcpConnection $connection
|
||||
* @return void
|
||||
*/
|
||||
public static function sendHandshake($connection)
|
||||
{
|
||||
if (!empty($connection->handshakeStep)) {
|
||||
return;
|
||||
}
|
||||
// Get Host.
|
||||
$port = $connection->getRemotePort();
|
||||
$host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port;
|
||||
// Handshake header.
|
||||
$connection->websocketSecKey = base64_encode(md5(mt_rand(), true));
|
||||
$user_header = isset($connection->headers) ? $connection->headers :
|
||||
(isset($connection->wsHttpHeader) ? $connection->wsHttpHeader : null);
|
||||
$user_header_str = '';
|
||||
if (!empty($user_header)) {
|
||||
if (is_array($user_header)){
|
||||
foreach($user_header as $k=>$v){
|
||||
$user_header_str .= "$k: $v\r\n";
|
||||
}
|
||||
} else {
|
||||
$user_header_str .= $user_header;
|
||||
}
|
||||
$user_header_str = "\r\n".trim($user_header_str);
|
||||
}
|
||||
$header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n".
|
||||
(!preg_match("/\nHost:/i", $user_header_str) ? "Host: $host\r\n" : '').
|
||||
"Connection: Upgrade\r\n".
|
||||
"Upgrade: websocket\r\n".
|
||||
(isset($connection->websocketOrigin) ? "Origin: ".$connection->websocketOrigin."\r\n":'').
|
||||
(isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":'').
|
||||
"Sec-WebSocket-Version: 13\r\n".
|
||||
"Sec-WebSocket-Key: " . $connection->websocketSecKey . $user_header_str . "\r\n\r\n";
|
||||
$connection->send($header, true);
|
||||
$connection->handshakeStep = 1;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
$connection->websocketDataBuffer = '';
|
||||
$connection->tmpWebsocketData = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket handshake.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param \Workerman\Connection\TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function dealHandshake($buffer, $connection)
|
||||
{
|
||||
$pos = strpos($buffer, "\r\n\r\n");
|
||||
if ($pos) {
|
||||
//checking Sec-WebSocket-Accept
|
||||
if (preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) {
|
||||
if ($match[1] !== base64_encode(sha1($connection->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) {
|
||||
Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . substr($buffer, 0, $pos) . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . substr($buffer, 0, $pos) . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// handshake complete
|
||||
|
||||
// Get WebSocket subprotocol (if specified by server)
|
||||
if (preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) {
|
||||
$connection->WSServerProtocol = trim($match[1]);
|
||||
}
|
||||
|
||||
$connection->handshakeStep = 2;
|
||||
$handshake_response_length = $pos + 4;
|
||||
// Try to emit onWebSocketConnect callback.
|
||||
if (isset($connection->onWebSocketConnect)) {
|
||||
try {
|
||||
call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length));
|
||||
} catch (\Exception $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
Worker::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
// Headbeat.
|
||||
if (!empty($connection->websocketPingInterval)) {
|
||||
$connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){
|
||||
if (false === $connection->send(pack('H*', '898000000000'), true)) {
|
||||
Timer::del($connection->websocketPingTimer);
|
||||
$connection->websocketPingTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$connection->consumeRecvBuffer($handshake_response_length);
|
||||
if (!empty($connection->tmpWebsocketData)) {
|
||||
$connection->send($connection->tmpWebsocketData, true);
|
||||
$connection->tmpWebsocketData = '';
|
||||
}
|
||||
if (strlen($buffer) > $handshake_response_length) {
|
||||
return self::input(substr($buffer, $handshake_response_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function WSSetProtocol($connection, $params) {
|
||||
$connection->WSClientProtocol = $params[0];
|
||||
}
|
||||
|
||||
public static function WSGetServerProtocol($connection) {
|
||||
return (property_exists($connection, 'WSServerProtocol')?$connection->WSServerProtocol:null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,604 @@
|
||||
# Workerman
|
||||
[](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
|
||||
## What is it
|
||||
Workerman is an asynchronous event driven PHP framework with high performance for easily building fast, scalable network applications. Supports HTTP, Websocket, SSL and other custom protocols. Supports libevent/event extension, [HHVM](https://github.com/facebook/hhvm) , [ReactPHP](https://github.com/reactphp/react).
|
||||
|
||||
## Requires
|
||||
PHP 5.3 or Higher
|
||||
A POSIX compatible operating system (Linux, OSX, BSD)
|
||||
POSIX and PCNTL extensions required
|
||||
Event extension recommended for better performance
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
composer require workerman/workerman
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### A websocket server
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
// Create a Websocket server
|
||||
$ws_worker = new Worker("websocket://0.0.0.0:2346");
|
||||
|
||||
// 4 processes
|
||||
$ws_worker->count = 4;
|
||||
|
||||
// Emitted when new connection come
|
||||
$ws_worker->onConnect = function($connection)
|
||||
{
|
||||
echo "New connection\n";
|
||||
};
|
||||
|
||||
// Emitted when data received
|
||||
$ws_worker->onMessage = function($connection, $data)
|
||||
{
|
||||
// Send hello $data
|
||||
$connection->send('hello ' . $data);
|
||||
};
|
||||
|
||||
// Emitted when connection closed
|
||||
$ws_worker->onClose = function($connection)
|
||||
{
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
// Run worker
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### An http server
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
// #### http worker ####
|
||||
$http_worker = new Worker("http://0.0.0.0:2345");
|
||||
|
||||
// 4 processes
|
||||
$http_worker->count = 4;
|
||||
|
||||
// Emitted when data received
|
||||
$http_worker->onMessage = function($connection, $data)
|
||||
{
|
||||
// $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available
|
||||
var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES);
|
||||
// send data to client
|
||||
$connection->send("hello world \n");
|
||||
};
|
||||
|
||||
// run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### A WebServer
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\WebServer;
|
||||
use Workerman\Worker;
|
||||
|
||||
// WebServer
|
||||
$web = new WebServer("http://0.0.0.0:80");
|
||||
|
||||
// 4 processes
|
||||
$web->count = 4;
|
||||
|
||||
// Set the root of domains
|
||||
$web->addRoot('www.your_domain.com', '/your/path/Web');
|
||||
$web->addRoot('www.another_domain.com', '/another/path/Web');
|
||||
// run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### A tcp server
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
// #### create socket and listen 1234 port ####
|
||||
$tcp_worker = new Worker("tcp://0.0.0.0:1234");
|
||||
|
||||
// 4 processes
|
||||
$tcp_worker->count = 4;
|
||||
|
||||
// Emitted when new connection come
|
||||
$tcp_worker->onConnect = function($connection)
|
||||
{
|
||||
echo "New Connection\n";
|
||||
};
|
||||
|
||||
// Emitted when data received
|
||||
$tcp_worker->onMessage = function($connection, $data)
|
||||
{
|
||||
// send data to client
|
||||
$connection->send("hello $data \n");
|
||||
};
|
||||
|
||||
// Emitted when new connection come
|
||||
$tcp_worker->onClose = function($connection)
|
||||
{
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Enable SSL
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
// SSL context.
|
||||
$context = array(
|
||||
'ssl' => array(
|
||||
'local_cert' => '/your/path/of/server.pem',
|
||||
'local_pk' => '/your/path/of/server.key',
|
||||
'verify_peer' => false,
|
||||
)
|
||||
);
|
||||
|
||||
// Create a Websocket server with ssl context.
|
||||
$ws_worker = new Worker("websocket://0.0.0.0:2346", $context);
|
||||
|
||||
// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://).
|
||||
// The similar approaches for Https etc.
|
||||
$ws_worker->transport = 'ssl';
|
||||
|
||||
$ws_worker->onMessage = function($connection, $data)
|
||||
{
|
||||
// Send hello $data
|
||||
$connection->send('hello ' . $data);
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Custom protocol
|
||||
Protocols/MyTextProtocol.php
|
||||
```php
|
||||
namespace Protocols;
|
||||
/**
|
||||
* User defined protocol
|
||||
* Format Text+"\n"
|
||||
*/
|
||||
class MyTextProtocol
|
||||
{
|
||||
public static function input($recv_buffer)
|
||||
{
|
||||
// Find the position of the first occurrence of "\n"
|
||||
$pos = strpos($recv_buffer, "\n");
|
||||
// Not a complete package. Return 0 because the length of package can not be calculated
|
||||
if($pos === false)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// Return length of the package
|
||||
return $pos+1;
|
||||
}
|
||||
|
||||
public static function decode($recv_buffer)
|
||||
{
|
||||
return trim($recv_buffer);
|
||||
}
|
||||
|
||||
public static function encode($data)
|
||||
{
|
||||
return $data."\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
// #### MyTextProtocol worker ####
|
||||
$text_worker = new Worker("MyTextProtocol://0.0.0.0:5678");
|
||||
|
||||
$text_worker->onConnect = function($connection)
|
||||
{
|
||||
echo "New connection\n";
|
||||
};
|
||||
|
||||
$text_worker->onMessage = function($connection, $data)
|
||||
{
|
||||
// send data to client
|
||||
$connection->send("hello world \n");
|
||||
};
|
||||
|
||||
$text_worker->onClose = function($connection)
|
||||
{
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
// run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Timer
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
use Workerman\Lib\Timer;
|
||||
|
||||
$task = new Worker();
|
||||
$task->onWorkerStart = function($task)
|
||||
{
|
||||
// 2.5 seconds
|
||||
$time_interval = 2.5;
|
||||
$timer_id = Timer::add($time_interval,
|
||||
function()
|
||||
{
|
||||
echo "Timer run\n";
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### AsyncTcpConnection (tcp/ws/text/frame etc...)
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
use Workerman\Connection\AsyncTcpConnection;
|
||||
|
||||
$worker = new Worker();
|
||||
$worker->onWorkerStart = function()
|
||||
{
|
||||
// Websocket protocol for client.
|
||||
$ws_connection = new AsyncTcpConnection("ws://echo.websocket.org:80");
|
||||
$ws_connection->onConnect = function($connection){
|
||||
$connection->send('hello');
|
||||
};
|
||||
$ws_connection->onMessage = function($connection, $data){
|
||||
echo "recv: $data\n";
|
||||
};
|
||||
$ws_connection->onError = function($connection, $code, $msg){
|
||||
echo "error: $msg\n";
|
||||
};
|
||||
$ws_connection->onClose = function($connection){
|
||||
echo "connection closed\n";
|
||||
};
|
||||
$ws_connection->connect();
|
||||
};
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Async Mysql of ReactPHP
|
||||
```
|
||||
composer require react/mysql
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('tcp://0.0.0.0:6161');
|
||||
$worker->onWorkerStart = function() {
|
||||
global $mysql;
|
||||
$loop = Worker::getEventLoop();
|
||||
$mysql = new React\MySQL\Connection($loop, array(
|
||||
'host' => '127.0.0.1',
|
||||
'dbname' => 'dbname',
|
||||
'user' => 'user',
|
||||
'passwd' => 'passwd',
|
||||
));
|
||||
$mysql->on('error', function($e){
|
||||
echo $e;
|
||||
});
|
||||
$mysql->connect(function ($e) {
|
||||
if($e) {
|
||||
echo $e;
|
||||
} else {
|
||||
echo "connect success\n";
|
||||
}
|
||||
});
|
||||
};
|
||||
$worker->onMessage = function($connection, $data) {
|
||||
global $mysql;
|
||||
$mysql->query('show databases' /*trim($data)*/, function ($command, $mysql) use ($connection) {
|
||||
if ($command->hasError()) {
|
||||
$error = $command->getError();
|
||||
} else {
|
||||
$results = $command->resultRows;
|
||||
$fields = $command->resultFields;
|
||||
$connection->send(json_encode($results));
|
||||
}
|
||||
});
|
||||
};
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Async Redis of ReactPHP
|
||||
```
|
||||
composer require clue/redis-react
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Clue\React\Redis\Factory;
|
||||
use Clue\React\Redis\Client;
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('tcp://0.0.0.0:6161');
|
||||
|
||||
$worker->onWorkerStart = function() {
|
||||
global $factory;
|
||||
$loop = Worker::getEventLoop();
|
||||
$factory = new Factory($loop);
|
||||
};
|
||||
|
||||
$worker->onMessage = function($connection, $data) {
|
||||
global $factory;
|
||||
$factory->createClient('localhost:6379')->then(function (Client $client) use ($connection) {
|
||||
$client->set('greeting', 'Hello world');
|
||||
$client->append('greeting', '!');
|
||||
|
||||
$client->get('greeting')->then(function ($greeting) use ($connection){
|
||||
// Hello world!
|
||||
echo $greeting . PHP_EOL;
|
||||
$connection->send($greeting);
|
||||
});
|
||||
|
||||
$client->incr('invocation')->then(function ($n) use ($connection){
|
||||
echo 'This is invocation #' . $n . PHP_EOL;
|
||||
$connection->send($n);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Aysnc dns of ReactPHP
|
||||
```
|
||||
composer require react/dns
|
||||
```
|
||||
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
$worker = new Worker('tcp://0.0.0.0:6161');
|
||||
$worker->onWorkerStart = function() {
|
||||
global $dns;
|
||||
// Get event-loop.
|
||||
$loop = Worker::getEventLoop();
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->create('8.8.8.8', $loop);
|
||||
};
|
||||
$worker->onMessage = function($connection, $host) {
|
||||
global $dns;
|
||||
$host = trim($host);
|
||||
$dns->resolve($host)->then(function($ip) use($host, $connection) {
|
||||
$connection->send("$host: $ip");
|
||||
},function($e) use($host, $connection){
|
||||
$connection->send("$host: {$e->getMessage()}");
|
||||
});
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Http client of ReactPHP
|
||||
```
|
||||
composer require react/http-client
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('tcp://0.0.0.0:6161');
|
||||
|
||||
$worker->onMessage = function($connection, $host) {
|
||||
$loop = Worker::getEventLoop();
|
||||
$client = new \React\HttpClient\Client($loop);
|
||||
$request = $client->request('GET', trim($host));
|
||||
$request->on('error', function(Exception $e) use ($connection) {
|
||||
$connection->send($e);
|
||||
});
|
||||
$request->on('response', function ($response) use ($connection) {
|
||||
$response->on('data', function ($data) use ($connection) {
|
||||
$connection->send($data);
|
||||
});
|
||||
});
|
||||
$request->end();
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### ZMQ of ReactPHP
|
||||
```
|
||||
composer require react/zmq
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('text://0.0.0.0:6161');
|
||||
|
||||
$worker->onWorkerStart = function() {
|
||||
global $pull;
|
||||
$loop = Worker::getEventLoop();
|
||||
$context = new React\ZMQ\Context($loop);
|
||||
$pull = $context->getSocket(ZMQ::SOCKET_PULL);
|
||||
$pull->bind('tcp://127.0.0.1:5555');
|
||||
|
||||
$pull->on('error', function ($e) {
|
||||
var_dump($e->getMessage());
|
||||
});
|
||||
|
||||
$pull->on('message', function ($msg) {
|
||||
echo "Received: $msg\n";
|
||||
});
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### STOMP of ReactPHP
|
||||
```
|
||||
composer require react/stomp
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker('text://0.0.0.0:6161');
|
||||
|
||||
$worker->onWorkerStart = function() {
|
||||
global $client;
|
||||
$loop = Worker::getEventLoop();
|
||||
$factory = new React\Stomp\Factory($loop);
|
||||
$client = $factory->createClient(array('vhost' => '/', 'login' => 'guest', 'passcode' => 'guest'));
|
||||
|
||||
$client
|
||||
->connect()
|
||||
->then(function ($client) use ($loop) {
|
||||
$client->subscribe('/topic/foo', function ($frame) {
|
||||
echo "Message received: {$frame->body}\n";
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Available commands
|
||||
```php start.php start ```
|
||||
```php start.php start -d ```
|
||||

|
||||
```php start.php status ```
|
||||

|
||||
```php start.php connections```
|
||||
```php start.php stop ```
|
||||
```php start.php restart ```
|
||||
```php start.php reload ```
|
||||
|
||||
## Documentation
|
||||
|
||||
中文主页:[http://www.workerman.net](http://www.workerman.net)
|
||||
|
||||
中文文档: [http://doc.workerman.net](http://doc.workerman.net)
|
||||
|
||||
Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md)
|
||||
|
||||
# Benchmarks
|
||||
```
|
||||
CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally
|
||||
Memory: 8G
|
||||
OS: Ubuntu 14.04 LTS
|
||||
Software: ab
|
||||
PHP: 5.5.9
|
||||
```
|
||||
|
||||
**Codes**
|
||||
```php
|
||||
<?php
|
||||
use Workerman\Worker;
|
||||
$worker = new Worker('tcp://0.0.0.0:1234');
|
||||
$worker->count=3;
|
||||
$worker->onMessage = function($connection, $data)
|
||||
{
|
||||
$connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello");
|
||||
};
|
||||
Worker::runAll();
|
||||
```
|
||||
**Result**
|
||||
|
||||
```shell
|
||||
ab -n1000000 -c100 -k http://127.0.0.1:1234/
|
||||
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
|
||||
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
|
||||
Licensed to The Apache Software Foundation, http://www.apache.org/
|
||||
|
||||
Benchmarking 127.0.0.1 (be patient)
|
||||
Completed 100000 requests
|
||||
Completed 200000 requests
|
||||
Completed 300000 requests
|
||||
Completed 400000 requests
|
||||
Completed 500000 requests
|
||||
Completed 600000 requests
|
||||
Completed 700000 requests
|
||||
Completed 800000 requests
|
||||
Completed 900000 requests
|
||||
Completed 1000000 requests
|
||||
Finished 1000000 requests
|
||||
|
||||
|
||||
Server Software: workerman/3.1.4
|
||||
Server Hostname: 127.0.0.1
|
||||
Server Port: 1234
|
||||
|
||||
Document Path: /
|
||||
Document Length: 5 bytes
|
||||
|
||||
Concurrency Level: 100
|
||||
Time taken for tests: 7.240 seconds
|
||||
Complete requests: 1000000
|
||||
Failed requests: 0
|
||||
Keep-Alive requests: 1000000
|
||||
Total transferred: 73000000 bytes
|
||||
HTML transferred: 5000000 bytes
|
||||
Requests per second: 138124.14 [#/sec] (mean)
|
||||
Time per request: 0.724 [ms] (mean)
|
||||
Time per request: 0.007 [ms] (mean, across all concurrent requests)
|
||||
Transfer rate: 9846.74 [Kbytes/sec] received
|
||||
|
||||
Connection Times (ms)
|
||||
min mean[+/-sd] median max
|
||||
Connect: 0 0 0.0 0 5
|
||||
Processing: 0 1 0.2 1 9
|
||||
Waiting: 0 1 0.2 1 9
|
||||
Total: 0 1 0.2 1 9
|
||||
|
||||
Percentage of the requests served within a certain time (ms)
|
||||
50% 1
|
||||
66% 1
|
||||
75% 1
|
||||
80% 1
|
||||
90% 1
|
||||
95% 1
|
||||
98% 1
|
||||
99% 1
|
||||
100% 9 (longest request)
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Other links with workerman
|
||||
|
||||
[PHPSocket.IO](https://github.com/walkor/phpsocket.io)
|
||||
[php-socks5](https://github.com/walkor/php-socks5)
|
||||
[php-http-proxy](https://github.com/walkor/php-http-proxy)
|
||||
|
||||
## Donate
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UQGGS9UB35WWG"><img src="http://donate.workerman.net/img/donate.png"></a>
|
||||
|
||||
## LICENSE
|
||||
|
||||
Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt).
|
||||
@ -0,0 +1,327 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman;
|
||||
|
||||
use Workerman\Protocols\Http;
|
||||
use Workerman\Protocols\HttpCache;
|
||||
|
||||
/**
|
||||
* WebServer.
|
||||
*/
|
||||
class WebServer extends Worker
|
||||
{
|
||||
/**
|
||||
* Virtual host to path mapping.
|
||||
*
|
||||
* @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www']
|
||||
*/
|
||||
protected $serverRoot = array();
|
||||
|
||||
/**
|
||||
* Mime mapping.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $mimeTypeMap = array();
|
||||
|
||||
|
||||
/**
|
||||
* Used to save user OnWorkerStart callback settings.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
protected $_onWorkerStart = null;
|
||||
|
||||
/**
|
||||
* Add virtual host.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $config
|
||||
* @return void
|
||||
*/
|
||||
public function addRoot($domain, $config)
|
||||
{
|
||||
if (is_string($config)) {
|
||||
$config = array('root' => $config);
|
||||
}
|
||||
$this->serverRoot[$domain] = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param string $socket_name
|
||||
* @param array $context_option
|
||||
*/
|
||||
public function __construct($socket_name, $context_option = array())
|
||||
{
|
||||
list(, $address) = explode(':', $socket_name, 2);
|
||||
parent::__construct('http:' . $address, $context_option);
|
||||
$this->name = 'WebServer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Run webserver instance.
|
||||
*
|
||||
* @see Workerman.Worker::run()
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->_onWorkerStart = $this->onWorkerStart;
|
||||
$this->onWorkerStart = array($this, 'onWorkerStart');
|
||||
$this->onMessage = array($this, 'onMessage');
|
||||
parent::run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit when process start.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function onWorkerStart()
|
||||
{
|
||||
if (empty($this->serverRoot)) {
|
||||
Worker::safeEcho(new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'));
|
||||
exit(250);
|
||||
}
|
||||
|
||||
// Init mimeMap.
|
||||
$this->initMimeTypeMap();
|
||||
|
||||
// Try to emit onWorkerStart callback.
|
||||
if ($this->_onWorkerStart) {
|
||||
try {
|
||||
call_user_func($this->_onWorkerStart, $this);
|
||||
} catch (\Exception $e) {
|
||||
self::log($e);
|
||||
exit(250);
|
||||
} catch (\Error $e) {
|
||||
self::log($e);
|
||||
exit(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init mime map.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initMimeTypeMap()
|
||||
{
|
||||
$mime_file = Http::getMimeTypesFile();
|
||||
if (!is_file($mime_file)) {
|
||||
$this->log("$mime_file mime.type file not fond");
|
||||
return;
|
||||
}
|
||||
$items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
if (!is_array($items)) {
|
||||
$this->log("get $mime_file mime.type content fail");
|
||||
return;
|
||||
}
|
||||
foreach ($items as $content) {
|
||||
if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) {
|
||||
$mime_type = $match[1];
|
||||
$workerman_file_extension_var = $match[2];
|
||||
$workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1));
|
||||
foreach ($workerman_file_extension_array as $workerman_file_extension) {
|
||||
self::$mimeTypeMap[$workerman_file_extension] = $mime_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit when http message coming.
|
||||
*
|
||||
* @param Connection\TcpConnection $connection
|
||||
* @return void
|
||||
*/
|
||||
public function onMessage($connection)
|
||||
{
|
||||
// REQUEST_URI.
|
||||
$workerman_url_info = parse_url('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
|
||||
if (!$workerman_url_info) {
|
||||
Http::header('HTTP/1.1 400 Bad Request');
|
||||
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
|
||||
$connection->send('<h1>400 Bad Request</h1>');
|
||||
} else {
|
||||
$connection->close('<h1>400 Bad Request</h1>');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/';
|
||||
|
||||
$workerman_path_info = pathinfo($workerman_path);
|
||||
$workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : '';
|
||||
if ($workerman_file_extension === '') {
|
||||
$workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php';
|
||||
$workerman_file_extension = 'php';
|
||||
}
|
||||
|
||||
$workerman_siteConfig = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot);
|
||||
$workerman_root_dir = $workerman_siteConfig['root'];
|
||||
$workerman_file = "$workerman_root_dir/$workerman_path";
|
||||
if(isset($workerman_siteConfig['additionHeader'])){
|
||||
Http::header($workerman_siteConfig['additionHeader']);
|
||||
}
|
||||
if ($workerman_file_extension === 'php' && !is_file($workerman_file)) {
|
||||
$workerman_file = "$workerman_root_dir/index.php";
|
||||
if (!is_file($workerman_file)) {
|
||||
$workerman_file = "$workerman_root_dir/index.html";
|
||||
$workerman_file_extension = 'html';
|
||||
}
|
||||
}
|
||||
|
||||
// File exsits.
|
||||
if (is_file($workerman_file)) {
|
||||
// Security check.
|
||||
if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath,
|
||||
$workerman_root_dir_realpath)
|
||||
) {
|
||||
Http::header('HTTP/1.1 400 Bad Request');
|
||||
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
|
||||
$connection->send('<h1>400 Bad Request</h1>');
|
||||
} else {
|
||||
$connection->close('<h1>400 Bad Request</h1>');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$workerman_file = realpath($workerman_file);
|
||||
|
||||
// Request php file.
|
||||
if ($workerman_file_extension === 'php') {
|
||||
$workerman_cwd = getcwd();
|
||||
chdir($workerman_root_dir);
|
||||
ini_set('display_errors', 'off');
|
||||
ob_start();
|
||||
// Try to include php file.
|
||||
try {
|
||||
// $_SERVER.
|
||||
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
|
||||
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
|
||||
include $workerman_file;
|
||||
} catch (\Exception $e) {
|
||||
// Jump_exit?
|
||||
if ($e->getMessage() != 'jump_exit') {
|
||||
Worker::safeEcho($e);
|
||||
}
|
||||
}
|
||||
$content = ob_get_clean();
|
||||
ini_set('display_errors', 'on');
|
||||
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
|
||||
$connection->send($content);
|
||||
} else {
|
||||
$connection->close($content);
|
||||
}
|
||||
chdir($workerman_cwd);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send file to client.
|
||||
return self::sendFile($connection, $workerman_file);
|
||||
} else {
|
||||
// 404
|
||||
Http::header("HTTP/1.1 404 Not Found");
|
||||
if(isset($workerman_siteConfig['custom404']) && file_exists($workerman_siteConfig['custom404'])){
|
||||
$html404 = file_get_contents($workerman_siteConfig['custom404']);
|
||||
}else{
|
||||
$html404 = '<html><head><title>404 File not found</title></head><body><center><h3>404 Not Found</h3></center></body></html>';
|
||||
}
|
||||
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
|
||||
$connection->send($html404);
|
||||
} else {
|
||||
$connection->close($html404);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendFile($connection, $file_path)
|
||||
{
|
||||
// Check 304.
|
||||
$info = stat($file_path);
|
||||
$modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : '';
|
||||
if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) {
|
||||
// Http 304.
|
||||
if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
|
||||
// 304
|
||||
Http::header('HTTP/1.1 304 Not Modified');
|
||||
// Send nothing but http headers..
|
||||
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
|
||||
$connection->send('');
|
||||
} else {
|
||||
$connection->close('');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Http header.
|
||||
if ($modified_time) {
|
||||
$modified_time = "Last-Modified: $modified_time\r\n";
|
||||
}
|
||||
$file_size = filesize($file_path);
|
||||
$file_info = pathinfo($file_path);
|
||||
$extension = isset($file_info['extension']) ? $file_info['extension'] : '';
|
||||
$file_name = isset($file_info['filename']) ? $file_info['filename'] : '';
|
||||
$header = "HTTP/1.1 200 OK\r\n";
|
||||
if (isset(self::$mimeTypeMap[$extension])) {
|
||||
$header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n";
|
||||
} else {
|
||||
$header .= "Content-Type: application/octet-stream\r\n";
|
||||
$header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n";
|
||||
}
|
||||
$header .= "Connection: keep-alive\r\n";
|
||||
$header .= $modified_time;
|
||||
$header .= "Content-Length: $file_size\r\n\r\n";
|
||||
$trunk_limit_size = 1024*1024;
|
||||
if ($file_size < $trunk_limit_size) {
|
||||
return $connection->send($header.file_get_contents($file_path), true);
|
||||
}
|
||||
$connection->send($header, true);
|
||||
|
||||
// Read file content from disk piece by piece and send to client.
|
||||
$connection->fileHandler = fopen($file_path, 'r');
|
||||
$do_write = function()use($connection)
|
||||
{
|
||||
// Send buffer not full.
|
||||
while(empty($connection->bufferFull))
|
||||
{
|
||||
// Read from disk.
|
||||
$buffer = fread($connection->fileHandler, 8192);
|
||||
// Read eof.
|
||||
if($buffer === '' || $buffer === false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$connection->send($buffer, true);
|
||||
}
|
||||
};
|
||||
// Send buffer full.
|
||||
$connection->onBufferFull = function($connection)
|
||||
{
|
||||
$connection->bufferFull = true;
|
||||
};
|
||||
// Send buffer drain.
|
||||
$connection->onBufferDrain = function($connection)use($do_write)
|
||||
{
|
||||
$connection->bufferFull = false;
|
||||
$do_write();
|
||||
};
|
||||
$do_write();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "workerman/workerman",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"event-loop",
|
||||
"asynchronous"
|
||||
],
|
||||
"homepage": "http://www.workerman.net",
|
||||
"license": "MIT",
|
||||
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "walkor",
|
||||
"email": "walkor@workerman.net",
|
||||
"homepage": "http://www.workerman.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "walkor@workerman.net",
|
||||
"issues": "https://github.com/walkor/workerman/issues",
|
||||
"forum": "http://wenda.workerman.net/",
|
||||
"wiki": "http://doc.workerman.net/",
|
||||
"source": "https://github.com/walkor/workerman"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Workerman\\": "./"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
Loading…
Reference in New Issue