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(); $connection->close();
return; return;
} }
//$connection->send("\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10");
//$connection->stage = STAGE_DNS;
$connection->stage = STAGE_CONNECTING; $connection->stage = STAGE_CONNECTING;
$remote_connection = new AsyncTcpConnection('tcp://'.$header_data[1].':'.$header_data[2]); $remote_connection = new AsyncTcpConnection('tcp://'.$header_data[1].':'.$header_data[2]);
$remote_connection->onConnect = function($remote_connection)use($connection) $remote_connection->onConnect = function($remote_connection)use($connection)
{ {
$connection->state = STAGE_STREAM; $connection->state = STAGE_STREAM;
$connection->send("\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10"); $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) $remote_connection->connect();
{
$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();
};
} }
}; };

View File

@ -1,61 +1,69 @@
<?php <?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; namespace Workerman;
// 定义Workerman根目录
if(!defined('WORKERMAN_ROOT_DIR'))
{
define('WORKERMAN_ROOT_DIR', realpath(__DIR__ . '/../'));
}
// 包含常量定义文件
require_once WORKERMAN_ROOT_DIR.'/Workerman/Lib/Constants.php';
/** /**
* 自动加载类 * Autoload.
* @author walkor<walkor@workerman.net>
*/ */
class Autoloader class Autoloader
{ {
// 应用的初始化目录,作为加载类文件的参考目录 /**
protected static $_appInitPath = ''; * Autoload root path.
*
* @var string
*/
protected static $_autoloadRootPath = '';
/** /**
* 设置应用初始化目录 * Set autoload root path.
*
* @param string $root_path * @param string $root_path
* @return void * @return void
*/ */
public static function setRootPath($root_path) public static function setRootPath($root_path)
{ {
self::$_appInitPath = $root_path; self::$_autoloadRootPath = $root_path;
} }
/** /**
* 根据命名空间加载文件 * Load files by namespace.
*
* @param string $name * @param string $name
* @return boolean * @return boolean
*/ */
public static function loadByNamespace($name) public static function loadByNamespace($name)
{ {
// 相对路径 $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name);
$class_path = str_replace('\\', DIRECTORY_SEPARATOR ,$name); if (strpos($name, 'Workerman\\') === 0) {
// 先尝试在应用目录寻找文件 $class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php';
$class_file = self::$_appInitPath . '/' . $class_path.'.php'; } else {
// 文件不存在则在workerman根目录中寻找 if (self::$_autoloadRootPath) {
if(!is_file($class_file)) $class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php';
{ }
$class_file = WORKERMAN_ROOT_DIR . 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); require_once($class_file);
if(class_exists($name, false)) if (class_exists($name, false)) {
{
return true; return true;
} }
} }
return false; return false;
} }
} }
// 设置类自动加载回调函数
spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); spl_autoload_register('\Workerman\Autoloader::loadByNamespace');

View File

@ -1,128 +1,274 @@
<?php <?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; namespace Workerman\Connection;
use Workerman\Events\Libevent;
use Workerman\Events\Select;
use Workerman\Events\EventInterface; use Workerman\Events\EventInterface;
use Workerman\Worker; use Workerman\Worker;
use \Exception; use Exception;
/** /**
* 异步tcp连接类 * AsyncTcpConnection.
* @author walkor<walkor@workerman.net>
*/ */
class AsyncTcpConnection extends TcpConnection class AsyncTcpConnection extends TcpConnection
{ {
/** /**
* 连接状态 连接中 * Emitted when socket connection is successfully established.
* @var int *
*/
protected $_status = self::STATUS_CONNECTING;
/**
* 当连接成功时,如果设置了连接成功回调,则执行
* @var callback * @var callback
*/ */
public $onConnect = null; public $onConnect = null;
/** /**
* 构造函数,创建连接 * Transport layer protocol.
* @param resource $socket *
* @param EventInterface $event * @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) public function __construct($remote_address)
{ {
// 获得协议及远程地址 $address_info = parse_url($remote_address);
list($scheme, $address) = explode(':', $remote_address, 2); if (!$address_info) {
if($scheme != 'tcp') echo new \Exception('bad remote_address');
{ $this->_remoteAddress = $remote_address;
// 判断协议类是否存在 } else {
$scheme = ucfirst($scheme); if (!isset($address_info['port'])) {
$this->protocol = '\\Protocols\\'.$scheme; $address_info['port'] = 80;
if(!class_exists($this->protocol)) }
{ if (!isset($address_info['path'])) {
$this->protocol = '\\Workerman\\Protocols\\' . $scheme; $address_info['path'] = '/';
if(!class_exists($this->protocol)) }
{ 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"); 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); // For statistics.
// 如果失败尝试触发失败回调(如果有回调的话) self::$statistics['connection_count']++;
if(!$this->_socket) $this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
{ }
$this->emitError(WORKERMAN_CONNECT_FAIL, $errstr);
/**
* Do connect.
*
* @return void
*/
public function connect()
{
if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && $this->_status !== self::STATUS_CLOSED) {
return; 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')); Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection'));
} }
/** /**
* 尝试触发失败回调 * Get remote address.
* @param int $code *
* @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 * @param string $msg
* @return void * @return void
*/ */
protected function emitError($code, $msg) protected function emitError($code, $msg)
{ {
if($this->onError) $this->_status = self::STATUS_CLOSING;
{ if ($this->onError) {
try{ try {
call_user_func($this->onError, $this, $code, $msg); call_user_func($this->onError, $this, $code, $msg);
} } catch (\Exception $e) {
catch(Exception $e) Worker::log($e);
{ exit(250);
echo $e; } catch (\Error $e) {
Worker::log($e);
exit(250);
} }
} }
} }
/** /**
* 检查连接状态,连接成功还是失败 * Check connection is successfully established or faild.
*
* @param resource $socket * @param resource $socket
* @return void * @return void
*/ */
public function checkConnection($socket) public function checkConnection($socket)
{ {
// 删除连接可写监听 // Check socket state.
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); if ($address = stream_socket_get_name($socket, true)) {
// 需要判断两次连接是否已经断开 // Remove write listener.
if(!feof($this->_socket) && !feof($this->_socket)) Worker::$globalEvent->del($socket, EventInterface::EV_WRITE);
{ // Nonblocking.
// 设置非阻塞 stream_set_blocking($socket, 0);
stream_set_blocking($this->_socket, 0); // Compatible with hhvm
// 监听可读事件 if (function_exists('stream_set_read_buffer')) {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); stream_set_read_buffer($socket, 0);
// 如果发送缓冲区有数据则执行发送
if($this->_sendBuffer)
{
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
} }
// 标记状态为连接已经建立 // Try to open keepalive for tcp and disable Nagle algorithm.
$this->_status = self::STATUS_ESTABLISH; if (function_exists('socket_import_stream') && $this->transport === 'tcp') {
// 为status 命令统计数据 $raw_socket = socket_import_stream($socket);
ConnectionInterface::$statistics['connection_count']++; socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1);
// 如果有设置onConnect回调则执行 socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1);
if($this->onConnect) }
{ // Register a listener waiting read event.
try 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); call_user_func($this->onConnect, $this);
} } catch (\Exception $e) {
catch(Exception $e) Worker::log($e);
{ exit(250);
self::$statistics['throw_exception']++; } catch (\Error $e) {
echo $e; Worker::log($e);
exit(250);
} }
} }
} // Try to emit protocol::onConnect
else if (method_exists($this->protocol, 'onConnect')) {
{ try {
// 连接未建立成功 call_user_func(array($this->protocol, 'onConnect'), $this);
$this->emitError(WORKERMAN_CONNECT_FAIL, 'connect fail'); } 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 <?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; namespace Workerman\Connection;
use Workerman\Events\Libevent;
use Workerman\Events\Select;
use Workerman\Events\EventInterface;
use Workerman\Worker;
use \Exception;
/** /**
* connection类的接口 * ConnectionInterface.
* @author walkor<walkor@workerman.net>
*/ */
abstract class ConnectionInterface abstract class ConnectionInterface
{ {
/** /**
* status命令的统计数据 * Statistics for status command.
*
* @var array * @var array
*/ */
public static $statistics = array( public static $statistics = array(
'connection_count'=>0, 'connection_count' => 0,
'total_request' => 0, 'total_request' => 0,
'throw_exception' => 0, 'throw_exception' => 0,
'send_fail' => 0, 'send_fail' => 0,
); );
/** /**
* 当收到数据时,如果有设置$onMessage回调则执行 * Emitted when data is received.
*
* @var callback * @var callback
*/ */
public $onMessage = null; public $onMessage = null;
/** /**
* 当连接关闭时,如果设置了$onClose回调则执行 * Emitted when the other end of the socket sends a FIN packet.
*
* @var callback * @var callback
*/ */
public $onClose = null; public $onClose = null;
/** /**
* 当出现错误时,如果设置了$onError回调则执行 * Emitted when an error occurs with connection.
*
* @var callback * @var callback
*/ */
public $onError = null; public $onError = null;
/** /**
* 发送数据给对端 * Sends data on the connection.
*
* @param string $send_buffer * @param string $send_buffer
* @return void|boolean * @return void|boolean
*/ */
abstract public function send($send_buffer); abstract public function send($send_buffer);
/** /**
* 获得远端ip * Get remote IP.
*
* @return string * @return string
*/ */
abstract public function getRemoteIp(); abstract public function getRemoteIp();
/** /**
* 获得远端端口 * Get remote port.
*
* @return int * @return int
*/ */
abstract public function getRemotePort(); abstract public function getRemotePort();
/** /**
* 关闭连接为了保持接口一致udp保留了此方法当是udp时调用此方法无任何作用 * Close connection.
* @void *
* @param $data
* @return void
*/ */
abstract public function close($data = null); abstract public function close($data = null);
} }

View File

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

View File

@ -1,145 +1,146 @@
<?php <?php
namespace Workerman\Events;
/** /**
* libevent * This file is part of workerman.
* @author walkor <walkor@workerman.net> *
* 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 class Libevent implements EventInterface
{ {
/** /**
* eventBase * Event base.
* @var object *
* @var resource
*/ */
protected $_eventBase = null; protected $_eventBase = null;
/** /**
* 所有的事件 * All listeners for read/write event.
*
* @var array * @var array
*/ */
protected $_allEvents = array(); protected $_allEvents = array();
/** /**
* 所有的信号事件 * Event listeners of signal.
*
* @var array * @var array
*/ */
protected $_eventSignal = array(); protected $_eventSignal = array();
/** /**
* 所有的定时事件 * All timer event listeners.
* [func, args, event, flag, time_interval] * [func, args, event, flag, time_interval]
*
* @var array * @var array
*/ */
protected $_eventTimer = array(); protected $_eventTimer = array();
/** /**
* 构造函数 * construct
* @return void
*/ */
public function __construct() public function __construct()
{ {
$this->_eventBase = event_base_new(); $this->_eventBase = event_base_new();
} }
/** /**
* 添加事件 * {@inheritdoc}
* @see EventInterface::add()
*/ */
public function add($fd, $flag, $func, $args=null) public function add($fd, $flag, $func, $args = array())
{ {
switch($flag) switch ($flag) {
{
case self::EV_SIGNAL: case self::EV_SIGNAL:
$fd_key = (int)$fd; $fd_key = (int)$fd;
$real_flag = EV_SIGNAL | EV_PERSIST; $real_flag = EV_SIGNAL | EV_PERSIST;
$this->_eventSignal[$fd_key] = event_new(); $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; return false;
} }
if(!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) {
{
return false; return false;
} }
if(!event_add($this->_eventSignal[$fd_key])) if (!event_add($this->_eventSignal[$fd_key])) {
{
return false; return false;
} }
return true; return true;
case self::EV_TIMER: case self::EV_TIMER:
case self::EV_TIMER_ONCE: case self::EV_TIMER_ONCE:
$event = event_new(); $event = event_new();
$timer_id = (int)$event; $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; return false;
} }
if(!event_base_set($event, $this->_eventBase)) if (!event_base_set($event, $this->_eventBase)) {
{
return false; return false;
} }
$time_interval = $fd*1000000; $time_interval = $fd * 1000000;
if(!event_add($event, $time_interval)) if (!event_add($event, $time_interval)) {
{
return false; return false;
} }
$this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval);
return $timer_id; return $timer_id;
default : default :
$fd_key = (int)$fd; $fd_key = (int)$fd;
$real_flag = $flag == self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST;
$event = event_new(); $event = event_new();
if(!event_set($event, $fd, $real_flag, $func, null)) if (!event_set($event, $fd, $real_flag, $func, null)) {
{
return false; return false;
} }
if(!event_base_set($event, $this->_eventBase)) if (!event_base_set($event, $this->_eventBase)) {
{
return false; return false;
} }
if(!event_add($event)) if (!event_add($event)) {
{
return false; return false;
} }
$this->_allEvents[$fd_key][$flag] = $event; $this->_allEvents[$fd_key][$flag] = $event;
return true; return true;
} }
} }
/** /**
* 删除事件 * {@inheritdoc}
* @see Events\EventInterface::del()
*/ */
public function del($fd ,$flag) public function del($fd, $flag)
{ {
switch($flag) switch ($flag) {
{
case self::EV_READ: case self::EV_READ:
case self::EV_WRITE: case self::EV_WRITE:
$fd_key = (int)$fd; $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]); event_del($this->_allEvents[$fd_key][$flag]);
unset($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]); unset($this->_allEvents[$fd_key]);
} }
break; break;
case self::EV_SIGNAL: case self::EV_SIGNAL:
$fd_key = (int)$fd; $fd_key = (int)$fd;
if(isset($this->_eventSignal[$fd_key])) if (isset($this->_eventSignal[$fd_key])) {
{
event_del($this->_eventSignal[$fd_key]); event_del($this->_eventSignal[$fd_key]);
unset($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:
case self::EV_TIMER_ONCE: case self::EV_TIMER_ONCE:
// 这里 fd 为timerid // 这里 fd 为timerid
if(isset($this->_eventTimer[$fd])) if (isset($this->_eventTimer[$fd])) {
{
event_del($this->_eventTimer[$fd][2]); event_del($this->_eventTimer[$fd][2]);
unset($this->_eventTimer[$fd]); unset($this->_eventTimer[$fd]);
} }
@ -156,48 +156,46 @@ class Libevent implements EventInterface
} }
return true; return true;
} }
/** /**
* 定时器回调 * Timer callback.
* @param null $_null *
* @param null $_null * @param mixed $_null1
* @param int $timer_id * @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]); 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]); 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) if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) {
{ $this->del($timer_id, self::EV_TIMER_ONCE);
echo $e;
} }
} }
/** /**
* 删除所有定时器 * {@inheritdoc}
* @return void
*/ */
public function clearAllTimer() public function clearAllTimer()
{ {
foreach($this->_eventTimer as $task_data) foreach ($this->_eventTimer as $task_data) {
{
event_del($task_data[2]); event_del($task_data[2]);
} }
$this->_eventTimer = array(); $this->_eventTimer = array();
} }
/** /**
* 事件循环 * {@inheritdoc}
* @see EventInterface::loop()
*/ */
public function loop() public function loop()
{ {

View File

@ -1,141 +1,163 @@
<?php <?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; namespace Workerman\Events;
/**
* select eventloop
*/
class Select implements EventInterface class Select implements EventInterface
{ {
/** /**
* 所有的事件 * All listeners for read/write event.
*
* @var array * @var array
*/ */
public $_allEvents = array(); public $_allEvents = array();
/** /**
* 所有信号事件 * Event listeners of signal.
*
* @var array * @var array
*/ */
public $_signalEvents = array(); public $_signalEvents = array();
/** /**
* 监听这些描述符的读事件 * Fds waiting for read event.
*
* @var array * @var array
*/ */
protected $_readFds = array(); protected $_readFds = array();
/** /**
* 监听这些描述符的写事件 * Fds waiting for write event.
*
* @var array * @var array
*/ */
protected $_writeFds = array(); protected $_writeFds = array();
/** /**
* 任务调度器,最大堆 * Timer scheduler.
* {['data':timer_id, 'priority':run_timestamp], ..} * {['data':timer_id, 'priority':run_timestamp], ..}
* @var SplPriorityQueue *
* @var \SplPriorityQueue
*/ */
protected $_scheduler = null; protected $_scheduler = null;
/** /**
* 定时任务 * All timer event listeners.
* [[func, args, flag, timer_interval], ..] * [[func, args, flag, timer_interval], ..]
*
* @var array * @var array
*/ */
protected $_task = array(); protected $_task = array();
/** /**
* 定时器id * Timer id.
*
* @var int * @var int
*/ */
protected $_timerId = 1; protected $_timerId = 1;
/** /**
* select超时时间单位微妙 * Select timeout.
*
* @var int * @var int
*/ */
protected $_selectTimeout = 100000000; protected $_selectTimeout = 100000000;
/** /**
* 构造函数 * Paired socket channels
* @return void *
* @var array
*/
protected $channel = array();
/**
* Construct.
*/ */
public function __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); $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); stream_set_blocking($this->channel[0], 0);
$this->_readFds[0] = $this->channel[0]; $this->_readFds[0] = $this->channel[0];
} }
// 初始化优先队列(最大堆) // Init SplPriorityQueue.
$this->_scheduler = new \SplPriorityQueue(); $this->_scheduler = new \SplPriorityQueue();
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
} }
/** /**
* 添加事件及处理函数 * {@inheritdoc}
* @see Events\EventInterface::add()
*/ */
public function add($fd, $flag, $func, $args = null) public function add($fd, $flag, $func, $args = array())
{ {
switch ($flag) switch ($flag) {
{
case self::EV_READ: case self::EV_READ:
$fd_key = (int)$fd; $fd_key = (int)$fd;
$this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_allEvents[$fd_key][$flag] = array($func, $fd);
$this->_readFds[$fd_key] = $fd; $this->_readFds[$fd_key] = $fd;
break; break;
case self::EV_WRITE: case self::EV_WRITE:
$fd_key = (int)$fd; $fd_key = (int)$fd;
$this->_allEvents[$fd_key][$flag] = array($func, $fd); $this->_allEvents[$fd_key][$flag] = array($func, $fd);
$this->_writeFds[$fd_key] = $fd; $this->_writeFds[$fd_key] = $fd;
break; break;
case self::EV_SIGNAL: case self::EV_SIGNAL:
$fd_key = (int)$fd; $fd_key = (int)$fd;
$this->_signalEvents[$fd_key][$flag] = array($func, $fd); $this->_signalEvents[$fd_key][$flag] = array($func, $fd);
pcntl_signal($fd, array($this, 'signalHandler')); pcntl_signal($fd, array($this, 'signalHandler'));
break; break;
case self::EV_TIMER: case self::EV_TIMER:
case self::EV_TIMER_ONCE: 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->_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(); $this->tick();
return $this->_timerId++; return $this->_timerId++;
} }
return true; return true;
} }
/** /**
* 信号处理函数 * Signal handler.
*
* @param int $signal * @param int $signal
*/ */
public function signalHandler($signal) public function signalHandler($signal)
{ {
call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal));
} }
/** /**
* 删除某个描述符的某类事件的监听 * {@inheritdoc}
* @see Events\EventInterface::del()
*/ */
public function del($fd ,$flag) public function del($fd, $flag)
{ {
$fd_key = (int)$fd; $fd_key = (int)$fd;
switch ($flag) switch ($flag) {
{
case self::EV_READ: case self::EV_READ:
unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); 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]); unset($this->_allEvents[$fd_key]);
} }
return true; return true;
case self::EV_WRITE: case self::EV_WRITE:
unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); 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]); unset($this->_allEvents[$fd_key]);
} }
return true; return true;
@ -145,67 +167,51 @@ class Select implements EventInterface
break; break;
case self::EV_TIMER: case self::EV_TIMER:
case self::EV_TIMER_ONCE; case self::EV_TIMER_ONCE;
// $fd_key为要删除的定时器id即timerId
unset($this->_task[$fd_key]); unset($this->_task[$fd_key]);
return true; return true;
} }
return false;; return false;
} }
/** /**
* 检查是否有可执行的定时任务,有的话执行 * Tick for timer.
*
* @return void * @return void
*/ */
protected function tick() protected function tick()
{ {
while(!$this->_scheduler->isEmpty()) while (!$this->_scheduler->isEmpty()) {
{ $scheduler_data = $this->_scheduler->top();
$scheduler_data = $this->_scheduler->top(); $timer_id = $scheduler_data['data'];
$timer_id = $scheduler_data['data']; $next_run_time = -$scheduler_data['priority'];
$next_run_time = -$scheduler_data['priority']; $time_now = microtime(true);
$time_now = microtime(true); $this->_selectTimeout = ($next_run_time - $time_now) * 1000000;
if($time_now >= $next_run_time) if ($this->_selectTimeout <= 0) {
{
$this->_scheduler->extract(); $this->_scheduler->extract();
// 如果任务不存在,则是对应的定时器已经删除 if (!isset($this->_task[$timer_id])) {
if(!isset($this->_task[$timer_id]))
{
continue; continue;
} }
// 任务数据[func, args, flag, timer_interval] // [func, args, flag, timer_interval]
$task_data = $this->_task[$timer_id]; $task_data = $this->_task[$timer_id];
// 如果是持续的定时任务,再把任务加到定时队列 if ($task_data[2] === self::EV_TIMER) {
if($task_data[2] == self::EV_TIMER) $next_run_time = $time_now + $task_data[3];
{
$next_run_time = $time_now+$task_data[3];
$this->_scheduler->insert($timer_id, -$next_run_time); $this->_scheduler->insert($timer_id, -$next_run_time);
} }
// 尝试执行任务 call_user_func_array($task_data[0], $task_data[1]);
try if (isset($this->_task[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
{ $this->del($timer_id, self::EV_TIMER_ONCE);
call_user_func_array($task_data[0], $task_data[1]);
}
catch(\Exception $e)
{
echo $e;
} }
continue; continue;
} }
else return;
{
// 设定超时时间
$this->_selectTimeout = ($next_run_time - $time_now)*1000000;
return;
}
} }
$this->_selectTimeout = 100000000; $this->_selectTimeout = 100000000;
} }
/** /**
* 删除所有定时器 * {@inheritdoc}
* @return void
*/ */
public function clearAllTimer() public function clearAllTimer()
{ {
@ -213,55 +219,45 @@ class Select implements EventInterface
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
$this->_task = array(); $this->_task = array();
} }
/** /**
* 主循环 * {@inheritdoc}
* @see Events\EventInterface::loop()
*/ */
public function loop() public function loop()
{ {
$e = null; $e = null;
while (1) while (1) {
{ // Calls signal handlers for pending signals
// 如果有信号,尝试执行信号处理函数
pcntl_signal_dispatch(); pcntl_signal_dispatch();
$read = $this->_readFds; $read = $this->_readFds;
$write = $this->_writeFds; $write = $this->_writeFds;
// 等待可读或者可写事件 // Waiting read/write/signal/timeout events.
@stream_select($read, $write, $e, 0, $this->_selectTimeout); $ret = @stream_select($read, $write, $e, 0, $this->_selectTimeout);
// 这些描述符可读,执行对应描述符的读回调函数 if (!$this->_scheduler->isEmpty()) {
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())
{
$this->tick(); $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 <?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'); date_default_timezone_set('Asia/Shanghai');
} }
// 显示错误到终端 // Display errors.
ini_set('display_errors', 'on'); ini_set('display_errors', 'on');
// Reporting all.
error_reporting(E_ALL);
// 连接失败 // For onError callback.
define('WORKERMAN_CONNECT_FAIL', 1); define('WORKERMAN_CONNECT_FAIL', 1);
// 发送失败 // For onError callback.
define('WORKERMAN_SEND_FAIL', 2); define('WORKERMAN_SEND_FAIL', 2);
// Compatible with php7
if(!class_exists('Error'))
{
class Error extends Exception
{
}
}

View File

@ -1,146 +1,142 @@
<?php <?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; namespace Workerman\Lib;
use \Workerman\Events\EventInterface;
use \Exception; use Workerman\Events\EventInterface;
use Exception;
/** /**
* * Timer.
* 定时器 *
* * example:
* <b>example:</b>
* <pre>
* <code>
* Workerman\Lib\Timer::init();
* Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..));
* <code>
* </pre>
* @author walkor <walkor@workerman.net>
*/ */
class Timer 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],..]],
* 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 * @var array
*/ */
protected static $_tasks = array(); protected static $_tasks = array();
/** /**
* event * event
* @var event *
* @var \Workerman\Events\EventInterface
*/ */
protected static $_event = null; protected static $_event = null;
/** /**
* 初始化 * Init.
*
* @param \Workerman\Events\EventInterface $event
* @return void * @return void
*/ */
public static function init($event = null) public static function init($event = null)
{ {
if($event) if ($event) {
{
self::$_event = $event; self::$_event = $event;
} } else {
else
{
pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false);
} }
} }
/** /**
* 信号处理函数只处理ALARM事件 * ALARM signal handler.
*
* @return void * @return void
*/ */
public static function signalHandle() public static function signalHandle()
{ {
if(!self::$_event) if (!self::$_event) {
{
pcntl_alarm(1); pcntl_alarm(1);
self::tick(); self::tick();
} }
} }
/** /**
* 添加一个定时器 * Add a timer.
* @param int $time_interval *
* @param int $time_interval
* @param callback $func * @param callback $func
* @param mix $args * @param mixed $args
* @return void * @param bool $persistent
* @return bool
*/ */
public static function add($time_interval, $func, $args = array(), $persistent = true) 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"); echo new Exception("bad time_interval");
return false; return false;
} }
if(self::$_event) if (self::$_event) {
{ return self::$_event->add($time_interval,
return self::$_event->add($time_interval, $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE , $func, $args); $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args);
} }
if(!is_callable($func)) if (!is_callable($func)) {
{
echo new Exception("not callable"); echo new Exception("not callable");
return false; return false;
} }
if(empty(self::$_tasks)) if (empty(self::$_tasks)) {
{
pcntl_alarm(1); pcntl_alarm(1);
} }
$time_now = time(); $time_now = time();
$run_time = $time_now + $time_interval; $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();
} }
self::$_tasks[$run_time][] = array($func, $args, $persistent, $time_interval); self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval);
return true; return true;
} }
/** /**
* 尝试触发定时回调 * Tick.
*
* @return void * @return void
*/ */
public static function tick() public static function tick()
{ {
if(empty(self::$_tasks)) if (empty(self::$_tasks)) {
{
pcntl_alarm(0); pcntl_alarm(0);
return; return;
} }
$time_now = time(); $time_now = time();
foreach (self::$_tasks as $run_time=>$task_data) foreach (self::$_tasks as $run_time => $task_data) {
{ if ($time_now >= $run_time) {
if($time_now >= $run_time) foreach ($task_data as $index => $one_task) {
{ $task_func = $one_task[0];
foreach($task_data as $index=>$one_task) $task_args = $one_task[1];
{ $persistent = $one_task[2];
$task_func = $one_task[0];
$task_args = $one_task[1];
$persistent = $one_task[2];
$time_interval = $one_task[3]; $time_interval = $one_task[3];
try try {
{
call_user_func_array($task_func, $task_args); call_user_func_array($task_func, $task_args);
} } catch (\Exception $e) {
catch(\Exception $e)
{
echo $e; echo $e;
} }
if($persistent) if ($persistent) {
{
self::add($time_interval, $task_func, $task_args); self::add($time_interval, $task_func, $task_args);
} }
} }
@ -148,28 +144,32 @@ class Timer
} }
} }
} }
/** /**
* 删除定时器 * Remove a timer.
* @param $timer_id *
* @param mixed $timer_id
* @return bool
*/ */
public static function del($timer_id) public static function del($timer_id)
{ {
if(self::$_event) if (self::$_event) {
{
return self::$_event->del($timer_id, EventInterface::EV_TIMER); return self::$_event->del($timer_id, EventInterface::EV_TIMER);
} }
return false;
} }
/** /**
* 删除所有定时 * Remove all timers.
*
* @return void
*/ */
public static function delAll() public static function delAll()
{ {
self::$_tasks = array(); self::$_tasks = array();
pcntl_alarm(0); pcntl_alarm(0);
if(self::$_event) if (self::$_event) {
{
self::$_event->clearAllTimer(); 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,476 +1,430 @@
<?php <?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\Connection\TcpConnection;
use Workerman\Worker;
/** /**
* http protocol * http protocol
* @author walkor<walkor@workerman.net>
*/ */
class Http class Http
{ {
/** /**
* 判断包长 * Check the integrity of the package.
* @param string $recv_buffer *
* @param string $recv_buffer
* @param TcpConnection $connection * @param TcpConnection $connection
* @return int * @return int
*/ */
public static function input($recv_buffer, TcpConnection $connection) public static function input($recv_buffer, TcpConnection $connection)
{ {
if(!strpos($recv_buffer, "\r\n\r\n")) if (!strpos($recv_buffer, "\r\n\r\n")) {
{ // Judge whether the package length exceeds the limit.
// 无法获得包长,避免客户端传递超大头部的数据包 if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) {
if(strlen($recv_buffer)>=TcpConnection::$maxPackageSize)
{
$connection->close(); $connection->close();
return 0; return 0;
} }
return 0; return 0;
} }
list($header, $body) = explode("\r\n\r\n", $recv_buffer, 2); list($header,) = explode("\r\n\r\n", $recv_buffer, 2);
if(0 === strpos($recv_buffer, "POST")) if (0 === strpos($recv_buffer, "POST")) {
{
// find Content-Length // find Content-Length
$match = array(); $match = array();
if(preg_match("/\r\nContent-Length: ?(\d+)/", $header, $match)) if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) {
{ $content_length = $match[1];
$content_lenght = $match[1]; return $content_length + strlen($header) + 4;
return $content_lenght + strlen($header) + 4; } else {
}
else
{
return 0; return 0;
} }
} } elseif (0 === strpos($recv_buffer, "GET")) {
else return strlen($header) + 4;
{ } else {
return strlen($header)+4; $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
} }
} }
/** /**
* 从http数据包中解析$_POST、$_GET、$_COOKIE等 * Parse $_POST、$_GET、$_COOKIE.
* @param string $recv_buffer *
* @param string $recv_buffer
* @param TcpConnection $connection * @param TcpConnection $connection
* @return void * @return array
*/ */
public static function decode($recv_buffer, TcpConnection $connection) public static function decode($recv_buffer, TcpConnection $connection)
{ {
// 初始化 // Init.
$_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array();
$GLOBALS['HTTP_RAW_POST_DATA'] = ''; $GLOBALS['HTTP_RAW_POST_DATA'] = '';
// 清空上次的数据 // Clear cache.
HttpCache::$header = array(); HttpCache::$header = array('Connection' => 'Connection: keep-alive');
HttpCache::$instance = new HttpCache(); HttpCache::$instance = new HttpCache();
// 需要设置的变量名 // $_SERVER
$_SERVER = array ( $_SERVER = array(
'QUERY_STRING' => '', 'QUERY_STRING' => '',
'REQUEST_METHOD' => '', 'REQUEST_METHOD' => '',
'REQUEST_URI' => '', 'REQUEST_URI' => '',
'SERVER_PROTOCOL' => '', 'SERVER_PROTOCOL' => '',
'SERVER_SOFTWARE' => 'workerman/3.0', 'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION,
'SERVER_NAME' => '', 'SERVER_NAME' => '',
'HTTP_HOST' => '', 'HTTP_HOST' => '',
'HTTP_USER_AGENT' => '', 'HTTP_USER_AGENT' => '',
'HTTP_ACCEPT' => '', 'HTTP_ACCEPT' => '',
'HTTP_ACCEPT_LANGUAGE' => '', 'HTTP_ACCEPT_LANGUAGE' => '',
'HTTP_ACCEPT_ENCODING' => '', 'HTTP_ACCEPT_ENCODING' => '',
'HTTP_COOKIE' => '', 'HTTP_COOKIE' => '',
'HTTP_CONNECTION' => '', 'HTTP_CONNECTION' => '',
'REMOTE_ADDR' => '', 'REMOTE_ADDR' => '',
'REMOTE_PORT' => '0', 'REMOTE_PORT' => '0',
); );
// 将header分割成数组 // Parse headers.
list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2);
$header_data = explode("\r\n", $http_header); $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]); unset($header_data[0]);
foreach($header_data as $content) foreach ($header_data as $content) {
{
// \r\n\r\n // \r\n\r\n
if(empty($content)) if (empty($content)) {
{
continue; continue;
} }
list($key, $value) = explode(':', $content, 2); list($key, $value) = explode(':', $content, 2);
$key = strtolower($key); $key = str_replace('-', '_', strtoupper($key));
$value = trim($value); $value = trim($value);
switch($key) $_SERVER['HTTP_' . $key] = $value;
{ switch ($key) {
// HTTP_HOST // HTTP_HOST
case 'host': case 'HOST':
$_SERVER['HTTP_HOST'] = $value; $tmp = explode(':', $value);
$tmp = explode(':', $value);
$_SERVER['SERVER_NAME'] = $tmp[0]; $_SERVER['SERVER_NAME'] = $tmp[0];
if(isset($tmp[1])) if (isset($tmp[1])) {
{
$_SERVER['SERVER_PORT'] = $tmp[1]; $_SERVER['SERVER_PORT'] = $tmp[1];
} }
break; break;
// cookie // cookie
case 'cookie': case 'COOKIE':
$_SERVER['HTTP_COOKIE'] = $value;
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
break; break;
// user-agent // content-type
case 'user-agent': case 'CONTENT_TYPE':
$_SERVER['HTTP_USER_AGENT'] = $value; if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) {
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))
{
$_SERVER['CONTENT_TYPE'] = $value; $_SERVER['CONTENT_TYPE'] = $value;
} } else {
else
{
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; $_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$http_post_boundary = '--'.$match[1]; $http_post_boundary = '--' . $match[1];
} }
break; break;
case 'CONTENT_LENGTH':
$_SERVER['CONTENT_LENGTH'] = $value;
break;
} }
} }
// 需要解析$_POST // Parse $_POST.
if($_SERVER['REQUEST_METHOD'] == 'POST') if ($_SERVER['REQUEST_METHOD'] === 'POST') {
{ if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'multipart/form-data') {
if(isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] == 'multipart/form-data')
{
self::parseUploadFiles($http_body, $http_post_boundary); self::parseUploadFiles($http_body, $http_post_boundary);
} } else {
else
{
parse_str($http_body, $_POST); parse_str($http_body, $_POST);
// $GLOBALS['HTTP_RAW_POST_DATA'] // $GLOBALS['HTTP_RAW_POST_DATA']
$GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body;
} }
} }
// QUERY_STRING // QUERY_STRING
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
if($_SERVER['QUERY_STRING']) if ($_SERVER['QUERY_STRING']) {
{
// $GET // $GET
parse_str($_SERVER['QUERY_STRING'], $_GET); parse_str($_SERVER['QUERY_STRING'], $_GET);
} } else {
else
{
$_SERVER['QUERY_STRING'] = ''; $_SERVER['QUERY_STRING'] = '';
} }
// REQUEST // REQUEST
$_REQUEST = array_merge($_GET, $_POST); $_REQUEST = array_merge($_GET, $_POST);
// REMOTE_ADDR REMOTE_PORT // REMOTE_ADDR REMOTE_PORT
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); $_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
return $recv_buffer; return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES);
} }
/** /**
* 编码增加HTTP头 * Http encode.
* @param string $content *
* @param string $content
* @param TcpConnection $connection * @param TcpConnection $connection
* @return string * @return string
*/ */
public static function encode($content, TcpConnection $connection) public static function encode($content, TcpConnection $connection)
{ {
// 没有http-code默认给个 // Default http-code.
if(!isset(HttpCache::$header['Http-Code'])) if (!isset(HttpCache::$header['Http-Code'])) {
{
$header = "HTTP/1.1 200 OK\r\n"; $header = "HTTP/1.1 200 OK\r\n";
} } else {
else $header = HttpCache::$header['Http-Code'] . "\r\n";
{
$header = HttpCache::$header['Http-Code']."\r\n";
unset(HttpCache::$header['Http-Code']); unset(HttpCache::$header['Http-Code']);
} }
// Content-Type // Content-Type
if(!isset(HttpCache::$header['Content-Type'])) if (!isset(HttpCache::$header['Content-Type'])) {
{
$header .= "Content-Type: text/html;charset=utf-8\r\n"; $header .= "Content-Type: text/html;charset=utf-8\r\n";
} }
// other headers // other headers
foreach(HttpCache::$header as $key=>$item) foreach (HttpCache::$header as $key => $item) {
{ if ('Set-Cookie' === $key && is_array($item)) {
if('Set-Cookie' == $key && is_array($item)) foreach ($item as $it) {
{ $header .= $it . "\r\n";
foreach($item as $it)
{
$header .= $it."\r\n";
} }
} } else {
else $header .= $item . "\r\n";
{
$header .= $item."\r\n";
} }
} }
// header // 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 // save session
self::sessionWriteClose(); self::sessionWriteClose();
// the whole http package // the whole http package
return $header.$content; return $header . $content;
} }
/** /**
* 设置http头 * 设置http头
* @return bool *
* @return bool|void
*/ */
public static function header($content, $replace = true, $http_response_code = 0) 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); 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'; $key = 'Http-Code';
} } else {
else
{
$key = strstr($content, ":", true); $key = strstr($content, ":", true);
if(empty($key)) if (empty($key)) {
{
return false; return false;
} }
} }
if('location' == strtolower($key) && !$http_response_code) if ('location' === strtolower($key) && !$http_response_code) {
{
return self::header($content, true, 302); return self::header($content, true, 302);
} }
if(isset(HttpCache::$codes[$http_response_code])) if (isset(HttpCache::$codes[$http_response_code])) {
{ HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . 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($key == 'Http-Code')
{
return true; return true;
} }
} }
if($key == 'Set-Cookie') if ($key === 'Set-Cookie') {
{
HttpCache::$header[$key][] = $content; HttpCache::$header[$key][] = $content;
} } else {
else
{
HttpCache::$header[$key] = $content; HttpCache::$header[$key] = $content;
} }
return true; return true;
} }
/** /**
* 删除一个header * Remove header.
*
* @param string $name * @param string $name
* @return void * @return void
*/ */
public static function headerRemove($name) public static function headerRemove($name)
{ {
if(PHP_SAPI != 'cli') if (PHP_SAPI != 'cli') {
{ header_remove($name);
return header_remove($name); return;
} }
unset( HttpCache::$header[$name]); unset(HttpCache::$header[$name]);
} }
/** /**
* 设置cookie * Set cookie.
* @param string $name *
* @param string $value * @param string $name
* @param string $value
* @param integer $maxage * @param integer $maxage
* @param string $path * @param string $path
* @param string $domain * @param string $domain
* @param bool $secure * @param bool $secure
* @param bool $HTTPOnly * @param bool $HTTPOnly
* @return bool|void
*/ */
public static function setcookie($name, $value = '', $maxage = 0, $path = '', $domain = '', $secure = false, $HTTPOnly = false) { public static function setcookie(
if(PHP_SAPI != 'cli') $name,
{ $value = '',
$maxage = 0,
$path = '',
$domain = '',
$secure = false,
$HTTPOnly = false
) {
if (PHP_SAPI != 'cli') {
return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly);
} }
return self::header( return self::header(
'Set-Cookie: ' . $name . '=' . rawurlencode($value) 'Set-Cookie: ' . $name . '=' . rawurlencode($value)
. (empty($domain) ? '' : '; Domain=' . $domain) . (empty($domain) ? '' : '; Domain=' . $domain)
. (empty($maxage) ? '' : '; Max-Age=' . $maxage) . (empty($maxage) ? '' : '; Max-Age=' . $maxage)
. (empty($path) ? '' : '; Path=' . $path) . (empty($path) ? '' : '; Path=' . $path)
. (!$secure ? '' : '; Secure') . (!$secure ? '' : '; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false); . (!$HTTPOnly ? '' : '; HttpOnly'), false);
} }
/** /**
* sessionStart * sessionStart
*
* @return bool * @return bool
*/ */
public static function sessionStart() public static function sessionStart()
{ {
if(PHP_SAPI != 'cli') if (PHP_SAPI != 'cli') {
{
return session_start(); return session_start();
} }
if(HttpCache::$instance->sessionStarted) if (HttpCache::$instance->sessionStarted) {
{
echo "already sessionStarted\nn"; echo "already sessionStarted\nn";
return true; return true;
} }
HttpCache::$instance->sessionStarted = true; HttpCache::$instance->sessionStarted = true;
// 没有sid则创建一个session文件生成一个sid // Generate a SID.
if(!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName])) if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) {
{ $file_name = tempnam(HttpCache::$sessionPath, 'ses');
$file_name = tempnam(HttpCache::$sessionPath, 'sess_'); if (!$file_name) {
if(!$file_name)
{
return false; return false;
} }
HttpCache::$instance->sessionFile = $file_name; HttpCache::$instance->sessionFile = $file_name;
$session_id = substr(basename($file_name), strlen('sess_')); $session_id = substr(basename($file_name), strlen('ses'));
return self::setcookie( return self::setcookie(
HttpCache::$sessionName HttpCache::$sessionName
, $session_id , $session_id
, ini_get('session.cookie_lifetime') , ini_get('session.cookie_lifetime')
, ini_get('session.cookie_path') , ini_get('session.cookie_path')
, ini_get('session.cookie_domain') , ini_get('session.cookie_domain')
, ini_get('session.cookie_secure') , ini_get('session.cookie_secure')
, ini_get('session.cookie_httponly') , ini_get('session.cookie_httponly')
); );
} }
if(!HttpCache::$instance->sessionFile) if (!HttpCache::$instance->sessionFile) {
{ HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName];
HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName];
} }
// 有sid则打开文件读取session值 // Read session from session file.
if(HttpCache::$instance->sessionFile) if (HttpCache::$instance->sessionFile) {
{
$raw = file_get_contents(HttpCache::$instance->sessionFile); $raw = file_get_contents(HttpCache::$instance->sessionFile);
if($raw) if ($raw) {
{
session_decode($raw); session_decode($raw);
} }
} }
return true;
} }
/** /**
* 保存session * Save session.
*
* @return bool * @return bool
*/ */
public static function sessionWriteClose() public static function sessionWriteClose()
{ {
if(PHP_SAPI != 'cli') if (PHP_SAPI != 'cli') {
{
return session_write_close(); return session_write_close();
} }
if(!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) {
{
$session_str = session_encode(); $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); return file_put_contents(HttpCache::$instance->sessionFile, $session_str);
} }
} }
return empty($_SESSION); return empty($_SESSION);
} }
/** /**
* 退出 * End, like call exit in php-fpm.
*
* @param string $msg * @param string $msg
* @throws \Exception * @throws \Exception
*/ */
public static function end($msg = '') public static function end($msg = '')
{ {
if(PHP_SAPI != 'cli') if (PHP_SAPI != 'cli') {
{
exit($msg); exit($msg);
} }
if($msg) if ($msg) {
{
echo $msg; echo $msg;
} }
throw new \Exception('jump_exit'); throw new \Exception('jump_exit');
} }
/** /**
* get mime types * Get mime types.
*
* @return string
*/ */
public static function getMimeTypesFile() 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)); $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); $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body);
if($boundary_data_array[0] === '') if ($boundary_data_array[0] === '') {
{
unset($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); 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); $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); list($header_key, $header_value) = explode(": ", $item);
$header_key = strtolower($header_key); $header_key = strtolower($header_key);
switch ($header_key) switch ($header_key) {
{
case "content-disposition": case "content-disposition":
// 是文件 // Is file data.
if(preg_match('/name=".*?"; filename="(.*?)"$/', $header_value, $match)) if (preg_match('/name=".*?"; filename="(.*?)"$/', $header_value, $match)) {
{ // Parse $_FILES.
$_FILES[] = array( $_FILES[] = array(
'file_name' => $match[1], 'file_name' => $match[1],
'file_data' => $boundary_value, 'file_data' => $boundary_value,
'file_size' => strlen($boundary_value), 'file_size' => strlen($boundary_value),
); );
continue; continue;
} } // Is post field.
// 是post field else {
else // Parse $_POST.
{ if (preg_match('/name="(.*?)"$/', $header_value, $match)) {
// 收集post
if(preg_match('/name="(.*?)"$/', $header_value, $match))
{
$_POST[$match[1]] = $boundary_value; $_POST[$match[1]] = $boundary_value;
} }
} }
@ -482,57 +436,61 @@ class Http
} }
/** /**
* 解析http协议数据包 缓存先关 * Http cache for the current http response.
* @author walkor
*/ */
class HttpCache class HttpCache
{ {
public static $codes = array( public static $codes = array(
100 => 'Continue', 100 => 'Continue',
101 => 'Switching Protocols', 101 => 'Switching Protocols',
200 => 'OK', 200 => 'OK',
201 => 'Created', 201 => 'Created',
202 => 'Accepted', 202 => 'Accepted',
203 => 'Non-Authoritative Information', 203 => 'Non-Authoritative Information',
204 => 'No Content', 204 => 'No Content',
205 => 'Reset Content', 205 => 'Reset Content',
206 => 'Partial Content', 206 => 'Partial Content',
300 => 'Multiple Choices', 300 => 'Multiple Choices',
301 => 'Moved Permanently', 301 => 'Moved Permanently',
302 => 'Found', 302 => 'Found',
303 => 'See Other', 303 => 'See Other',
304 => 'Not Modified', 304 => 'Not Modified',
305 => 'Use Proxy', 305 => 'Use Proxy',
306 => '(Unused)', 306 => '(Unused)',
307 => 'Temporary Redirect', 307 => 'Temporary Redirect',
400 => 'Bad Request', 400 => 'Bad Request',
401 => 'Unauthorized', 401 => 'Unauthorized',
402 => 'Payment Required', 402 => 'Payment Required',
403 => 'Forbidden', 403 => 'Forbidden',
404 => 'Not Found', 404 => 'Not Found',
405 => 'Method Not Allowed', 405 => 'Method Not Allowed',
406 => 'Not Acceptable', 406 => 'Not Acceptable',
407 => 'Proxy Authentication Required', 407 => 'Proxy Authentication Required',
408 => 'Request Timeout', 408 => 'Request Timeout',
409 => 'Conflict', 409 => 'Conflict',
410 => 'Gone', 410 => 'Gone',
411 => 'Length Required', 411 => 'Length Required',
412 => 'Precondition Failed', 412 => 'Precondition Failed',
413 => 'Request Entity Too Large', 413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long', 414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type', 415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable', 416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed', 417 => 'Expectation Failed',
422 => 'Unprocessable Entity', 422 => 'Unprocessable Entity',
423 => 'Locked', 423 => 'Locked',
500 => 'Internal Server Error', 500 => 'Internal Server Error',
501 => 'Not Implemented', 501 => 'Not Implemented',
502 => 'Bad Gateway', 502 => 'Bad Gateway',
503 => 'Service Unavailable', 503 => 'Service Unavailable',
504 => 'Gateway Timeout', 504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported', 505 => 'HTTP Version Not Supported',
); );
/**
* @var HttpCache
*/
public static $instance = null; public static $instance = null;
public static $header = array(); public static $header = array();
public static $sessionPath = ''; public static $sessionPath = '';
public static $sessionName = ''; public static $sessionName = '';
@ -543,8 +501,7 @@ class HttpCache
{ {
self::$sessionName = ini_get('session.name'); self::$sessionName = ini_get('session.name');
self::$sessionPath = session_save_path(); self::$sessionPath = session_save_path();
if(!self::$sessionPath) if (!self::$sessionPath) {
{
self::$sessionPath = sys_get_temp_dir(); self::$sessionPath = sys_get_temp_dir();
} }
@\session_start(); @\session_start();

View File

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

View File

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

View File

@ -1,221 +1,258 @@
<?php <?php
namespace Workerman\Protocols;
/** /**
* WebSocket 协议服务端解包和打包 * This file is part of workerman.
* @author walkor <walkor@workerman.net> *
* 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;
use Workerman\Worker;
/**
* WebSocket protocol.
*/
class Websocket implements \Workerman\Protocols\ProtocolInterface class Websocket implements \Workerman\Protocols\ProtocolInterface
{ {
/** /**
* websocket头部最小长度 * Minimum head length of websocket protocol.
*
* @var int * @var int
*/ */
const MIN_HEAD_LEN = 6; const MIN_HEAD_LEN = 2;
/** /**
* websocket blob类型 * Websocket blob type.
* @var char *
* @var string
*/ */
const BINARY_TYPE_BLOB = "\x81"; const BINARY_TYPE_BLOB = "\x81";
/** /**
* websocket arraybuffer类型 * Websocket arraybuffer type.
* @var char *
* @var string
*/ */
const BINARY_TYPE_ARRAYBUFFER = "\x82"; const BINARY_TYPE_ARRAYBUFFER = "\x82";
/** /**
* 检查包的完整性 * Check the integrity of the package.
* @param string $buffer *
* @param string $buffer
* @param ConnectionInterface $connection
* @return int
*/ */
public static function input($buffer, ConnectionInterface $connection) public static function input($buffer, ConnectionInterface $connection)
{ {
// 数据长度 // Receive length.
$recv_len = strlen($buffer); $recv_len = strlen($buffer);
// 长度不够 // We need more data.
if($recv_len < self::MIN_HEAD_LEN) if ($recv_len < self::MIN_HEAD_LEN) {
{
return 0; return 0;
} }
// 还没有握手 // Has not yet completed the handshake.
if(empty($connection->websocketHandshake)) if (empty($connection->websocketHandshake)) {
{
return self::dealHandshake($buffer, $connection); return self::dealHandshake($buffer, $connection);
} }
// $connection->websocketCurrentFrameLength有值说明当前fin为0则缓冲websocket帧数据 // Buffer websocket frame data.
if($connection->websocketCurrentFrameLength) if ($connection->websocketCurrentFrameLength) {
{ // We need more frame data.
// 如果当前帧数据未收全,则继续收 if ($connection->websocketCurrentFrameLength > $recv_len) {
if($connection->websocketCurrentFrameLength > $recv_len) // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
{
// 返回0因为不清楚完整的数据包长度需要等待fin=1的帧
return 0; return 0;
} }
} } else {
else $data_len = ord($buffer[1]) & 127;
{ $firstbyte = ord($buffer[0]);
$data_len = ord($buffer[1]) & 127; $is_fin_frame = $firstbyte >> 7;
$firstbyte = ord($buffer[0]); $opcode = $firstbyte & 0xf;
$is_fin_frame = $firstbyte>>7; switch ($opcode) {
$opcode = $firstbyte & 0xf;
switch($opcode)
{
// 附加数据帧 @todo 实现附加数据帧
case 0x0: case 0x0:
break; break;
// 文本数据帧 // Blob type.
case 0x1: case 0x1:
break; break;
// 二进制数据帧 // Arraybuffer type.
case 0x2: case 0x2:
break; break;
// 关闭的包 // Close package.
case 0x8: case 0x8:
// 如果有设置onWebSocketClose回调尝试执行 // Try to emit onWebSocketClose callback.
if(isset($connection->onWebSocketClose)) if (isset($connection->onWebSocketClose)) {
{ try {
call_user_func($connection->onWebSocketClose, $connection); call_user_func($connection->onWebSocketClose, $connection);
} } catch (\Exception $e) {
// 默认行为是关闭连接 Worker::log($e);
else exit(250);
{ } catch (\Error $e) {
Worker::log($e);
exit(250);
}
} // Close connection.
else {
$connection->close(); $connection->close();
} }
return 0; return 0;
// ping的包 // Ping package.
case 0x9: case 0x9:
// 如果有设置onWebSocketPing回调尝试执行 // Try to emit onWebSocketPing callback.
if(isset($connection->onWebSocketPing)) if (isset($connection->onWebSocketPing)) {
{ try {
call_user_func($connection->onWebSocketPing, $connection); call_user_func($connection->onWebSocketPing, $connection);
} } catch (\Exception $e) {
// 默认发送pong Worker::log($e);
else exit(250);
{ } catch (\Error $e) {
Worker::log($e);
exit(250);
}
} // Send pong package to client.
else {
$connection->send(pack('H*', '8a00'), true); $connection->send(pack('H*', '8a00'), true);
} }
// 从接受缓冲区中消费掉该数据包
if(!$data_len) // Consume data from receive buffer.
{ if (!$data_len) {
$connection->consumeRecvBuffer(self::MIN_HEAD_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; return 0;
} }
break; break;
// pong的包 // Pong package.
case 0xa: case 0xa:
// 如果有设置onWebSocketPong回调尝试执行 // Try to emit onWebSocketPong callback.
if(isset($connection->onWebSocketPong)) if (isset($connection->onWebSocketPong)) {
{ try {
call_user_func($connection->onWebSocketPong, $connection); 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) if (!$data_len) {
{
$connection->consumeRecvBuffer(self::MIN_HEAD_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; return 0;
} }
break; break;
// 错误的opcode // Wrong opcode.
default : default :
echo "error opcode $opcode and close websocket connection\n"; echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n";
$connection->close(); $connection->close();
return 0; return 0;
} }
// websocket二进制数据 // Calculate packet length.
$head_len = self::MIN_HEAD_LEN; $head_len = 6;
if ($data_len === 126) { if ($data_len === 126) {
$head_len = 8; $head_len = 8;
if($head_len > $recv_len) if ($head_len > $recv_len) {
{
return 0; return 0;
} }
$pack = unpack('ntotal_len', substr($buffer, 2, 2)); $pack = unpack('nn/ntotal_len', $buffer);
$data_len = $pack['total_len']; $data_len = $pack['total_len'];
} else if ($data_len === 127) { } else {
$head_len = 14; if ($data_len === 127) {
if($head_len > $recv_len) $head_len = 14;
{ if ($head_len > $recv_len) {
return 0; 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; $current_frame_length = $head_len + $data_len;
if($is_fin_frame) if ($is_fin_frame) {
{
return $current_frame_length; return $current_frame_length;
} } else {
else
{
$connection->websocketCurrentFrameLength = $current_frame_length; $connection->websocketCurrentFrameLength = $current_frame_length;
} }
} }
// 收到的数据刚好是一个frame // Received just a frame length data.
if($connection->websocketCurrentFrameLength == $recv_len) if ($connection->websocketCurrentFrameLength === $recv_len) {
{
self::decode($buffer, $connection); self::decode($buffer, $connection);
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
$connection->websocketCurrentFrameLength = 0; $connection->websocketCurrentFrameLength = 0;
return 0; return 0;
} } // The length of the received data is greater than the length of a frame.
// 收到的数据大于一个frame elseif ($connection->websocketCurrentFrameLength < $recv_len) {
elseif($connection->websocketCurrentFrameLength < $recv_len)
{
self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
$current_frame_length = $connection->websocketCurrentFrameLength; $current_frame_length = $connection->websocketCurrentFrameLength;
$connection->websocketCurrentFrameLength = 0; $connection->websocketCurrentFrameLength = 0;
// 继续读取下一个frame // Continue to read next frame.
return self::input(substr($buffer, $current_frame_length), $connection); return self::input(substr($buffer, $current_frame_length), $connection);
} } // The length of the received data is less than the length of a frame.
// 收到的数据不足一个frame else {
else
{
return 0; return 0;
} }
} }
/** /**
* 打包 * Websocket encode.
* @param string $buffer *
* @param string $buffer
* @param ConnectionInterface $connection
* @return string * @return string
*/ */
public static function encode($buffer, ConnectionInterface $connection) public static function encode($buffer, ConnectionInterface $connection)
{ {
if (!is_scalar($buffer)) {
throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. ");
}
$len = strlen($buffer); $len = strlen($buffer);
// 还没握手不能发数据 if (empty($connection->websocketType)) {
if(empty($connection->websocketHandshake)) $connection->websocketType = self::BINARY_TYPE_BLOB;
{
$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;
} }
$first_byte = $connection->websocketType; $first_byte = $connection->websocketType;
if($len<=125) if ($len <= 125) {
{ $encode_buffer = $first_byte . chr($len) . $buffer;
return $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)
{ // Handshake not completed so temporary buffer websocket data waiting for send.
return $first_byte.chr(126).pack("n", $len).$buffer; if (empty($connection->websocketHandshake)) {
} if (empty($connection->tmpWebsocketData)) {
else $connection->tmpWebsocketData = '';
{ }
return $first_byte.chr(127).pack("xxxxN", $len).$buffer; $connection->tmpWebsocketData .= $encode_buffer;
// Return empty string.
return '';
} }
return $encode_buffer;
} }
/** /**
* 解包 * Websocket decode.
* @param string $buffer *
* @param string $buffer
* @param ConnectionInterface $connection
* @return string * @return string
*/ */
public static function decode($buffer, ConnectionInterface $connection) public static function decode($buffer, ConnectionInterface $connection)
@ -224,166 +261,176 @@ class Websocket implements \Workerman\Protocols\ProtocolInterface
$len = ord($buffer[1]) & 127; $len = ord($buffer[1]) & 127;
if ($len === 126) { if ($len === 126) {
$masks = substr($buffer, 4, 4); $masks = substr($buffer, 4, 4);
$data = substr($buffer, 8); $data = substr($buffer, 8);
} else if ($len === 127) {
$masks = substr($buffer, 10, 4);
$data = substr($buffer, 14);
} else { } else {
$masks = substr($buffer, 2, 4); if ($len === 127) {
$data = substr($buffer, 6); $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++) { for ($index = 0; $index < strlen($data); $index++) {
$decoded .= $data[$index] ^ $masks[$index % 4]; $decoded .= $data[$index] ^ $masks[$index % 4];
} }
if($connection->websocketCurrentFrameLength) if ($connection->websocketCurrentFrameLength) {
{
$connection->websocketDataBuffer .= $decoded; $connection->websocketDataBuffer .= $decoded;
return $connection->websocketDataBuffer; return $connection->websocketDataBuffer;
} } else {
else if ($connection->websocketDataBuffer !== '') {
{ $decoded = $connection->websocketDataBuffer . $decoded;
$decoded = $connection->websocketDataBuffer . $decoded; $connection->websocketDataBuffer = '';
$connection->websocketDataBuffer = ''; }
return $decoded; return $decoded;
} }
} }
/** /**
* 处理websocket握手 * Websocket handshake.
* @param string $buffer *
* @param TcpConnection $connection * @param string $buffer
* @param \Workerman\Connection\TcpConnection $connection
* @return int * @return int
*/ */
protected static function dealHandshake($buffer, $connection) protected static function dealHandshake($buffer, $connection)
{ {
// 握手阶段客户端发送HTTP协议 // HTTP protocol.
if(0 === strpos($buffer, 'GET')) if (0 === strpos($buffer, 'GET')) {
{ // Find \r\n\r\n.
// 判断\r\n\r\n边界
$heder_end_pos = strpos($buffer, "\r\n\r\n"); $heder_end_pos = strpos($buffer, "\r\n\r\n");
if(!$heder_end_pos) if (!$heder_end_pos) {
{
return 0; return 0;
} }
$header_length = $heder_end_pos + 4;
// 解析Sec-WebSocket-Key
// Get Sec-WebSocket-Key.
$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]; $Sec_WebSocket_Key = $match[1];
} } else {
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->send("HTTP/1.1 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Sec-WebSocket-Key not found", true);
$connection->close(); $connection->close();
return 0; return 0;
} }
$new_key = base64_encode(sha1($Sec_WebSocket_Key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true)); // Calculation websocket key.
// 握手返回的数据 $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
$new_message = "HTTP/1.1 101 Switching Protocols\r\n"; // Handshake response data.
$new_message .= "Upgrade: websocket\r\n"; $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n";
$new_message .= "Sec-WebSocket-Version: 13\r\n"; $handshake_message .= "Upgrade: websocket\r\n";
$new_message .= "Connection: Upgrade\r\n"; $handshake_message .= "Sec-WebSocket-Version: 13\r\n";
$new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\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; $connection->websocketHandshake = true;
// Websocket data buffer.
$connection->websocketDataBuffer = ''; $connection->websocketDataBuffer = '';
// Current websocket frame length.
$connection->websocketCurrentFrameLength = 0; $connection->websocketCurrentFrameLength = 0;
// Current websocket frame data.
$connection->websocketCurrentFrameBuffer = ''; $connection->websocketCurrentFrameBuffer = '';
$connection->consumeRecvBuffer(strlen($buffer)); // Consume handshake data.
$connection->send($new_message, true); $connection->consumeRecvBuffer($header_length);
// blob or arraybuffer // Send handshake response.
$connection->websocketType = self::BINARY_TYPE_BLOB; $connection->send($handshake_message, true);
// 如果有设置onWebSocketConnect回调尝试执行
if(isset($connection->onWebSocketConnect)) // There are data waiting to be sent.
{ if (!empty($connection->tmpWebsocketData)) {
self::parseHttpHeader($buffer); $connection->send($connection->tmpWebsocketData, true);
try $connection->tmpWebsocketData = '';
{
call_user_func($connection->onWebSocketConnect, $connection, $buffer);
}
catch(\Exception $e)
{
echo $e;
}
$_GET = $_COOKIE = $_SERVER = array();
} }
// blob or arraybuffer
if (empty($connection->websocketType)) {
$connection->websocketType = self::BINARY_TYPE_BLOB;
}
// Try to emit onWebSocketConnect callback.
if (isset($connection->onWebSocketConnect)) {
self::parseHttpHeader($buffer);
try {
call_user_func($connection->onWebSocketConnect, $connection, $buffer);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) {
$connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION);
}
$_GET = $_SERVER = $_SESSION = $_COOKIE = array();
}
if (strlen($buffer) > $header_length) {
return self::input(substr($buffer, $header_length), $connection);
}
return 0; return 0;
} } // Is flash policy-file-request.
// 如果是flash的policy-file-request elseif (0 === strpos($buffer, '<polic')) {
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";
{
$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->send($policy_xml, true);
$connection->consumeRecvBuffer(strlen($buffer)); $connection->consumeRecvBuffer(strlen($buffer));
return 0; return 0;
} }
// 出错 // 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->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(); $connection->close();
return 0; return 0;
} }
/** /**
* 从header中获取 * Parse http header.
*
* @param string $buffer * @param string $buffer
* @return void * @return void
*/ */
protected static function parseHttpHeader($buffer) protected static function parseHttpHeader($buffer)
{ {
$header_data = explode("\r\n", $buffer); // Parse headers.
$_SERVER = array(); list($http_header, ) = explode("\r\n\r\n", $buffer, 2);
list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', $header_data[0]); $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]); unset($header_data[0]);
foreach($header_data as $content) foreach ($header_data as $content) {
{
// \r\n\r\n // \r\n\r\n
if(empty($content)) if (empty($content)) {
{
continue; continue;
} }
list($key, $value) = explode(':', $content, 2); list($key, $value) = explode(':', $content, 2);
$key = strtolower($key); $key = str_replace('-', '_', strtoupper($key));
$value = trim($value); $value = trim($value);
switch($key) $_SERVER['HTTP_' . $key] = $value;
{ switch ($key) {
// HTTP_HOST // HTTP_HOST
case 'host': case 'HOST':
$_SERVER['HTTP_HOST'] = $value; $tmp = explode(':', $value);
$tmp = explode(':', $value);
$_SERVER['SERVER_NAME'] = $tmp[0]; $_SERVER['SERVER_NAME'] = $tmp[0];
if(isset($tmp[1])) if (isset($tmp[1])) {
{
$_SERVER['SERVER_PORT'] = $tmp[1]; $_SERVER['SERVER_PORT'] = $tmp[1];
} }
break; break;
// HTTP_COOKIE // cookie
case 'cookie': case 'COOKIE':
$_SERVER['HTTP_COOKIE'] = $value;
parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
break; 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 // QUERY_STRING
$_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
if($_SERVER['QUERY_STRING']) if ($_SERVER['QUERY_STRING']) {
{
// $GET // $GET
parse_str($_SERVER['QUERY_STRING'], $_GET); parse_str($_SERVER['QUERY_STRING'], $_GET);
} } else {
else
{
$_SERVER['QUERY_STRING'] = ''; $_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 <?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; namespace Workerman;
use \Workerman\Worker; use Workerman\Protocols\Http;
use \Workerman\Protocols\Http; use Workerman\Protocols\HttpCache;
use \Workerman\Protocols\HttpCache;
/** /**
* * WebServer.
* 基于Worker实现的一个简单的WebServer
* 支持静态文件、支持文件上传、支持POST
* HTTP协议
*
* @author walkor <walkor@workerman.net>
*/ */
class WebServer extends Worker class WebServer extends Worker
{ {
/** /**
* 默认mime类型 * Virtual host to path mapping.
* @var string *
*/
protected static $defaultMimeType = 'text/html; charset=utf-8';
/**
* 服务器名到文件路径的转换
* @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www']
*/ */
protected $serverRoot = array(); protected $serverRoot = array();
/** /**
* mime类型映射关系 * Mime mapping.
*
* @var array * @var array
*/ */
protected static $mimeTypeMap = array(); protected static $mimeTypeMap = array();
/** /**
* 用来保存用户设置的onWorkerStart回调 * Used to save user OnWorkerStart callback settings.
*
* @var callback * @var callback
*/ */
protected $_onWorkerStart = null; protected $_onWorkerStart = null;
/** /**
* 添加站点域名与站点目录的对应关系类似nginx的 * Add virtual host.
*
* @param string $domain * @param string $domain
* @param string $root_path * @param string $root_path
* @return void * @return void
*/ */
public function addRoot($domain, $root_path) public function addRoot($domain, $root_path)
{ {
$this->serverRoot[$domain] = $root_path; $this->serverRoot[$domain] = $root_path;
} }
/** /**
* 构造函数 * Construct.
*
* @param string $socket_name * @param string $socket_name
* @param array $context_option * @param array $context_option
*/ */
public function __construct($socket_name, $context_option = array()) public function __construct($socket_name, $context_option = array())
{ {
list($scheme, $address) = explode(':', $socket_name, 2); list(, $address) = explode(':', $socket_name, 2);
parent::__construct('http:'.$address, $context_option); parent::__construct('http:' . $address, $context_option);
$this->name = 'WebServer'; $this->name = 'WebServer';
} }
/** /**
* 运行 * Run webserver instance.
*
* @see Workerman.Worker::run() * @see Workerman.Worker::run()
*/ */
public function run() public function run()
{ {
$this->_onWorkerStart = $this->onWorkerStart; $this->_onWorkerStart = $this->onWorkerStart;
$this->onWorkerStart = array($this, 'onWorkerStart'); $this->onWorkerStart = array($this, 'onWorkerStart');
$this->onMessage = array($this, 'onMessage'); $this->onMessage = array($this, 'onMessage');
parent::run(); parent::run();
} }
/** /**
* 进程启动的时候一些初始化工作 * Emit when process start.
*
* @throws \Exception * @throws \Exception
*/ */
public function onWorkerStart() 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'); throw new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path');
} }
// 初始化HttpCache // Init HttpCache.
HttpCache::init(); HttpCache::init();
// 初始化mimeMap // Init mimeMap.
$this->initMimeTypeMap(); $this->initMimeTypeMap();
// 尝试执行开发者设定的onWorkerStart回调 // Try to emit onWorkerStart callback.
if($this->_onWorkerStart) if ($this->_onWorkerStart) {
{ try {
call_user_func($this->_onWorkerStart, $this); 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 * @return void
*/ */
public function initMimeTypeMap() public function initMimeTypeMap()
{ {
$mime_file = Http::getMimeTypesFile(); $mime_file = Http::getMimeTypesFile();
if(!is_file($mime_file)) if (!is_file($mime_file)) {
{ $this->log("$mime_file mime.type file not fond");
$this->notice("$mime_file mime.type file not fond");
return; return;
} }
$items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $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"); $this->log("get $mime_file mime.type content fail");
return; return;
} }
foreach($items as $content) foreach ($items as $content) {
{ if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) {
if(preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) $mime_type = $match[1];
{ $workerman_file_extension_var = $match[2];
$mime_type = $match[1]; $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1));
$extension_var = $match[2]; foreach ($workerman_file_extension_array as $workerman_file_extension) {
$extension_array = explode(' ', substr($extension_var, 0, -1)); self::$mimeTypeMap[$workerman_file_extension] = $mime_type;
foreach($extension_array as $extension) }
{
self::$mimeTypeMap[$extension] = $mime_type;
}
} }
} }
} }
/** /**
* 当接收到完整的http请求后的处理逻辑 * Emit when http message coming.
* 1、如果请求的是以php为后缀的文件则尝试加载 *
* 2、如果请求的url没有后缀则尝试加载对应目录的index.php * @param Connection\TcpConnection $connection
* 3、如果请求的是非php为后缀的文件尝试读取原始数据并发送
* 4、如果请求的文件不存在则返回404
* @param TcpConnection $connection
* @param mixed $data
* @return void * @return void
*/ */
public function onMessage($connection, $data) public function onMessage($connection)
{ {
// 请求的文件 // REQUEST_URI.
$url_info = parse_url($_SERVER['REQUEST_URI']); $workerman_url_info = parse_url($_SERVER['REQUEST_URI']);
if(!$url_info) if (!$workerman_url_info) {
{
Http::header('HTTP/1.1 400 Bad Request'); 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); $workerman_path_info = pathinfo($workerman_path);
$extension = isset($path_info['extension']) ? $path_info['extension'] : '' ; $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : '';
if($extension == '') if ($workerman_file_extension === '') {
{ $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php';
$path = ($len = strlen($path)) && $path[$len -1] == '/' ? $path.'index.php' : $path . '/index.php'; $workerman_file_extension = 'php';
$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 ($workerman_file_extension === 'php' && !is_file($workerman_file)) {
if($extension == 'php' && !is_file($file)) $workerman_file = "$workerman_root_dir/index.php";
{ if (!is_file($workerman_file)) {
$file = "$root_dir/index.php"; $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))
{
Http::header('HTTP/1.1 400 Bad Request');
return $connection->close('<h1>400 Bad Request</h1>');
} }
}
$file = realpath($file);
// File exsits.
// 如果请求的是php文件 if (is_file($workerman_file)) {
if($extension == 'php') // Security check.
{ if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath,
$cwd = getcwd(); $workerman_root_dir_realpath)
chdir($root_dir); ) {
Http::header('HTTP/1.1 400 Bad Request');
$connection->close('<h1>400 Bad Request</h1>');
return;
}
$workerman_file = realpath($workerman_file);
// Request php file.
if ($workerman_file_extension === 'php') {
$workerman_cwd = getcwd();
chdir($workerman_root_dir);
ini_set('display_errors', 'off'); ini_set('display_errors', 'off');
// 缓冲输出
ob_start(); ob_start();
// 载入php文件 // Try to include php file.
try try {
{ // $_SERVER.
// $_SERVER变量
$_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp();
$_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); $_SERVER['REMOTE_PORT'] = $connection->getRemotePort();
include $file; include $workerman_file;
} } catch (\Exception $e) {
catch(\Exception $e) // Jump_exit?
{ if ($e->getMessage() != 'jump_exit') {
// 如果不是exit
if($e->getMessage() != 'jump_exit')
{
echo $e; echo $e;
} }
} }
$content = ob_get_clean(); $content = ob_get_clean();
ini_set('display_errors', 'on'); ini_set('display_errors', 'on');
$connection->close($content); if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") {
chdir($cwd); $connection->send($content);
return ; } else {
} $connection->close($content);
// 请求的是静态资源文件
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('');
} }
chdir($workerman_cwd);
return;
} }
if($modified_time) // Send file to client.
{ return self::sendFile($connection, $workerman_file);
Http::header("Last-Modified: $modified_time"); } else {
}
// 发送给客户端
return $connection->close(file_get_contents($file));
}
else
{
// 404 // 404
Http::header("HTTP/1.1 404 Not Found"); 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"
}