update workerman to 3.3.4

This commit is contained in:
walkor 2016-09-20 21:27:41 +08:00
parent a3d823d04a
commit 58dc935eb0
25 changed files with 4159 additions and 2500 deletions

View File

@ -52,46 +52,16 @@ $worker->onMessage = function($connection, $buffer)
$connection->close();
return;
}
//$connection->send("\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10");
//$connection->stage = STAGE_DNS;
$connection->stage = STAGE_CONNECTING;
$remote_connection = new AsyncTcpConnection('tcp://'.$header_data[1].':'.$header_data[2]);
$remote_connection->onConnect = function($remote_connection)use($connection)
{
$connection->state = STAGE_STREAM;
$connection->send("\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10");
$connection->pipe($remote_connection);
$remote_connection->pipe($connection);
};
$remote_connection->onMessage = function($remote_connection, $buffer)use($connection)
{
$connection->send($buffer);
};
$remote_connection->onClose = function($remote_connection)use($connection)
{
$connection->close();
};
$remote_connection->onError = function($remote_connection, $code, $type)use($connection)
{
if($connection->stage == STAGE_CONNECTING)
{
$connection->send("\x05\x03\x00\x01\x00\x00\x00\x00\x10\x10");
}
$connection->close();
};
$connection->onMessage = function($connection, $data)use($remote_connection)
{
$remote_connection->send($data);
};
$connection->onClose = function($connection)use($remote_connection)
{
$remote_connection->close();
};
$connection->onError = function($connection)use($remote_connection)
{
echo "connection err\n";
$connection->close();
$remote_connection->close();
};
$remote_connection->connect();
}
};

View File

@ -1,61 +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;
// 定义Workerman根目录
if(!defined('WORKERMAN_ROOT_DIR'))
{
define('WORKERMAN_ROOT_DIR', realpath(__DIR__ . '/../'));
}
// 包含常量定义文件
require_once WORKERMAN_ROOT_DIR.'/Workerman/Lib/Constants.php';
/**
* 自动加载类
* @author walkor<walkor@workerman.net>
* Autoload.
*/
class Autoloader
{
// 应用的初始化目录,作为加载类文件的参考目录
protected static $_appInitPath = '';
/**
* 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::$_appInitPath = $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);
// 先尝试在应用目录寻找文件
$class_file = self::$_appInitPath . '/' . $class_path.'.php';
// 文件不存在则在workerman根目录中寻找
if(!is_file($class_file))
{
$class_file = WORKERMAN_ROOT_DIR . DIRECTORY_SEPARATOR . "$class_path.php";
$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))
{
// 加载
if (is_file($class_file)) {
require_once($class_file);
if(class_exists($name, false))
{
if (class_exists($name, false)) {
return true;
}
}
return false;
}
}
// 设置类自动加载回调函数
spl_autoload_register('\Workerman\Autoloader::loadByNamespace');

View File

@ -1,128 +1,274 @@
<?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\Libevent;
use Workerman\Events\Select;
use Workerman\Events\EventInterface;
use Workerman\Worker;
use \Exception;
use Exception;
/**
* 异步tcp连接类
* @author walkor<walkor@workerman.net>
* AsyncTcpConnection.
*/
class AsyncTcpConnection extends TcpConnection
{
/**
* 连接状态 连接中
* @var int
*/
protected $_status = self::STATUS_CONNECTING;
/**
* 当连接成功时,如果设置了连接成功回调,则执行
* Emitted when socket connection is successfully established.
*
* @var callback
*/
public $onConnect = null;
/**
* 构造函数,创建连接
* @param resource $socket
* @param EventInterface $event
* Transport layer protocol.
*
* @var string
*/
public $transport = 'tcp';
/**
* Status.
*
* @var int
*/
protected $_status = self::STATUS_INITIAL;
/**
* Remote host.
*
* @var string
*/
protected $_remoteHost = '';
/**
* Connect start time.
*
* @var string
*/
protected $_connectStartTime = 0;
/**
* Remote URI.
*
* @var string
*/
protected $_remoteURI = '';
/**
* 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
* @throws Exception
*/
public function __construct($remote_address)
{
// 获得协议及远程地址
list($scheme, $address) = explode(':', $remote_address, 2);
if($scheme != 'tcp')
{
// 判断协议类是否存在
$scheme = ucfirst($scheme);
$this->protocol = '\\Protocols\\'.$scheme;
if(!class_exists($this->protocol))
{
$this->protocol = '\\Workerman\\Protocols\\' . $scheme;
if(!class_exists($this->protocol))
{
$address_info = parse_url($remote_address);
if (!$address_info) {
echo new \Exception('bad remote_address');
$this->_remoteAddress = $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->_remoteURI = "{$address_info['path']}{$address_info['query']}";
$scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp';
}
$this->id = self::$_idRecorder++;
// 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];
}
// 创建异步连接
$this->_socket = stream_socket_client("tcp:$address", $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT);
// 如果失败尝试触发失败回调(如果有回调的话)
if(!$this->_socket)
{
$this->emitError(WORKERMAN_CONNECT_FAIL, $errstr);
// For statistics.
self::$statistics['connection_count']++;
$this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
}
/**
* 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);
// Open socket connection asynchronously.
$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) {
$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'));
}
/**
* 尝试触发失败回调
* @param int $code
* 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)
{
if($this->onError)
{
try{
$this->_status = self::STATUS_CLOSING;
if ($this->onError) {
try {
call_user_func($this->onError, $this, $code, $msg);
}
catch(Exception $e)
{
echo $e;
} 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($socket)
{
// 删除连接可写监听
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
// 需要判断两次连接是否已经断开
if(!feof($this->_socket) && !feof($this->_socket))
{
// 设置非阻塞
stream_set_blocking($this->_socket, 0);
// 监听可读事件
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
// 如果发送缓冲区有数据则执行发送
if($this->_sendBuffer)
{
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
// Check socket state.
if ($address = stream_socket_get_name($socket, true)) {
// Remove write listener.
Worker::$globalEvent->del($socket, EventInterface::EV_WRITE);
// Nonblocking.
stream_set_blocking($socket, 0);
// Compatible with hhvm
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($socket, 0);
}
// 标记状态为连接已经建立
$this->_status = self::STATUS_ESTABLISH;
// 为status 命令统计数据
ConnectionInterface::$statistics['connection_count']++;
// 如果有设置onConnect回调则执行
if($this->onConnect)
{
try
{
// Try to open keepalive for tcp and disable Nagle algorithm.
if (function_exists('socket_import_stream') && $this->transport === 'tcp') {
$raw_socket = socket_import_stream($socket);
socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1);
socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1);
}
// Register a listener waiting read event.
Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead'));
// There are some data waiting to send.
if ($this->_sendBuffer) {
Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
}
$this->_status = self::STATUS_ESTABLISH;
$this->_remoteAddress = $address;
// Try to emit onConnect callback.
if ($this->onConnect) {
try {
call_user_func($this->onConnect, $this);
}
catch(Exception $e)
{
self::$statistics['throw_exception']++;
echo $e;
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
}
else
{
// 连接未建立成功
$this->emitError(WORKERMAN_CONNECT_FAIL, 'connect fail');
// 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;
}
}
}
}

View File

@ -1,68 +1,83 @@
<?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\Libevent;
use Workerman\Events\Select;
use Workerman\Events\EventInterface;
use Workerman\Worker;
use \Exception;
/**
* connection类的接口
* @author walkor<walkor@workerman.net>
* ConnectionInterface.
*/
abstract class ConnectionInterface
{
/**
* status命令的统计数据
* Statistics for status command.
*
* @var array
*/
public static $statistics = array(
'connection_count'=>0,
'total_request' => 0,
'throw_exception' => 0,
'send_fail' => 0,
'connection_count' => 0,
'total_request' => 0,
'throw_exception' => 0,
'send_fail' => 0,
);
/**
* 当收到数据时,如果有设置$onMessage回调则执行
* Emitted when data is received.
*
* @var callback
*/
public $onMessage = null;
/**
* 当连接关闭时,如果设置了$onClose回调则执行
* Emitted when the other end of the socket sends a FIN packet.
*
* @var callback
*/
public $onClose = null;
/**
* 当出现错误时,如果设置了$onError回调则执行
* Emitted when an error occurs with connection.
*
* @var callback
*/
public $onError = null;
/**
* 发送数据给对端
* Sends data on the connection.
*
* @param string $send_buffer
* @return void|boolean
*/
abstract public function send($send_buffer);
/**
* 获得远端ip
* Get remote IP.
*
* @return string
*/
abstract public function getRemoteIp();
/**
* 获得远端端口
* Get remote port.
*
* @return int
*/
abstract public function getRemotePort();
/**
* 关闭连接为了保持接口一致udp保留了此方法当是udp时调用此方法无任何作用
* @void
* Close connection.
*
* @param $data
* @return void
*/
abstract public function close($data = null);
}

View File

@ -1,317 +1,344 @@
<?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\Libevent;
use Workerman\Events\Select;
use Workerman\Events\EventInterface;
use Workerman\Worker;
use \Exception;
use Exception;
/**
* Tcp连接类
* @author walkor<walkor@workerman.net>
* TcpConnection.
*/
class TcpConnection extends ConnectionInterface
{
/**
* 当数据可读时从socket缓冲区读取多少字节数据
* Read buffer size.
*
* @var int
*/
const READ_BUFFER_SIZE = 8192;
const READ_BUFFER_SIZE = 65535;
/**
* 连接状态 连接中
* Status initial.
*
* @var int
*/
const STATUS_INITIAL = 0;
/**
* Status connecting.
*
* @var int
*/
const STATUS_CONNECTING = 1;
/**
* 连接状态 已经建立连接
* Status connection established.
*
* @var int
*/
const STATUS_ESTABLISH = 2;
/**
* 连接状态 连接关闭中标识调用了close方法但是发送缓冲区中任然有数据
* 等待发送缓冲区的数据发送完毕写入到socket写缓冲区后执行关闭
* Status closing.
*
* @var int
*/
const STATUS_CLOSING = 4;
/**
* 连接状态 已经关闭
* Status closed.
*
* @var int
*/
const STATUS_CLOSED = 8;
/**
* 当对端发来数据时,如果设置了$onMessage回调则执行
* Emitted when data is received.
*
* @var callback
*/
public $onMessage = null;
/**
* 当连接关闭时,如果设置了$onClose回调则执行
* Emitted when the other end of the socket sends a FIN packet.
*
* @var callback
*/
public $onClose = null;
/**
* 当出现错误是,如果设置了$onError回调则执行
* Emitted when an error occurs with connection.
*
* @var callback
*/
public $onError = null;
/**
* 当发送缓冲区满时,如果设置了$onBufferFull回调则执行
* Emitted when the send buffer becomes full.
*
* @var callback
*/
public $onBufferFull = null;
/**
* 当发送缓冲区被清空时,如果设置了$onBufferDrain回调则执行
* Emitted when the send buffer becomes empty.
*
* @var callback
*/
public $onBufferDrain = null;
/**
* 使用的应用层协议,是协议类的名称
* 值类似于 Workerman\\Protocols\\Http
* @var string
* Application layer protocol.
* The format is like this Workerman\\Protocols\\Http.
*
* @var \Workerman\Protocols\ProtocolInterface
*/
public $protocol = '';
public $protocol = null;
/**
* 属于哪个worker
* Which worker belong to.
*
* @var Worker
*/
public $worker = null;
/**
* 发送缓冲区大小当发送缓冲区满时会尝试触发onBufferFull回调如果有设置的话
* 如果没设置onBufferFull回调由于发送缓冲区满则后续发送的数据将被丢弃
* 直到发送缓冲区有空的位置
* 注意 此值可以动态设置
* 例如 Workerman\Connection\TcpConnection::$maxSendBufferSize=1024000;
* Connection->id.
*
* @var int
*/
public static $maxSendBufferSize = 1048576;
public $id = 0;
/**
* 能接受的最大数据包,为了防止恶意攻击,当数据包的大小大于此值时执行断开
* 注意 此值可以动态设置
* 例如 Workerman\Connection\TcpConnection::$maxPackageSize=1024000;
* A copy of $worker->id which used to clean up the connection in worker->connections
*
* @var int
*/
protected $_id = 0;
/**
* Sets the maximum send buffer size for the current connection.
* OnBufferFull callback will be emited When the send buffer is full.
*
* @var int
*/
public $maxSendBufferSize = 1048576;
/**
* Default send buffer size.
*
* @var int
*/
public static $defaultMaxSendBufferSize = 1048576;
/**
* Maximum acceptable packet size.
*
* @var int
*/
public static $maxPackageSize = 10485760;
/**
* 实际的socket资源
* Id recorder.
*
* @var int
*/
protected static $_idRecorder = 1;
/**
* Socket
*
* @var resource
*/
protected $_socket = null;
/**
* 发送缓冲区
* Send buffer.
*
* @var string
*/
protected $_sendBuffer = '';
/**
* 接收缓冲区
* Receive buffer.
*
* @var string
*/
protected $_recvBuffer = '';
/**
* 当前正在处理的数据包的包长此值是协议的intput方法的返回值
* Current package length.
*
* @var int
*/
protected $_currentPackageLength = 0;
/**
* 当前的连接状态
* Connection status.
*
* @var int
*/
protected $_status = self::STATUS_ESTABLISH;
/**
* 对端ip
* @var string
*/
protected $_remoteIp = '';
/**
* 对端端口
* @var int
*/
protected $_remotePort = 0;
/**
* 对端的地址 ip+port
* 值类似于 192.168.1.100:3698
* Remote address.
*
* @var string
*/
protected $_remoteAddress = '';
/**
* 是否是停止接收数据
* Is paused.
*
* @var bool
*/
protected $_isPaused = false;
/**
* 构造函数
* Construct.
*
* @param resource $socket
* @param EventInterface $event
* @param string $remote_address
*/
public function __construct($socket)
public function __construct($socket, $remote_address = '')
{
self::$statistics['connection_count']++;
$this->id = $this->_id = self::$_idRecorder++;
$this->_socket = $socket;
stream_set_blocking($this->_socket, 0);
// Compatible with hhvm
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($this->_socket, 0);
}
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
$this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
$this->_remoteAddress = $remote_address;
}
/**
* 发送数据给对端
* Sends data on the connection.
*
* @param string $send_buffer
* @param bool $raw
* @return void|boolean
* @param bool $raw
* @return void|bool|null
*/
public function send($send_buffer, $raw = false)
{
// 如果当前状态是连接中,则把数据放入发送缓冲区
if($this->_status === self::STATUS_CONNECTING)
{
// Try to call protocol::encode($send_buffer) before sending.
if (false === $raw && $this->protocol) {
$parser = $this->protocol;
$send_buffer = $parser::encode($send_buffer, $this);
if ($send_buffer === '') {
return null;
}
}
if ($this->_status === self::STATUS_INITIAL || $this->_status === self::STATUS_CONNECTING) {
$this->_sendBuffer .= $send_buffer;
return null;
}
// 如果当前连接是关闭则返回false
elseif($this->_status == self::STATUS_CLOSED)
{
} elseif ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) {
return false;
}
// 如果没有设置以原始数据发送,并且有设置协议则按照协议编码
if(false === $raw && $this->protocol)
{
$parser = $this->protocol;
$send_buffer = $parser::encode($send_buffer, $this);
}
// 如果发送缓冲区为空,尝试直接发送
if($this->_sendBuffer === '')
{
// 直接发送
// Attempt to send data directly.
if ($this->_sendBuffer === '') {
$len = @fwrite($this->_socket, $send_buffer);
// 所有数据都发送完毕
if($len === strlen($send_buffer))
{
// send successful.
if ($len === strlen($send_buffer)) {
return true;
}
// 只有部分数据发送成功
if($len > 0)
{
// 未发送成功部分放入发送缓冲区
// Send only part of the data.
if ($len > 0) {
$this->_sendBuffer = substr($send_buffer, $len);
}
else
{
// 如果连接断开
if(feof($this->_socket))
{
// status统计发送失败次数
} else {
// Connection closed?
if (!is_resource($this->_socket) || feof($this->_socket)) {
self::$statistics['send_fail']++;
// 如果有设置失败回调,则执行
if($this->onError)
{
try
{
if ($this->onError) {
try {
call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed');
}
catch(Exception $e)
{
echo $e;
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
// 销毁连接
$this->destroy();
return false;
}
// 连接未断开,发送失败,则把所有数据放入发送缓冲区
$this->_sendBuffer = $send_buffer;
}
// 监听对端可写事件
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
// 检查发送缓冲区是否已满如果满了尝试触发onBufferFull回调
// Check if the send buffer is full.
$this->checkBufferIsFull();
return null;
}
else
{
// 缓冲区已经标记为满,仍然然有数据发送,则丢弃数据包
if(self::$maxSendBufferSize <= strlen($this->_sendBuffer))
{
// 为status命令统计发送失败次数
} else {
// Buffer has been marked as full but still has data to send the packet is discarded.
if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) {
self::$statistics['send_fail']++;
// 如果有设置失败回调,则执行
if($this->onError)
{
try
{
if ($this->onError) {
try {
call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
}
catch(Exception $e)
{
echo $e;
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
return false;
}
// 将数据放入放缓冲区
$this->_sendBuffer .= $send_buffer;
// 检查发送缓冲区是否已满如果满了尝试触发onBufferFull回调
// Check if the send buffer is full.
$this->checkBufferIsFull();
}
}
/**
* 获得对端ip
* Get remote IP.
*
* @return string
*/
public function getRemoteIp()
{
if(!$this->_remoteIp)
{
$this->_remoteAddress = stream_socket_get_name($this->_socket, true);
if($this->_remoteAddress)
{
list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
$this->_remotePort = (int)$this->_remotePort;
}
$pos = strrpos($this->_remoteAddress, ':');
if ($pos) {
return trim(substr($this->_remoteAddress, 0, $pos), '[]');
}
return $this->_remoteIp;
return '';
}
/**
* 获得对端端口
* Get remote port.
*
* @return int
*/
public function getRemotePort()
{
if(!$this->_remotePort)
{
$this->_remoteAddress = stream_socket_get_name($this->_socket, true);
if($this->_remoteAddress)
{
list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
$this->_remotePort = (int)$this->_remotePort;
}
if ($this->_remoteAddress) {
return (int)substr(strrchr($this->_remoteAddress, ':'), 1);
}
return $this->_remotePort;
return 0;
}
/**
* 暂停接收数据,一般用于控制上传流量
* Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload.
*
* @return void
*/
public function pauseRecv()
@ -321,176 +348,185 @@ class TcpConnection extends ConnectionInterface
}
/**
* 恢复接收数据,一般用户控制上传流量
* Resumes reading after a call to pauseRecv.
*
* @return void
*/
public function resumeRecv()
{
if($this->_isPaused == true)
{
if ($this->_isPaused === true) {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
$this->_isPaused = false;
$this->baseRead($this->_socket);
$this->baseRead($this->_socket, false);
}
}
/**
* 当socket可读时的回调
* Base read handler.
*
* @param resource $socket
* @param bool $check_eof
* @return void
*/
public function baseRead($socket)
public function baseRead($socket, $check_eof = true)
{
while($buffer = fread($socket, self::READ_BUFFER_SIZE))
{
$this->_recvBuffer .= $buffer;
}
$buffer = fread($socket, self::READ_BUFFER_SIZE);
if($this->_recvBuffer)
{
if(!$this->onMessage)
{
return ;
}
// Check connection closed.
if ($buffer === '' || $buffer === false) {
if ($check_eof && (feof($socket) || !is_resource($socket) || $buffer === false)) {
$this->destroy();
return;
}
} else {
$this->_recvBuffer .= $buffer;
}
// 如果设置了协议
if($this->protocol)
{
$parser = $this->protocol;
while($this->_recvBuffer && !$this->_isPaused)
{
// 当前包的长度已知
if($this->_currentPackageLength)
{
// 数据不够一个包
if($this->_currentPackageLength > strlen($this->_recvBuffer))
{
break;
}
}
else
{
// 获得当前包长
$this->_currentPackageLength = $parser::input($this->_recvBuffer, $this);
// 数据不够,无法获得包长
if($this->_currentPackageLength === 0)
{
break;
}
elseif($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize)
{
// 数据不够一个包
if($this->_currentPackageLength > strlen($this->_recvBuffer))
{
break;
}
}
// 包错误
else
{
$this->close('error package. package_length='.var_export($this->_currentPackageLength, true));
}
}
// If the application layer protocol has been set up.
if ($this->protocol) {
$parser = $this->protocol;
while ($this->_recvBuffer !== '' && !$this->_isPaused) {
// The current packet length is known.
if ($this->_currentPackageLength) {
// Data is not enough for a package.
if ($this->_currentPackageLength > strlen($this->_recvBuffer)) {
break;
}
} else {
// Get current package length.
$this->_currentPackageLength = $parser::input($this->_recvBuffer, $this);
// The packet length is unknown.
if ($this->_currentPackageLength === 0) {
break;
} elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize) {
// Data is not enough for a package.
if ($this->_currentPackageLength > strlen($this->_recvBuffer)) {
break;
}
} // Wrong package.
else {
echo 'error package. package_length=' . var_export($this->_currentPackageLength, true);
$this->destroy();
return;
}
}
// 数据足够一个包长
self::$statistics['total_request']++;
// 从缓冲区中获取一个完整的包
$one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength);
// 将当前包从接受缓冲区中去掉
$this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength);
// 重置当前包长为0
$this->_currentPackageLength = 0;
// 处理数据包
try
{
call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this));
}
catch(Exception $e)
{
self::$statistics['throw_exception']++;
echo $e;
}
}
if($this->_status !== self::STATUS_CLOSED && feof($socket))
{
$this->destroy();
}
return;
}
// 没有设置协议,则直接把接收的数据当做一个包处理
self::$statistics['total_request']++;
try
{
call_user_func($this->onMessage, $this, $this->_recvBuffer);
}
catch(Exception $e)
{
self::$statistics['throw_exception']++;
echo $e;
}
// 清空缓冲区
$this->_recvBuffer = '';
// 判断连接是否已经断开
if($this->_status !== self::STATUS_CLOSED && feof($socket))
{
$this->destroy();
return;
}
}
// 没收到数据,判断连接是否已经断开
else if(feof($socket))
{
$this->destroy();
return;
}
// The data is enough for a packet.
self::$statistics['total_request']++;
// The current packet length is equal to the length of the buffer.
if (strlen($this->_recvBuffer) === $this->_currentPackageLength) {
$one_request_buffer = $this->_recvBuffer;
$this->_recvBuffer = '';
} else {
// Get a full package from the buffer.
$one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength);
// Remove the current package from the receive buffer.
$this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength);
}
// Reset the current packet length to 0.
$this->_currentPackageLength = 0;
if (!$this->onMessage) {
continue;
}
try {
// Decode request buffer before Emitting onMessage callback.
call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this));
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
return;
}
if ($this->_recvBuffer === '' || $this->_isPaused) {
return;
}
// Applications protocol is not set.
self::$statistics['total_request']++;
if (!$this->onMessage) {
$this->_recvBuffer = '';
return;
}
try {
call_user_func($this->onMessage, $this, $this->_recvBuffer);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
// Clean receive buffer.
$this->_recvBuffer = '';
}
/**
* socket可写时的回调
* @return void
* Base write handler.
*
* @return void|bool
*/
public function baseWrite()
{
$len = @fwrite($this->_socket, $this->_sendBuffer);
if($len === strlen($this->_sendBuffer))
{
if ($len === strlen($this->_sendBuffer)) {
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
$this->_sendBuffer = '';
// 发送缓冲区的数据被发送完毕尝试触发onBufferDrain回调
if($this->onBufferDrain)
{
try
{
// Try to emit onBufferDrain callback when the send buffer becomes empty.
if ($this->onBufferDrain) {
try {
call_user_func($this->onBufferDrain, $this);
}
catch(Exception $e)
{
echo $e;
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
// 如果连接状态为关闭,则销毁连接
if($this->_status == self::STATUS_CLOSING)
{
if ($this->_status === self::STATUS_CLOSING) {
$this->destroy();
}
return true;
}
if($len > 0)
{
$this->_sendBuffer = substr($this->_sendBuffer, $len);
}
else
{
if(feof($this->_socket))
{
self::$statistics['send_fail']++;
$this->destroy();
}
if ($len > 0) {
$this->_sendBuffer = substr($this->_sendBuffer, $len);
} else {
self::$statistics['send_fail']++;
$this->destroy();
}
}
/**
* 从缓冲区中消费掉$length长度的数据
* This method pulls all the data out of a readable stream, and writes it to the supplied destination.
*
* @param TcpConnection $dest
* @return void
*/
public function pipe($dest)
{
$source = $this;
$this->onMessage = function ($source, $data) use ($dest) {
$dest->send($data);
};
$this->onClose = function ($source) use ($dest) {
$dest->destroy();
};
$dest->onBufferFull = function ($dest) use ($source) {
$source->pauseRecv();
};
$dest->onBufferDrain = function ($dest) use ($source) {
$source->resumeRecv();
};
}
/**
* Remove $length of data from receive buffer.
*
* @param int $length
* @return void
*/
@ -500,32 +536,30 @@ class TcpConnection extends ConnectionInterface
}
/**
* 关闭连接
* Close connection.
*
* @param mixed $data
* @void
* @param bool $raw
* @return void
*/
public function close($data = null)
public function close($data = null, $raw = false)
{
if($this->_status == self::STATUS_CLOSING)
{
return false;
}
else
{
if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) {
return;
} else {
if ($data !== null) {
$this->send($data, $raw);
}
$this->_status = self::STATUS_CLOSING;
}
if($data !== null)
{
$this->send($data);
}
if($this->_sendBuffer === '')
{
$this->destroy();
if ($this->_sendBuffer === '') {
$this->destroy();
}
}
/**
* 获得socket连接
* Get the real socket.
*
* @return resource
*/
public function getSocket()
@ -534,52 +568,85 @@ class TcpConnection extends ConnectionInterface
}
/**
* 检查发送缓冲区是否已满如果满了尝试触发onBufferFull回调
* Check whether the send buffer is full.
*
* @return void
*/
protected function checkBufferIsFull()
{
if(self::$maxSendBufferSize <= strlen($this->_sendBuffer))
{
if($this->onBufferFull)
{
try
{
if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) {
if ($this->onBufferFull) {
try {
call_user_func($this->onBufferFull, $this);
}
catch(Exception $e)
{
echo $e;
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
}
}
/**
* 销毁连接
* @void
* Destroy connection.
*
* @return void
*/
protected function destroy()
public function destroy()
{
self::$statistics['connection_count']--;
if($this->onClose)
{
try
{
call_user_func($this->onClose, $this);
}
catch (Exception $e)
{
self::$statistics['throw_exception']++;
echo $e;
}
}
if($this->worker)
{
unset($this->worker->connections[(int)$this->_socket]);
}
Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ);
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
@fclose($this->_socket);
$this->_status = self::STATUS_CLOSED;
// Avoid repeated calls.
if ($this->_status === self::STATUS_CLOSED) {
return;
}
// Remove event listener.
Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ);
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
// Close socket.
@fclose($this->_socket);
// Remove from worker->connections.
if ($this->worker) {
unset($this->worker->connections[$this->_id]);
}
$this->_status = self::STATUS_CLOSED;
// 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);
}
}
// Try to emit protocol::onClose
if (method_exists($this->protocol, 'onClose')) {
try {
call_user_func(array($this->protocol, 'onClose'), $this);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
if ($this->_status === self::STATUS_CLOSED) {
// Cleaning up the callback to avoid memory leaks.
$this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null;
}
}
/**
* Destruct.
*
* @return void
*/
public function __destruct()
{
self::$statistics['connection_count']--;
}
}

View File

@ -1,105 +1,114 @@
<?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\Libevent;
use Workerman\Events\Select;
use Workerman\Events\EventInterface;
use Workerman\Worker;
use \Exception;
/**
* udp连接类udp实际上是无连接的这里是为了保持与TCP接口一致
* @author walkor<walkor@workerman.net>
* UdpConnection.
*/
class UdpConnection extends ConnectionInterface
{
/**
* 应用层协议
* 值类似于 Workerman\\Protocols\\Http
* @var string
* Application layer protocol.
* The format is like this Workerman\\Protocols\\Http.
*
* @var \Workerman\Protocols\ProtocolInterface
*/
public $protocol = '';
public $protocol = null;
/**
* udp socket 资源
* Udp socket.
*
* @var resource
*/
protected $_socket = null;
/**
* 对端 ip
* @var string
*/
protected $_remoteIp = '';
/**
* 对端 端口
* @var int
*/
protected $_remotePort = 0;
/**
* 对端 地址
* 值类似于 192.168.10.100:3698
* Remote address.
*
* @var string
*/
protected $_remoteAddress = '';
/**
* 构造函数
* Construct.
*
* @param resource $socket
* @param string $remote_address
* @param string $remote_address
*/
public function __construct($socket, $remote_address)
{
$this->_socket = $socket;
$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)
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);
}
/**
* 获得对端 ip
* Get remote IP.
*
* @return string
*/
public function getRemoteIp()
{
if(!$this->_remoteIp)
{
list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
$pos = strrpos($this->_remoteAddress, ':');
if ($pos) {
return trim(substr($this->_remoteAddress, 0, $pos), '[]');
}
return $this->_remoteIp;
return '';
}
/**
* 获得对端端口
* Get remote port.
*
* @return int
*/
public function getRemotePort()
{
if(!$this->_remotePort)
{
list($this->_remoteIp, $this->_remotePort) = explode(':', $this->_remoteAddress, 2);
if ($this->_remoteAddress) {
return (int)substr(strrchr($this->_remoteAddress, ':'), 1);
}
return $this->_remotePort;
return 0;
}
/**
* 关闭连接此处为了保持与TCP接口一致提供了close方法
* @void
* Close connection.
*
* @param mixed $data
* @param bool $raw
* @return bool
*/
public function close($data = null)
public function close($data = null, $raw = false)
{
if($data !== null)
{
$this->send($data);
if ($data !== null) {
$this->send($data, $raw);
}
return true;
}

173
Workerman/Events/Ev.php Normal file
View File

@ -0,0 +1,173 @@
<?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->_allEvents[$fd_key][$flag]->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();
}
}

188
Workerman/Events/Event.php Normal file
View File

@ -0,0 +1,188 @@
<?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()
{
$this->_eventBase = new \EventBase();
}
/**
* @see EventInterface::add()
*/
public function add($fd, $flag, $func, $args=array())
{
switch ($flag) {
case self::EV_SIGNAL:
$fd_key = (int)$fd;
$event = \Event::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 \Event($this->_eventBase, -1, \Event::TIMEOUT|\Event::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 ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST;
$event = new \Event($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->_allEvents[$fd_key][$flag]->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();
}
}

View File

@ -1,63 +1,85 @@
<?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;
/**
* 信号事件
* 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;
/**
* 添加事件回调
* @param resource $fd
* @param int $flag
* 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);
/**
* 删除事件回调
* @param resource $fd
* @param int $flag
* 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();

View File

@ -1,39 +1,56 @@
<?php
namespace Workerman\Events;
/**
* libevent
* @author walkor <walkor@workerman.net>
* 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
{
/**
* eventBase
* @var object
* 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();
/**
* 构造函数
* @return void
* construct
*/
public function __construct()
{
@ -41,70 +58,59 @@ class Libevent implements EventInterface
}
/**
* 添加事件
* @see EventInterface::add()
* {@inheritdoc}
*/
public function add($fd, $flag, $func, $args=null)
public function add($fd, $flag, $func, $args = array())
{
switch($flag)
{
switch ($flag) {
case self::EV_SIGNAL:
$fd_key = (int)$fd;
$real_flag = EV_SIGNAL | EV_PERSIST;
$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))
{
if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) {
return false;
}
if(!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase))
{
if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) {
return false;
}
if(!event_add($this->_eventSignal[$fd_key]))
{
if (!event_add($this->_eventSignal[$fd_key])) {
return false;
}
return true;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$event = event_new();
$event = event_new();
$timer_id = (int)$event;
if(!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id))
{
if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) {
return false;
}
if(!event_base_set($event, $this->_eventBase))
{
if (!event_base_set($event, $this->_eventBase)) {
return false;
}
$time_interval = $fd*1000000;
if(!event_add($event, $time_interval))
{
$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;
$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))
{
if (!event_set($event, $fd, $real_flag, $func, null)) {
return false;
}
if(!event_base_set($event, $this->_eventBase))
{
if (!event_base_set($event, $this->_eventBase)) {
return false;
}
if(!event_add($event))
{
if (!event_add($event)) {
return false;
}
@ -116,30 +122,25 @@ class Libevent implements EventInterface
}
/**
* 删除事件
* @see Events\EventInterface::del()
* {@inheritdoc}
*/
public function del($fd ,$flag)
public function del($fd, $flag)
{
switch($flag)
{
switch ($flag) {
case self::EV_READ:
case self::EV_WRITE:
$fd_key = (int)$fd;
if(isset($this->_allEvents[$fd_key][$flag]))
{
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]))
{
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]))
{
if (isset($this->_eventSignal[$fd_key])) {
event_del($this->_eventSignal[$fd_key]);
unset($this->_eventSignal[$fd_key]);
}
@ -147,8 +148,7 @@ class Libevent implements EventInterface
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
// 这里 fd 为timerid
if(isset($this->_eventTimer[$fd]))
{
if (isset($this->_eventTimer[$fd])) {
event_del($this->_eventTimer[$fd][2]);
unset($this->_eventTimer[$fd]);
}
@ -158,46 +158,44 @@ class Libevent implements EventInterface
}
/**
* 定时器回调
* @param null $_null
* @param null $_null
* @param int $timer_id
* Timer callback.
*
* @param mixed $_null1
* @param int $_null2
* @param mixed $timer_id
*/
protected function timerCallback($_null, $_null, $timer_id)
protected function timerCallback($_null1, $_null2, $timer_id)
{
// 如果是连续的定时任务,再把任务加进去
if($this->_eventTimer[$timer_id][3] == self::EV_TIMER)
{
if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) {
event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]);
}
try
{
// 执行任务
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);
}
catch(\Exception $e)
{
echo $e;
if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) {
$this->del($timer_id, self::EV_TIMER_ONCE);
}
}
/**
* 删除所有定时器
* @return void
* {@inheritdoc}
*/
public function clearAllTimer()
{
foreach($this->_eventTimer as $task_data)
{
foreach ($this->_eventTimer as $task_data) {
event_del($task_data[2]);
}
$this->_eventTimer = array();
}
/**
* 事件循环
* @see EventInterface::loop()
* {@inheritdoc}
*/
public function loop()
{

View File

@ -1,105 +1,130 @@
<?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();
/**
* 任务调度器,最大堆
* Timer scheduler.
* {['data':timer_id, 'priority':run_timestamp], ..}
* @var SplPriorityQueue
*
* @var \SplPriorityQueue
*/
protected $_scheduler = null;
/**
* 定时任务
* All timer event listeners.
* [[func, args, flag, timer_interval], ..]
*
* @var array
*/
protected $_task = array();
/**
* 定时器id
* Timer id.
*
* @var int
*/
protected $_timerId = 1;
/**
* select超时时间单位微妙
* Select timeout.
*
* @var int
*/
protected $_selectTimeout = 100000000;
/**
* 构造函数
* @return void
* 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(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
if($this->channel)
{
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);
}
/**
* 添加事件及处理函数
* @see Events\EventInterface::add()
* {@inheritdoc}
*/
public function add($fd, $flag, $func, $args = null)
public function add($fd, $flag, $func, $args = array())
{
switch ($flag)
{
switch ($flag) {
case self::EV_READ:
$fd_key = (int)$fd;
$fd_key = (int)$fd;
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
$this->_readFds[$fd_key] = $fd;
$this->_readFds[$fd_key] = $fd;
break;
case self::EV_WRITE:
$fd_key = (int)$fd;
$fd_key = (int)$fd;
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
$this->_writeFds[$fd_key] = $fd;
$this->_writeFds[$fd_key] = $fd;
break;
case self::EV_SIGNAL:
$fd_key = (int)$fd;
$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:
// $fd 为 定时的时间间隔单位为秒支持小数能精确到0.001秒
$run_time = microtime(true)+$fd;
$run_time = microtime(true) + $fd;
$this->_scheduler->insert($this->_timerId, -$run_time);
$this->_task[$this->_timerId] = array($func, $args, $flag, $fd);
$this->_task[$this->_timerId] = array($func, (array)$args, $flag, $fd);
$this->tick();
return $this->_timerId++;
}
@ -108,7 +133,8 @@ class Select implements EventInterface
}
/**
* 信号处理函数
* Signal handler.
*
* @param int $signal
*/
public function signalHandler($signal)
@ -117,25 +143,21 @@ class Select implements EventInterface
}
/**
* 删除某个描述符的某类事件的监听
* @see Events\EventInterface::del()
* {@inheritdoc}
*/
public function del($fd ,$flag)
public function del($fd, $flag)
{
$fd_key = (int)$fd;
switch ($flag)
{
switch ($flag) {
case self::EV_READ:
unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]);
if(empty($this->_allEvents[$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]))
{
if (empty($this->_allEvents[$fd_key])) {
unset($this->_allEvents[$fd_key]);
}
return true;
@ -145,67 +167,51 @@ class Select implements EventInterface
break;
case self::EV_TIMER:
case self::EV_TIMER_ONCE;
// $fd_key为要删除的定时器id即timerId
unset($this->_task[$fd_key]);
return true;
}
return false;;
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);
if($time_now >= $next_run_time)
{
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->_task[$timer_id]))
{
if (!isset($this->_task[$timer_id])) {
continue;
}
// 任务数据[func, args, flag, timer_interval]
// [func, args, flag, timer_interval]
$task_data = $this->_task[$timer_id];
// 如果是持续的定时任务,再把任务加到定时队列
if($task_data[2] == self::EV_TIMER)
{
$next_run_time = $time_now+$task_data[3];
if ($task_data[2] === self::EV_TIMER) {
$next_run_time = $time_now + $task_data[3];
$this->_scheduler->insert($timer_id, -$next_run_time);
}
// 尝试执行任务
try
{
call_user_func_array($task_data[0], $task_data[1]);
}
catch(\Exception $e)
{
echo $e;
call_user_func_array($task_data[0], $task_data[1]);
if (isset($this->_task[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
$this->del($timer_id, self::EV_TIMER_ONCE);
}
continue;
}
else
{
// 设定超时时间
$this->_selectTimeout = ($next_run_time - $time_now)*1000000;
return;
}
return;
}
$this->_selectTimeout = 100000000;
}
/**
* 删除所有定时器
* @return void
* {@inheritdoc}
*/
public function clearAllTimer()
{
@ -215,53 +221,43 @@ class Select implements EventInterface
}
/**
* 主循环
* @see Events\EventInterface::loop()
* {@inheritdoc}
*/
public function loop()
{
$e = null;
while (1)
{
// 如果有信号,尝试执行信号处理函数
while (1) {
// Calls signal handlers for pending signals
pcntl_signal_dispatch();
$read = $this->_readFds;
$read = $this->_readFds;
$write = $this->_writeFds;
// 等待可读或者可写事件
@stream_select($read, $write, $e, 0, $this->_selectTimeout);
// Waiting read/write/signal/timeout events.
$ret = @stream_select($read, $write, $e, 0, $this->_selectTimeout);
// 这些描述符可读,执行对应描述符的读回调函数
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(!$this->_scheduler->isEmpty())
{
if (!$this->_scheduler->isEmpty()) {
$this->tick();
}
if (!$ret) {
continue;
}
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]));
}
}
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]));
}
}
}
}
}

View File

@ -1,13 +1,35 @@
<?php
// 如果ini没设置时区则设置一个默认的
if(!ini_get('date.timezone') )
{
/**
* 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
*/
// Date.timezone
if (!ini_get('date.timezone')) {
date_default_timezone_set('Asia/Shanghai');
}
// 显示错误到终端
// Display errors.
ini_set('display_errors', 'on');
// Reporting all.
error_reporting(E_ALL);
// 连接失败
// For onError callback.
define('WORKERMAN_CONNECT_FAIL', 1);
// 发送失败
// For onError callback.
define('WORKERMAN_SEND_FAIL', 2);
// Compatible with php7
if(!class_exists('Error'))
{
class Error extends Exception
{
}
}

View File

@ -1,146 +1,142 @@
<?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 \Exception;
use Workerman\Events\EventInterface;
use Exception;
/**
* Timer.
*
* 定时器
*
* <b>example:</b>
* <pre>
* <code>
* Workerman\Lib\Timer::init();
* example:
* Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..));
* <code>
* </pre>
* @author walkor <walkor@workerman.net>
*/
class Timer
{
/**
* 基于ALARM信号的任务
* 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 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)
{
if ($event) {
self::$_event = $event;
}
else
{
} else {
pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false);
}
}
/**
* 信号处理函数只处理ALARM事件
* ALARM signal handler.
*
* @return void
*/
public static function signalHandle()
{
if(!self::$_event)
{
if (!self::$_event) {
pcntl_alarm(1);
self::tick();
}
}
/**
* 添加一个定时器
* @param int $time_interval
* Add a timer.
*
* @param int $time_interval
* @param callback $func
* @param mix $args
* @return void
* @param mixed $args
* @param bool $persistent
* @return bool
*/
public static function add($time_interval, $func, $args = array(), $persistent = true)
{
if($time_interval <= 0)
{
if ($time_interval <= 0) {
echo 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 (self::$_event) {
return self::$_event->add($time_interval,
$persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args);
}
if(!is_callable($func))
{
if (!is_callable($func)) {
echo new Exception("not callable");
return false;
}
if(empty(self::$_tasks))
{
if (empty(self::$_tasks)) {
pcntl_alarm(1);
}
$time_now = time();
$run_time = $time_now + $time_interval;
if(!isset(self::$_tasks[$run_time]))
{
if (!isset(self::$_tasks[$run_time])) {
self::$_tasks[$run_time] = array();
}
self::$_tasks[$run_time][] = array($func, $args, $persistent, $time_interval);
self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval);
return true;
}
/**
* 尝试触发定时回调
* Tick.
*
* @return void
*/
public static function tick()
{
if(empty(self::$_tasks))
{
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];
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
{
try {
call_user_func_array($task_func, $task_args);
}
catch(\Exception $e)
{
} catch (\Exception $e) {
echo $e;
}
if($persistent)
{
if ($persistent) {
self::add($time_interval, $task_func, $task_args);
}
}
@ -150,26 +146,30 @@ class Timer
}
/**
* 删除定时器
* @param $timer_id
* Remove a timer.
*
* @param mixed $timer_id
* @return bool
*/
public static function del($timer_id)
{
if(self::$_event)
{
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)
{
if (self::$_event) {
self::$_event->clearAllTimer();
}
}

21
Workerman/MIT-LICENSE.txt Normal file
View File

@ -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.

View File

@ -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;
}
}

View File

@ -1,151 +0,0 @@
<?php
namespace Workerman\Protocols;
/**
* Gateway与Worker间通讯的二进制协议
*
* struct GatewayProtocol
* {
* unsigned int pack_len,
* unsigned char cmd,//命令字
* unsigned int local_ip,
* unsigned short local_port,
* unsigned int client_ip,
* unsigned short client_port,
* unsigned int client_id,
* unsigned char flag,
* unsigned int ext_len,
* char[ext_len] ext_data,
* char[pack_length-HEAD_LEN] body//包体
* }
*
*
* @author walkor <walkor@workerman.net>
*/
class GatewayProtocol
{
// 发给workergateway有一个新的连接
const CMD_ON_CONNECTION = 1;
// 发给worker的客户端有消息
const CMD_ON_MESSAGE = 3;
// 发给worker上的关闭链接事件
const CMD_ON_CLOSE = 4;
// 发给gateway的向单个用户发送数据
const CMD_SEND_TO_ONE = 5;
// 发给gateway的向所有用户发送数据
const CMD_SEND_TO_ALL = 6;
// 发给gateway的踢出用户
const CMD_KICK = 7;
// 发给gateway通知用户session更改
const CMD_UPDATE_SESSION = 9;
// 获取在线状态
const CMD_GET_ONLINE_STATUS = 10;
// 判断是否在线
const CMD_IS_ONLINE = 11;
// 包体是标量
const FLAG_BODY_IS_SCALAR = 0x01;
/**
* 包头长度
* @var integer
*/
const HEAD_LEN = 26;
public static $empty = array(
'cmd' => 0,
'local_ip' => '0.0.0.0',
'local_port' => 0,
'client_ip' => '0.0.0.0',
'client_port' => 0,
'client_id' => 0,
'flag' => 0,
'ext_data' => '',
'body' => '',
);
/**
* 返回包长度
* @param string $buffer
* @return int return current package length
*/
public static function input($buffer)
{
if(strlen($buffer) < self::HEAD_LEN)
{
return 0;
}
$data = unpack("Npack_len", $buffer);
return $data['pack_len'];
}
/**
* 获取整个包的buffer
* @param array $data
* @return string
*/
public static function encode($data)
{
$flag = (int)is_scalar($data['body']);
if(!$flag)
{
$data['body'] = serialize($data['body']);
}
$ext_len = strlen($data['ext_data']);
$package_len = self::HEAD_LEN + $ext_len + strlen($data['body']);
return pack("NCNnNnNNC", $package_len,
$data['cmd'], ip2long($data['local_ip']),
$data['local_port'], ip2long($data['client_ip']),
$data['client_port'], $data['client_id'],
$ext_len, $flag) . $data['ext_data'] . $data['body'];
}
/**
* 从二进制数据转换为数组
* @param string $buffer
* @return array
*/
public static function decode($buffer)
{
$data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nclient_id/Next_len/Cflag", $buffer);
$data['local_ip'] = long2ip($data['local_ip']);
$data['client_ip'] = long2ip($data['client_ip']);
if($data['ext_len'] > 0)
{
$data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']);
if($data['flag'] & self::FLAG_BODY_IS_SCALAR)
{
$data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']);
}
else
{
$data['body'] = unserialize(substr($buffer, self::HEAD_LEN + $data['ext_len']));
}
}
else
{
$data['ext_data'] = '';
if($data['flag'] & self::FLAG_BODY_IS_SCALAR)
{
$data['body'] = substr($buffer, self::HEAD_LEN);
}
else
{
$data['body'] = unserialize(substr($buffer, self::HEAD_LEN));
}
}
return $data;
}
}

View File

@ -1,173 +1,147 @@
<?php
namespace Workerman\Protocols;
/**
* 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
* @author walkor<walkor@workerman.net>
*/
class Http
{
/**
* 判断包长
* @param string $recv_buffer
* 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"))
{
// 无法获得包长,避免客户端传递超大头部的数据包
if(strlen($recv_buffer)>=TcpConnection::$maxPackageSize)
{
if (!strpos($recv_buffer, "\r\n\r\n")) {
// Judge whether the package length exceeds the limit.
if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) {
$connection->close();
return 0;
}
return 0;
}
list($header, $body) = explode("\r\n\r\n", $recv_buffer, 2);
if(0 === strpos($recv_buffer, "POST"))
{
list($header,) = explode("\r\n\r\n", $recv_buffer, 2);
if (0 === strpos($recv_buffer, "POST")) {
// find Content-Length
$match = array();
if(preg_match("/\r\nContent-Length: ?(\d+)/", $header, $match))
{
$content_lenght = $match[1];
return $content_lenght + strlen($header) + 4;
}
else
{
if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) {
$content_length = $match[1];
return $content_length + strlen($header) + 4;
} else {
return 0;
}
}
else
{
return strlen($header)+4;
} elseif (0 === strpos($recv_buffer, "GET")) {
return strlen($header) + 4;
} else {
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
}
}
/**
* 从http数据包中解析$_POST、$_GET、$_COOKIE等
* @param string $recv_buffer
* Parse $_POST、$_GET、$_COOKIE.
*
* @param string $recv_buffer
* @param TcpConnection $connection
* @return void
* @return array
*/
public static function decode($recv_buffer, TcpConnection $connection)
{
// 初始化
$_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array();
// Init.
$_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array();
$GLOBALS['HTTP_RAW_POST_DATA'] = '';
// 清空上次的数据
HttpCache::$header = array();
// Clear cache.
HttpCache::$header = array('Connection' => 'Connection: keep-alive');
HttpCache::$instance = new HttpCache();
// 需要设置的变量名
$_SERVER = array (
'QUERY_STRING' => '',
'REQUEST_METHOD' => '',
'REQUEST_URI' => '',
'SERVER_PROTOCOL' => '',
'SERVER_SOFTWARE' => 'workerman/3.0',
'SERVER_NAME' => '',
'HTTP_HOST' => '',
'HTTP_USER_AGENT' => '',
'HTTP_ACCEPT' => '',
'HTTP_ACCEPT_LANGUAGE' => '',
'HTTP_ACCEPT_ENCODING' => '',
'HTTP_COOKIE' => '',
'HTTP_CONNECTION' => '',
'REMOTE_ADDR' => '',
'REMOTE_PORT' => '0',
);
// $_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' => '',
'REMOTE_ADDR' => '',
'REMOTE_PORT' => '0',
);
// 将header分割成数组
// 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]);
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)
{
foreach ($header_data as $content) {
// \r\n\r\n
if(empty($content))
{
if (empty($content)) {
continue;
}
list($key, $value) = explode(':', $content, 2);
$key = strtolower($key);
$value = trim($value);
switch($key)
{
list($key, $value) = explode(':', $content, 2);
$key = str_replace('-', '_', strtoupper($key));
$value = trim($value);
$_SERVER['HTTP_' . $key] = $value;
switch ($key) {
// HTTP_HOST
case 'host':
$_SERVER['HTTP_HOST'] = $value;
$tmp = explode(':', $value);
case 'HOST':
$tmp = explode(':', $value);
$_SERVER['SERVER_NAME'] = $tmp[0];
if(isset($tmp[1]))
{
if (isset($tmp[1])) {
$_SERVER['SERVER_PORT'] = $tmp[1];
}
break;
// cookie
case 'cookie':
$_SERVER['HTTP_COOKIE'] = $value;
case 'COOKIE':
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
break;
// user-agent
case 'user-agent':
$_SERVER['HTTP_USER_AGENT'] = $value;
break;
// accept
case 'accept':
$_SERVER['HTTP_ACCEPT'] = $value;
break;
// accept-language
case 'accept-language':
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = $value;
break;
// accept-encoding
case 'accept-encoding':
$_SERVER['HTTP_ACCEPT_ENCODING'] = $value;
break;
// connection
case 'connection':
$_SERVER['HTTP_CONNECTION'] = $value;
break;
case 'referer':
$_SERVER['HTTP_REFERER'] = $value;
break;
case 'if-modified-since':
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = $value;
break;
case 'if-none-match':
$_SERVER['HTTP_IF_NONE_MATCH'] = $value;
break;
case 'content-type':
if(!preg_match('/boundary="?(\S+)"?/', $value, $match))
{
// content-type
case 'CONTENT_TYPE':
if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) {
$_SERVER['CONTENT_TYPE'] = $value;
}
else
{
} else {
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$http_post_boundary = '--'.$match[1];
$http_post_boundary = '--' . $match[1];
}
break;
case 'CONTENT_LENGTH':
$_SERVER['CONTENT_LENGTH'] = $value;
break;
}
}
// 需要解析$_POST
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] == 'multipart/form-data')
{
// Parse $_POST.
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'multipart/form-data') {
self::parseUploadFiles($http_body, $http_post_boundary);
}
else
{
} else {
parse_str($http_body, $_POST);
// $GLOBALS['HTTP_RAW_POST_DATA']
$GLOBALS['HTTP_RAW_POST_DATA'] = $http_body;
@ -176,13 +150,10 @@ class Http
// QUERY_STRING
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
if($_SERVER['QUERY_STRING'])
{
if ($_SERVER['QUERY_STRING']) {
// $GET
parse_str($_SERVER['QUERY_STRING'], $_GET);
}
else
{
} else {
$_SERVER['QUERY_STRING'] = '';
}
@ -193,103 +164,85 @@ class Http
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
return $recv_buffer;
return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES);
}
/**
* 编码增加HTTP头
* @param string $content
* Http encode.
*
* @param string $content
* @param TcpConnection $connection
* @return string
*/
public static function encode($content, TcpConnection $connection)
{
// 没有http-code默认给个
if(!isset(HttpCache::$header['Http-Code']))
{
// 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";
} else {
$header = HttpCache::$header['Http-Code'] . "\r\n";
unset(HttpCache::$header['Http-Code']);
}
// Content-Type
if(!isset(HttpCache::$header['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";
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";
} else {
$header .= $item . "\r\n";
}
}
// header
$header .= "Server: WorkerMan/3.0\r\nContent-Length: ".strlen($content)."\r\n\r\n";
$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;
return $header . $content;
}
/**
* 设置http头
* @return bool
*
* @return bool|void
*/
public static function header($content, $replace = true, $http_response_code = 0)
{
if(PHP_SAPI != 'cli')
{
if (PHP_SAPI != 'cli') {
return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace);
}
if(strpos($content, 'HTTP') === 0)
{
if (strpos($content, 'HTTP') === 0) {
$key = 'Http-Code';
}
else
{
} else {
$key = strstr($content, ":", true);
if(empty($key))
{
if (empty($key)) {
return false;
}
}
if('location' == strtolower($key) && !$http_response_code)
{
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')
{
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')
{
if ($key === 'Set-Cookie') {
HttpCache::$header[$key][] = $content;
}
else
{
} else {
HttpCache::$header[$key] = $content;
}
@ -297,109 +250,112 @@ class Http
}
/**
* 删除一个header
* Remove header.
*
* @param string $name
* @return void
*/
public static function headerRemove($name)
{
if(PHP_SAPI != 'cli')
{
return header_remove($name);
if (PHP_SAPI != 'cli') {
header_remove($name);
return;
}
unset( HttpCache::$header[$name]);
unset(HttpCache::$header[$name]);
}
/**
* 设置cookie
* @param string $name
* @param string $value
* Set cookie.
*
* @param string $name
* @param string $value
* @param integer $maxage
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $HTTPOnly
* @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')
{
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);
'Set-Cookie: ' . $name . '=' . rawurlencode($value)
. (empty($domain) ? '' : '; Domain=' . $domain)
. (empty($maxage) ? '' : '; Max-Age=' . $maxage)
. (empty($path) ? '' : '; Path=' . $path)
. (!$secure ? '' : '; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
}
/**
* sessionStart
*
* @return bool
*/
public static function sessionStart()
{
if(PHP_SAPI != 'cli')
{
if (PHP_SAPI != 'cli') {
return session_start();
}
if(HttpCache::$instance->sessionStarted)
{
if (HttpCache::$instance->sessionStarted) {
echo "already sessionStarted\nn";
return true;
}
HttpCache::$instance->sessionStarted = true;
// 没有sid则创建一个session文件生成一个sid
if(!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName]))
{
$file_name = tempnam(HttpCache::$sessionPath, 'sess_');
if(!$file_name)
{
// Generate a SID.
if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) {
$file_name = tempnam(HttpCache::$sessionPath, 'ses');
if (!$file_name) {
return false;
}
HttpCache::$instance->sessionFile = $file_name;
$session_id = substr(basename($file_name), strlen('sess_'));
$session_id = substr(basename($file_name), strlen('ses'));
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')
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 . '/sess_' . $_COOKIE[HttpCache::$sessionName];
if (!HttpCache::$instance->sessionFile) {
HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName];
}
// 有sid则打开文件读取session值
if(HttpCache::$instance->sessionFile)
{
// Read session from session file.
if (HttpCache::$instance->sessionFile) {
$raw = file_get_contents(HttpCache::$instance->sessionFile);
if($raw)
{
if ($raw) {
session_decode($raw);
}
}
return true;
}
/**
* 保存session
* Save session.
*
* @return bool
*/
public static function sessionWriteClose()
{
if(PHP_SAPI != 'cli')
{
if (PHP_SAPI != 'cli') {
return session_write_close();
}
if(!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION))
{
if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) {
$session_str = session_encode();
if($session_str && HttpCache::$instance->sessionFile)
{
if ($session_str && HttpCache::$instance->sessionFile) {
return file_put_contents(HttpCache::$instance->sessionFile, $session_str);
}
}
@ -407,70 +363,68 @@ class Http
}
/**
* 退出
* End, like call exit in php-fpm.
*
* @param string $msg
* @throws \Exception
*/
public static function end($msg = '')
{
if(PHP_SAPI != 'cli')
{
if (PHP_SAPI != 'cli') {
exit($msg);
}
if($msg)
{
if ($msg) {
echo $msg;
}
throw new \Exception('jump_exit');
}
/**
* get mime types
* Get mime types.
*
* @return string
*/
public static function getMimeTypesFile()
{
return __DIR__.'/Http/mime.types';
return __DIR__ . '/Http/mime.types';
}
/**
* 解析$_FILES
/**
* Parse $_FILES.
*
* @param string $http_body
* @param string $http_post_boundary
* @return void
*/
protected function parseUploadFiles($http_body, $http_post_boundary)
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] === '')
{
$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]);
}
foreach($boundary_data_array as $boundary_data_buffer)
{
foreach ($boundary_data_array as $boundary_data_buffer) {
list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2);
// 去掉末尾\r\n
// Remove \r\n from the end of buffer.
$boundary_value = substr($boundary_value, 0, -2);
foreach (explode("\r\n", $boundary_header_buffer) as $item)
{
foreach (explode("\r\n", $boundary_header_buffer) as $item) {
list($header_key, $header_value) = explode(": ", $item);
$header_key = strtolower($header_key);
switch ($header_key)
{
switch ($header_key) {
case "content-disposition":
// 是文件
if(preg_match('/name=".*?"; filename="(.*?)"$/', $header_value, $match))
{
// Is file data.
if (preg_match('/name=".*?"; filename="(.*?)"$/', $header_value, $match)) {
// Parse $_FILES.
$_FILES[] = array(
'file_name' => $match[1],
'file_data' => $boundary_value,
'file_size' => strlen($boundary_value),
);
continue;
}
// 是post field
else
{
// 收集post
if(preg_match('/name="(.*?)"$/', $header_value, $match))
{
} // Is post field.
else {
// Parse $_POST.
if (preg_match('/name="(.*?)"$/', $header_value, $match)) {
$_POST[$match[1]] = $boundary_value;
}
}
@ -482,57 +436,61 @@ class Http
}
/**
* 解析http协议数据包 缓存先关
* @author walkor
* 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',
);
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 $sessionPath = '';
public static $sessionName = '';
@ -543,8 +501,7 @@ class HttpCache
{
self::$sessionName = ini_get('session.name');
self::$sessionPath = session_save_path();
if(!self::$sessionPath)
{
if (!self::$sessionPath) {
self::$sessionPath = sys_get_temp_dir();
}
@\session_start();

View File

@ -1,43 +1,51 @@
<?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\ConnectionInterface;
/**
* Protocol interface
* @author walkor <walkor@workerman.net>
*/
interface ProtocolInterface
{
/**
* 用于分包即在接收的buffer中返回当前请求的长度字节
* 如果可以在$recv_buffer中得到请求包的长度则返回长度
* 否则返回0表示需要更多的数据才能得到当前请求包的长度
* 如果返回false或者负数则代表请求不符合协议则连接会断开
* 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
* @param string $recv_buffer
* @return int|false
*/
public static function input($recv_buffer, ConnectionInterface $connection);
/**
* 用于请求解包
* input返回值大于0并且WorkerMan收到了足够的数据则自动调用decode
* 然后触发onMessage回调并将decode解码后的数据传递给onMessage回调的第二个参数
* 也就是说当收到完整的客户端请求时会自动调用decode解码无需业务代码中手动调用
* Decode package and emit onMessage($message) callback, $message is the result that decode returned.
*
* @param ConnectionInterface $connection
* @param string $recv_buffer
* @param string $recv_buffer
* @return mixed
*/
public static function decode($recv_buffer, ConnectionInterface $connection);
/**
* 用于请求打包
* 当需要向客户端发送数据即调用$connection->send($data);
* 会自动把$data用encode打包一次变成符合协议的数据格式然后再发送给客户端
* 也就是说发送给客户端的数据会自动encode打包无需业务代码中手动调用
* Encode package brefore sending to client.
*
* @param ConnectionInterface $connection
* @param mixed $data
* @param mixed $data
* @return string
*/
public static function encode($data, ConnectionInterface $connection);

View File

@ -1,60 +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;
use Workerman\Connection\TcpConnection;
/**
* Text协议
* 以换行为请求结束标记
* @author walkor <walkor@workerman.net>
* Text Protocol.
*/
class Text
{
/**
* 检查包的完整性
* 如果能够得到包长则返回包的长度否则返回0继续等待数据
* @param string $buffer
* Check the integrity of the package.
*
* @param string $buffer
* @param TcpConnection $connection
* @return int
*/
public static function input($buffer ,TcpConnection $connection)
public static function input($buffer, TcpConnection $connection)
{
// 由于没有包头,无法预先知道包长,不能无限制的接收数据,
// 所以需要判断当前接收的数据是否超过限定值
if(strlen($buffer)>=TcpConnection::$maxPackageSize)
{
// Judge whether the package length exceeds the limit.
if (strlen($buffer) >= TcpConnection::$maxPackageSize) {
$connection->close();
return 0;
}
// 获得换行字符"\n"位置
// Find the position of "\n".
$pos = strpos($buffer, "\n");
// 没有换行符无法得知包长返回0继续等待数据
if($pos === false)
{
// No "\n", packet length is unknown, continue to wait for the data so return 0.
if ($pos === false) {
return 0;
}
// 有换行符,返回当前包长,包含换行符
return $pos+1;
// Return the current package length.
return $pos + 1;
}
/**
* 打包,当向客户端发送数据的时候会自动调用
* Encode.
*
* @param string $buffer
* @return string
*/
public static function encode($buffer)
{
// 加上换行
return $buffer."\n";
// Add "\n"
return $buffer . "\n";
}
/**
* 解包当接收到的数据字节数等于input返回的值大于0的值自动调用
* 并传递给onMessage回调函数的$data参数
* Decode.
*
* @param string $buffer
* @return string
*/
public static function decode($buffer)
{
// 去掉换行
// Remove "\n"
return trim($buffer);
}
}

View File

@ -1,221 +1,258 @@
<?php
namespace Workerman\Protocols;
/**
* WebSocket 协议服务端解包和打包
* @author walkor <walkor@workerman.net>
* 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\Worker;
/**
* WebSocket protocol.
*/
class Websocket implements \Workerman\Protocols\ProtocolInterface
{
/**
* websocket头部最小长度
* Minimum head length of websocket protocol.
*
* @var int
*/
const MIN_HEAD_LEN = 6;
const MIN_HEAD_LEN = 2;
/**
* websocket blob类型
* @var char
* Websocket blob type.
*
* @var string
*/
const BINARY_TYPE_BLOB = "\x81";
/**
* websocket arraybuffer类型
* @var char
* Websocket arraybuffer type.
*
* @var string
*/
const BINARY_TYPE_ARRAYBUFFER = "\x82";
/**
* 检查包的完整性
* @param string $buffer
* 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);
// 长度不够
if($recv_len < self::MIN_HEAD_LEN)
{
// We need more data.
if ($recv_len < self::MIN_HEAD_LEN) {
return 0;
}
// 还没有握手
if(empty($connection->websocketHandshake))
{
// Has not yet completed the handshake.
if (empty($connection->websocketHandshake)) {
return self::dealHandshake($buffer, $connection);
}
// $connection->websocketCurrentFrameLength有值说明当前fin为0则缓冲websocket帧数据
if($connection->websocketCurrentFrameLength)
{
// 如果当前帧数据未收全,则继续收
if($connection->websocketCurrentFrameLength > $recv_len)
{
// 返回0因为不清楚完整的数据包长度需要等待fin=1的帧
// 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
{
$data_len = ord($buffer[1]) & 127;
$firstbyte = ord($buffer[0]);
$is_fin_frame = $firstbyte>>7;
$opcode = $firstbyte & 0xf;
switch($opcode)
{
// 附加数据帧 @todo 实现附加数据帧
} else {
$data_len = ord($buffer[1]) & 127;
$firstbyte = ord($buffer[0]);
$is_fin_frame = $firstbyte >> 7;
$opcode = $firstbyte & 0xf;
switch ($opcode) {
case 0x0:
break;
// 文本数据帧
// Blob type.
case 0x1:
break;
// 二进制数据帧
// Arraybuffer type.
case 0x2:
break;
// 关闭的包
// Close package.
case 0x8:
// 如果有设置onWebSocketClose回调尝试执行
if(isset($connection->onWebSocketClose))
{
call_user_func($connection->onWebSocketClose, $connection);
}
// 默认行为是关闭连接
else
{
// 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的包
// Ping package.
case 0x9:
// 如果有设置onWebSocketPing回调尝试执行
if(isset($connection->onWebSocketPing))
{
call_user_func($connection->onWebSocketPing, $connection);
}
// 默认发送pong
else
{
// Try to emit onWebSocketPing callback.
if (isset($connection->onWebSocketPing)) {
try {
call_user_func($connection->onWebSocketPing, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
} // Send pong package to client.
else {
$connection->send(pack('H*', '8a00'), true);
}
// 从接受缓冲区中消费掉该数据包
if(!$data_len)
{
// Consume data from receive buffer.
if (!$data_len) {
$connection->consumeRecvBuffer(self::MIN_HEAD_LEN);
if ($recv_len > self::MIN_HEAD_LEN) {
return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection);
}
return 0;
}
break;
// pong的包
// Pong package.
case 0xa:
// 如果有设置onWebSocketPong回调尝试执行
if(isset($connection->onWebSocketPong))
{
call_user_func($connection->onWebSocketPong, $connection);
// Try to emit onWebSocketPong callback.
if (isset($connection->onWebSocketPong)) {
try {
call_user_func($connection->onWebSocketPong, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
// 从接受缓冲区中消费掉该数据包
if(!$data_len)
{
// Consume data from receive buffer.
if (!$data_len) {
$connection->consumeRecvBuffer(self::MIN_HEAD_LEN);
if ($recv_len > self::MIN_HEAD_LEN) {
return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection);
}
return 0;
}
break;
// 错误的opcode
// Wrong opcode.
default :
echo "error opcode $opcode and close websocket connection\n";
echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n";
$connection->close();
return 0;
}
// websocket二进制数据
$head_len = self::MIN_HEAD_LEN;
// Calculate packet length.
$head_len = 6;
if ($data_len === 126) {
$head_len = 8;
if($head_len > $recv_len)
{
if ($head_len > $recv_len) {
return 0;
}
$pack = unpack('ntotal_len', substr($buffer, 2, 2));
$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;
} 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'];
}
$arr = unpack('N2', substr($buffer, 2, 8));
$data_len = $arr[1]*4294967296 + $arr[2];
}
$current_frame_length = $head_len + $data_len;
if($is_fin_frame)
{
if ($is_fin_frame) {
return $current_frame_length;
}
else
{
} else {
$connection->websocketCurrentFrameLength = $current_frame_length;
}
}
// 收到的数据刚好是一个frame
if($connection->websocketCurrentFrameLength == $recv_len)
{
// Received just a frame length data.
if ($connection->websocketCurrentFrameLength === $recv_len) {
self::decode($buffer, $connection);
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
$connection->websocketCurrentFrameLength = 0;
return 0;
}
// 收到的数据大于一个frame
elseif($connection->websocketCurrentFrameLength < $recv_len)
{
} // 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;
$current_frame_length = $connection->websocketCurrentFrameLength;
$connection->websocketCurrentFrameLength = 0;
// 继续读取下一个frame
// Continue to read next frame.
return self::input(substr($buffer, $current_frame_length), $connection);
}
// 收到的数据不足一个frame
else
{
} // The length of the received data is less than the length of a frame.
else {
return 0;
}
}
/**
* 打包
* @param string $buffer
* Websocket encode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function encode($buffer, ConnectionInterface $connection)
{
$len = strlen($buffer);
// 还没握手不能发数据
if(empty($connection->websocketHandshake))
{
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Send data before handshake. ", true);
$connection->close();
return false;
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 = self::BINARY_TYPE_BLOB;
}
$first_byte = $connection->websocketType;
if($len<=125)
{
return $first_byte.chr($len).$buffer;
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;
}
}
else if($len<=65535)
{
return $first_byte.chr(126).pack("n", $len).$buffer;
}
else
{
return $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 = '';
}
$connection->tmpWebsocketData .= $encode_buffer;
// Return empty string.
return '';
}
return $encode_buffer;
}
/**
* 解包
* @param string $buffer
* Websocket decode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function decode($buffer, ConnectionInterface $connection)
@ -224,166 +261,176 @@ class Websocket implements \Workerman\Protocols\ProtocolInterface
$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);
$data = substr($buffer, 8);
} else {
$masks = substr($buffer, 2, 4);
$data = substr($buffer, 6);
if ($len === 127) {
$masks = substr($buffer, 10, 4);
$data = substr($buffer, 14);
} else {
$masks = substr($buffer, 2, 4);
$data = substr($buffer, 6);
}
}
for ($index = 0; $index < strlen($data); $index++) {
$decoded .= $data[$index] ^ $masks[$index % 4];
}
if($connection->websocketCurrentFrameLength)
{
if ($connection->websocketCurrentFrameLength) {
$connection->websocketDataBuffer .= $decoded;
return $connection->websocketDataBuffer;
}
else
{
$decoded = $connection->websocketDataBuffer . $decoded;
$connection->websocketDataBuffer = '';
} else {
if ($connection->websocketDataBuffer !== '') {
$decoded = $connection->websocketDataBuffer . $decoded;
$connection->websocketDataBuffer = '';
}
return $decoded;
}
}
/**
* 处理websocket握手
* @param string $buffer
* @param TcpConnection $connection
* Websocket handshake.
*
* @param string $buffer
* @param \Workerman\Connection\TcpConnection $connection
* @return int
*/
protected static function dealHandshake($buffer, $connection)
{
// 握手阶段客户端发送HTTP协议
if(0 === strpos($buffer, 'GET'))
{
// 判断\r\n\r\n边界
// 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)
{
if (!$heder_end_pos) {
return 0;
}
$header_length = $heder_end_pos + 4;
// 解析Sec-WebSocket-Key
// Get Sec-WebSocket-Key.
$Sec_WebSocket_Key = '';
if(preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/", $buffer, $match))
{
if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) {
$Sec_WebSocket_Key = $match[1];
}
else
{
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Sec-WebSocket-Key not found", true);
} else {
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Sec-WebSocket-Key not found.<br>This is a WebSocket service and can not be accessed via HTTP.<br>See <a href=\"http://wiki.workerman.net/Error1\">http://wiki.workerman.net/Error1</a>",
true);
$connection->close();
return 0;
}
$new_key = base64_encode(sha1($Sec_WebSocket_Key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
// 握手返回的数据
$new_message = "HTTP/1.1 101 Switching Protocols\r\n";
$new_message .= "Upgrade: websocket\r\n";
$new_message .= "Sec-WebSocket-Version: 13\r\n";
$new_message .= "Connection: Upgrade\r\n";
$new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
// 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 .= "Server: workerman/".Worker::VERSION."\r\n";
$handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
// Mark handshake complete..
$connection->websocketHandshake = true;
// Websocket data buffer.
$connection->websocketDataBuffer = '';
// Current websocket frame length.
$connection->websocketCurrentFrameLength = 0;
// Current websocket frame data.
$connection->websocketCurrentFrameBuffer = '';
$connection->consumeRecvBuffer(strlen($buffer));
$connection->send($new_message, true);
// Consume handshake data.
$connection->consumeRecvBuffer($header_length);
// Send handshake response.
$connection->send($handshake_message, true);
// There are data waiting to be sent.
if (!empty($connection->tmpWebsocketData)) {
$connection->send($connection->tmpWebsocketData, true);
$connection->tmpWebsocketData = '';
}
// blob or arraybuffer
$connection->websocketType = self::BINARY_TYPE_BLOB;
// 如果有设置onWebSocketConnect回调尝试执行
if(isset($connection->onWebSocketConnect))
{
if (empty($connection->websocketType)) {
$connection->websocketType = self::BINARY_TYPE_BLOB;
}
// Try to emit onWebSocketConnect callback.
if (isset($connection->onWebSocketConnect)) {
self::parseHttpHeader($buffer);
try
{
try {
call_user_func($connection->onWebSocketConnect, $connection, $buffer);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
catch(\Exception $e)
{
echo $e;
if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) {
$connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION);
}
$_GET = $_COOKIE = $_SERVER = array();
$_GET = $_SERVER = $_SESSION = $_COOKIE = array();
}
if (strlen($buffer) > $header_length) {
return self::input(substr($buffer, $header_length), $connection);
}
return 0;
}
// 如果是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";
} // 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;
}
// 出错
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Invalid handshake data for websocket. ", true);
// Bad websocket handshake request.
$connection->send("HTTP/1.1 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Invalid handshake data for websocket. ",
true);
$connection->close();
return 0;
}
/**
* 从header中获取
* Parse http header.
*
* @param string $buffer
* @return void
*/
protected static function parseHttpHeader($buffer)
{
$header_data = explode("\r\n", $buffer);
$_SERVER = array();
list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', $header_data[0]);
// 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)
{
foreach ($header_data as $content) {
// \r\n\r\n
if(empty($content))
{
if (empty($content)) {
continue;
}
list($key, $value) = explode(':', $content, 2);
$key = strtolower($key);
$value = trim($value);
switch($key)
{
list($key, $value) = explode(':', $content, 2);
$key = str_replace('-', '_', strtoupper($key));
$value = trim($value);
$_SERVER['HTTP_' . $key] = $value;
switch ($key) {
// HTTP_HOST
case 'host':
$_SERVER['HTTP_HOST'] = $value;
$tmp = explode(':', $value);
case 'HOST':
$tmp = explode(':', $value);
$_SERVER['SERVER_NAME'] = $tmp[0];
if(isset($tmp[1]))
{
if (isset($tmp[1])) {
$_SERVER['SERVER_PORT'] = $tmp[1];
}
break;
// HTTP_COOKIE
case 'cookie':
$_SERVER['HTTP_COOKIE'] = $value;
// cookie
case 'COOKIE':
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
break;
// HTTP_USER_AGENT
case 'user-agent':
$_SERVER['HTTP_USER_AGENT'] = $value;
break;
// HTTP_REFERER
case 'referer':
$_SERVER['HTTP_REFERER'] = $value;
break;
case 'origin':
$_SERVER['HTTP_ORIGIN'] = $value;
break;
}
}
// QUERY_STRING
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
if($_SERVER['QUERY_STRING'])
{
if ($_SERVER['QUERY_STRING']) {
// $GET
parse_str($_SERVER['QUERY_STRING'], $_GET);
}
else
{
} else {
$_SERVER['QUERY_STRING'] = '';
}
}

383
Workerman/Protocols/Ws.php Normal file
View File

@ -0,0 +1,383 @@
<?php
namespace Workerman\Protocols;
use Workerman\Worker;
use Workerman\Lib\Timer;
/**
* Websocket protocol for client.
*/
class Ws
{
/**
* Minimum head length of websocket protocol.
*
* @var int
*/
const MIN_HEAD_LEN = 2;
/**
* 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)) {
echo "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 < self::MIN_HEAD_LEN) {
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 {
$data_len = ord($buffer[1]) & 127;
$firstbyte = ord($buffer[0]);
$is_fin_frame = $firstbyte >> 7;
$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:
// Try to emit onWebSocketPing callback.
if (isset($connection->onWebSocketPing)) {
try {
call_user_func($connection->onWebSocketPing, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
} // Send pong package to client.
else {
$connection->send(pack('H*', '8a00'), true);
}
// Consume data from receive buffer.
if (!$data_len) {
$connection->consumeRecvBuffer(self::MIN_HEAD_LEN);
if ($recv_len > self::MIN_HEAD_LEN) {
return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection);
}
return 0;
}
break;
// Pong package.
case 0xa:
// Try to emit onWebSocketPong callback.
if (isset($connection->onWebSocketPong)) {
try {
call_user_func($connection->onWebSocketPong, $connection);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
// Consume data from receive buffer.
if (!$data_len) {
$connection->consumeRecvBuffer(self::MIN_HEAD_LEN);
if ($recv_len > self::MIN_HEAD_LEN) {
return self::input(substr($buffer, self::MIN_HEAD_LEN), $connection);
}
return 0;
}
break;
// Wrong opcode.
default :
echo "error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n";
$connection->close();
return 0;
}
// Calculate packet length.
if ($data_len === 126) {
if (strlen($buffer) < 6) {
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;
}
if ($is_fin_frame) {
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)) {
self::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:
for ($i = 0; $i < $length; $i++) {
$frame .= $payload[$i] ^ $mask_key[$i % 4];
}
if ($connection->handshakeStep === 1) {
$connection->tmpWebsocketData = isset($connection->tmpWebsocketData) ? $connection->tmpWebsocketData . $frame : $frame;
return '';
}
return $frame;
}
/**
* Websocket decode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function decode($bytes, $connection)
{
$masked = $bytes[1] >> 7;
$data_length = $masked ? ord($bytes[1]) & 127 : ord($bytes[1]);
$decoded_data = '';
if ($masked === true) {
if ($data_length === 126) {
$mask = substr($bytes, 4, 4);
$coded_data = substr($bytes, 8);
} else if ($data_length === 127) {
$mask = substr($bytes, 10, 4);
$coded_data = substr($bytes, 14);
} else {
$mask = substr($bytes, 2, 4);
$coded_data = substr($bytes, 6);
}
for ($i = 0; $i < strlen($coded_data); $i++) {
$decoded_data .= $coded_data[$i] ^ $mask[$i % 4];
}
} else {
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)
{
self::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.
$header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n".
"Host: $host\r\n".
"Connection: Upgrade\r\n".
"Upgrade: websocket\r\n".
"Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n".
"Sec-WebSocket-Version: 13\r\n".
"Sec-WebSocket-Key: ".base64_encode(sha1(uniqid(mt_rand(), true), true))."\r\n\r\n";
$connection->send($header, true);
$connection->handshakeStep = 1;
$connection->websocketCurrentFrameLength = 0;
$connection->websocketDataBuffer = '';
}
/**
* 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) {
// handshake complete
$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*', '8900'), 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;
}
}

400
Workerman/README.md Normal file
View File

@ -0,0 +1,400 @@
# Workerman
[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
## What is it
Workerman is a library for event-driven programming in PHP. It has a huge number of features. Each worker is able to handle thousands of connections.
## Requires
PHP 5.3 or Higher
A POSIX compatible operating system (Linux, OSX, BSD)
POSIX and PCNTL extensions for PHP
## Installation
```
composer require workerman/workerman
```
## Basic Usage
### A websocket server
test.php
```php
<?php
use Workerman\Worker;
require_once './Workerman/Autoloader.php';
// 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
test.php
```php
require_once './Workerman/Autoloader.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
test.php
```php
require_once './Workerman/Autoloader.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
test.php
```php
require_once './Workerman/Autoloader.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();
```
### 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";
}
}
```
test.php
```php
require_once './Workerman/Autoloader.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
test.php
```php
require_once './Workerman/Autoloader.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();
```
run with:
```php test.php start```
## Available commands
```php test.php start ```
```php test.php start -d ```
![workerman start](http://www.workerman.net/img/workerman-start.png)
```php test.php status ```
![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123)
```php test.php stop ```
```php test.php restart ```
```php test.php reload ```
## Documentation
中文主页:[http://www.workerman.net](http://www.workerman.net)
中文文档: [http://doc3.workerman.net](http://doc3.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)
```
# Demos
## [tadpole](http://kedou.workerman.net/)
[Live demo](http://kedou.workerman.net/)
[Source code](https://github.com/walkor/workerman)
![workerman todpole](http://www.workerman.net/img/workerman-todpole.png)
## [BrowserQuest](http://www.workerman.net/demos/browserquest/)
[Live demo](http://www.workerman.net/demos/browserquest/)
[Source code](https://github.com/walkor/BrowserQuest-PHP)
![BrowserQuest width workerman](http://www.workerman.net/img/browserquest.jpg)
## [web vmstat](http://www.workerman.net/demos/vmstat/)
[Live demo](http://www.workerman.net/demos/vmstat/)
[Source code](https://github.com/walkor/workerman-vmstat)
![web vmstat](http://www.workerman.net/img/workerman-vmstat.png)
## [live-ascii-camera](https://github.com/walkor/live-ascii-camera)
[Live demo camera page](http://www.workerman.net/demos/live-ascii-camera/camera.html)
[Live demo receive page](http://www.workerman.net/demos/live-ascii-camera/)
[Source code](https://github.com/walkor/live-ascii-camera)
![live-ascii-camera](http://www.workerman.net/img/live-ascii-camera.png)
## [live-camera](https://github.com/walkor/live-camera)
[Live demo camera page](http://www.workerman.net/demos/live-camera/camera.html)
[Live demo receive page](http://www.workerman.net/demos/live-camera/)
[Source code](https://github.com/walkor/live-camera)
![live-camera](http://www.workerman.net/img/live-camera.jpg)
## [chat room](http://chat.workerman.net/)
[Live demo](http://chat.workerman.net/)
[Source code](https://github.com/walkor/workerman-chat)
![workerman-chat](http://www.workerman.net/img/workerman-chat.png)
## [PHPSocket.IO](https://github.com/walkor/phpsocket.io)
[Live demo](http://www.workerman.net/demos/phpsocketio-chat/)
[Source code](https://github.com/walkor/phpsocket.io)
![phpsocket.io](http://www.workerman.net/img/socket.io.png)
## [statistics](http://www.workerman.net:55757/)
[Live demo](http://www.workerman.net:55757/)
[Source code](https://github.com/walkor/workerman-statistics)
![workerman-statistics](http://www.workerman.net/img/workerman-statistics.png)
## [flappybird](http://workerman.net/demos/flappy-bird/)
[Live demo](http://workerman.net/demos/flappy-bird/)
[Source code](https://github.com/walkor/workerman-flappy-bird)
![workerman-statistics](http://www.workerman.net/img/workerman-flappy-bird.png)
## [jsonRpc](https://github.com/walkor/workerman-JsonRpc)
[Source code](https://github.com/walkor/workerman-JsonRpc)
![workerman-jsonRpc](http://www.workerman.net/img/workerman-json-rpc.png)
## [thriftRpc](https://github.com/walkor/workerman-thrift)
[Source code](https://github.com/walkor/workerman-thrift)
![workerman-thriftRpc](http://www.workerman.net/img/workerman-thrift.png)
## [web-msg-sender](https://github.com/walkor/web-msg-sender)
[Live demo send page](http://workerman.net:3333/)
[Live demo receive page](http://workerman.net/web-msg-sender.html)
[Source code](https://github.com/walkor/web-msg-sender)
![web-msg-sender](http://www.workerman.net/img/web-msg-sender.png)
## [shadowsocks-php](https://github.com/walkor/shadowsocks-php)
[Source code](https://github.com/walkor/shadowsocks-php)
![shadowsocks-php](http://www.workerman.net/img/shadowsocks-php.png)
## [queue](https://github.com/walkor/workerman-queue)
[Source code](https://github.com/walkor/workerman-queue)
## LICENSE
Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt).

View File

@ -1,258 +1,301 @@
<?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\Worker;
use \Workerman\Protocols\Http;
use \Workerman\Protocols\HttpCache;
use Workerman\Protocols\Http;
use Workerman\Protocols\HttpCache;
/**
*
* 基于Worker实现的一个简单的WebServer
* 支持静态文件、支持文件上传、支持POST
* HTTP协议
*
* @author walkor <walkor@workerman.net>
* WebServer.
*/
class WebServer extends Worker
{
/**
* 默认mime类型
* @var string
*/
protected static $defaultMimeType = 'text/html; charset=utf-8';
/**
* 服务器名到文件路径的转换
* Virtual host to path mapping.
*
* @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www']
*/
protected $serverRoot = array();
/**
* mime类型映射关系
* Mime mapping.
*
* @var array
*/
protected static $mimeTypeMap = array();
/**
* 用来保存用户设置的onWorkerStart回调
* Used to save user OnWorkerStart callback settings.
*
* @var callback
*/
protected $_onWorkerStart = null;
/**
* 添加站点域名与站点目录的对应关系类似nginx的
* Add virtual host.
*
* @param string $domain
* @param string $root_path
* @return void
*/
public function addRoot($domain, $root_path)
public function addRoot($domain, $root_path)
{
$this->serverRoot[$domain] = $root_path;
}
/**
* 构造函数
* Construct.
*
* @param string $socket_name
* @param array $context_option
* @param array $context_option
*/
public function __construct($socket_name, $context_option = array())
{
list($scheme, $address) = explode(':', $socket_name, 2);
parent::__construct('http:'.$address, $context_option);
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');
$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))
{
if (empty($this->serverRoot)) {
throw new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path');
}
// 初始化HttpCache
// Init HttpCache.
HttpCache::init();
// 初始化mimeMap
// Init mimeMap.
$this->initMimeTypeMap();
// 尝试执行开发者设定的onWorkerStart回调
if($this->_onWorkerStart)
{
call_user_func($this->_onWorkerStart, $this);
// 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);
}
}
}
/**
* 初始化mimeType
* Init mime map.
*
* @return void
*/
public function initMimeTypeMap()
{
$mime_file = Http::getMimeTypesFile();
if(!is_file($mime_file))
{
$this->notice("$mime_file mime.type file not fond");
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))
{
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];
$extension_var = $match[2];
$extension_array = explode(' ', substr($extension_var, 0, -1));
foreach($extension_array as $extension)
{
self::$mimeTypeMap[$extension] = $mime_type;
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;
}
}
}
}
/**
* 当接收到完整的http请求后的处理逻辑
* 1、如果请求的是以php为后缀的文件则尝试加载
* 2、如果请求的url没有后缀则尝试加载对应目录的index.php
* 3、如果请求的是非php为后缀的文件尝试读取原始数据并发送
* 4、如果请求的文件不存在则返回404
* @param TcpConnection $connection
* @param mixed $data
* Emit when http message coming.
*
* @param Connection\TcpConnection $connection
* @return void
*/
public function onMessage($connection, $data)
public function onMessage($connection)
{
// 请求的文件
$url_info = parse_url($_SERVER['REQUEST_URI']);
if(!$url_info)
{
// REQUEST_URI.
$workerman_url_info = parse_url($_SERVER['REQUEST_URI']);
if (!$workerman_url_info) {
Http::header('HTTP/1.1 400 Bad Request');
return $connection->close('<h1>400 Bad Request</h1>');
$connection->close('<h1>400 Bad Request</h1>');
return;
}
$path = $url_info['path'];
$workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/';
$path_info = pathinfo($path);
$extension = isset($path_info['extension']) ? $path_info['extension'] : '' ;
if($extension == '')
{
$path = ($len = strlen($path)) && $path[$len -1] == '/' ? $path.'index.php' : $path . '/index.php';
$extension = 'php';
$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';
}
$root_dir = isset($this->serverRoot[$_SERVER['HTTP_HOST']]) ? $this->serverRoot[$_SERVER['HTTP_HOST']] : current($this->serverRoot);
$workerman_root_dir = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot);
$file = "$root_dir/$path";
$workerman_file = "$workerman_root_dir/$workerman_path";
// 对应的php文件不存在则直接使用根目录的index.php
if($extension == 'php' && !is_file($file))
{
$file = "$root_dir/index.php";
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';
}
}
// 请求的文件存在
if(is_file($file))
{
// 判断是否是站点目录里的文件
if((!($request_realpath = realpath($file)) || !($root_dir_realpath = realpath($root_dir))) || 0 !== strpos($request_realpath, $root_dir_realpath))
{
// 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');
return $connection->close('<h1>400 Bad Request</h1>');
$connection->close('<h1>400 Bad Request</h1>');
return;
}
$file = realpath($file);
$workerman_file = realpath($workerman_file);
// 如果请求的是php文件
if($extension == 'php')
{
$cwd = getcwd();
chdir($root_dir);
// Request php file.
if ($workerman_file_extension === 'php') {
$workerman_cwd = getcwd();
chdir($workerman_root_dir);
ini_set('display_errors', 'off');
// 缓冲输出
ob_start();
// 载入php文件
try
{
// $_SERVER变量
// Try to include php file.
try {
// $_SERVER.
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
include $file;
}
catch(\Exception $e)
{
// 如果不是exit
if($e->getMessage() != 'jump_exit')
{
include $workerman_file;
} catch (\Exception $e) {
// Jump_exit?
if ($e->getMessage() != 'jump_exit') {
echo $e;
}
}
$content = ob_get_clean();
ini_set('display_errors', 'on');
$connection->close($content);
chdir($cwd);
return ;
}
// 请求的是静态资源文件
if(isset(self::$mimeTypeMap[$extension]))
{
Http::header('Content-Type: '. self::$mimeTypeMap[$extension]);
}
else
{
Http::header('Content-Type: '. self::$defaultMimeType);
}
// 获取文件信息
$info = stat($file);
$modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' GMT' : '';
// 如果有$_SERVER['HTTP_IF_MODIFIED_SINCE']
if(!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info)
{
// 文件没有更改则直接304
if($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE'])
{
// 304
Http::header('HTTP/1.1 304 Not Modified');
// 发送给客户端
return $connection->close('');
if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
$connection->send($content);
} else {
$connection->close($content);
}
chdir($workerman_cwd);
return;
}
if($modified_time)
{
Http::header("Last-Modified: $modified_time");
}
// 发送给客户端
return $connection->close(file_get_contents($file));
}
else
{
// Send file to client.
return self::sendFile($connection, $workerman_file);
} else {
// 404
Http::header("HTTP/1.1 404 Not Found");
return $connection->close('<html><head><title>404 页面不存在</title></head><body><center><h3>404 Not Found</h3></center></body></html>');
$connection->close('<html><head><title>404 File not found</title></head><body><center><h3>404 Not Found</h3></center></body></html>');
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']) . ' GMT' : '';
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..
$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

33
Workerman/composer.json Normal file
View File

@ -0,0 +1,33 @@
{
"name" : "workerman/workerman",
"type" : "project",
"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://doc3.workerman.net/index.html",
"source": "https://github.com/walkor/workerman"
},
"require": {
"php": ">=5.3"
},
"suggest": {
"ext-libevent": "For better performance."
},
"autoload": {
"psr-4": {"Workerman\\": "./"}
},
"minimum-stability":"dev"
}