<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Connection;
use Workerman\Events\EventInterface;
use Workerman\Lib\Timer;
use Workerman\Worker;
use Exception;
/**
* 异步TCP连接
*/
class AsyncTcpConnection extends TcpConnection
{
/**
* 成功建立socket连接时发出。
*
* @var callback
*/
public $onConnect = null;
/**
* 传输层协议
*
* @var string
*/
public $transport = 'tcp';
/**
* 状态
*
* @var int
*/
protected $_status = self::STATUS_INITIAL;
/**
* 远程主机
*
* @var string
*/
protected $_remoteHost = '';
/**
* 远程端口
*
* @var int
*/
protected $_remotePort = 80;
/**
* 连接开始时间
*
* @var string
*/
protected $_connectStartTime = 0;
/**
* 远程URI
*
* @var string
*/
protected $_remoteURI = '';
/**
* 文本选项
*
* @var array
*/
protected $_contextOption = null;
/**
* 重新连接定时器
*
* @var int
*/
protected $_reconnectTimer = null;
/**
* PHP内置协议
*
* @var array
*/
protected static $_builtinTransports = array(
'tcp' => 'tcp',
'udp' => 'udp',
'unix' => 'unix',
'ssl' => 'ssl',
'sslv2' => 'sslv2',
'sslv3' => 'sslv3',
'tls' => 'tls'
);
/**
* 构造方法
*
* @param string $remote_address
* @param array $context_option
* @throws Exception
*/
public function __construct($remote_address, $context_option = null)
{
$address_info = parse_url($remote_address);
if (!$address_info) {
list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2);
if (!$this->_remoteAddress) {
Worker::safeEcho(new \Exception('bad remote_address'));
}
} else {
if (!isset($address_info['port'])) {
$address_info['port'] = 80;
}
if (!isset($address_info['path'])) {
$address_info['path'] = '/';
}
if (!isset($address_info['query'])) {
$address_info['query'] = '';
} else {
$address_info['query'] = '?' . $address_info['query'];
}
$this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}";
$this->_remoteHost = $address_info['host'];
$this->_remotePort = $address_info['port'];
$this->_remoteURI = "{$address_info['path']}{$address_info['query']}";
$scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp';
}
$this->id = $this->_id = self::$_idRecorder++;
if(PHP_INT_MAX === self::$_idRecorder){
self::$_idRecorder = 0;
}
// 检查应用层协议类。
if (!isset(self::$_builtinTransports[$scheme])) {
$scheme = ucfirst($scheme);
$this->protocol = '\\Protocols\\' . $scheme;
if (!class_exists($this->protocol)) {
$this->protocol = "\\Workerman\\Protocols\\$scheme";
if (!class_exists($this->protocol)) {
throw new Exception("class \\Protocols\\$scheme not exist");
}
}
} else {
$this->transport = self::$_builtinTransports[$scheme];
}
// 统计
self::$statistics['connection_count']++;
$this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
$this->_contextOption = $context_option;
static::$connections[$this->_id] = $this;
}
/**
* 连接
*
* @return void
*/
public function connect()
{
if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING &&
$this->_status !== self::STATUS_CLOSED) {
return;
}
$this->_status = self::STATUS_CONNECTING;
$this->_connectStartTime = microtime(true);
if ($this->transport !== 'unix') {
// 异步打开socket连接
if ($this->_contextOption) {
$context = stream_context_create($this->_contextOption);
$this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
$errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT, $context);
} else {
$this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
$errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT);
}
} else {
$this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0,
STREAM_CLIENT_ASYNC_CONNECT);
}
// 如果失败尝试发出onError回调。
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;
}
// 将socket添加到全局事件循环等待连接已成功建立或失败。
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection'));
// For windows.
if(DIRECTORY_SEPARATOR === '\\') {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection'));
}
}
/**
* 重新连接
*
* @param int $after
* @return void
*/
public function reconnect($after = 0)
{
$this->_status = self::STATUS_INITIAL;
static::$connections[$this->_id] = $this;
if ($this->_reconnectTimer) {
Timer::del($this->_reconnectTimer);
}
if ($after > 0) {
$this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false);
return;
}
$this->connect();
}
/**
* 取消重新连接
*/
public function cancelReconnect()
{
if ($this->_reconnectTimer) {
Timer::del($this->_reconnectTimer);
}
}
/**
* 获取远程地址
*
* @return string
*/
public function getRemoteHost()
{
return $this->_remoteHost;
}
/**
* 获取远程URI.
*
* @return string
*/
public function getRemoteURI()
{
return $this->_remoteURI;
}
/**
* 尝试发出onError回调
*
* @param int $code
* @param string $msg
* @return void
*/
protected function emitError($code, $msg)
{
$this->_status = self::STATUS_CLOSING;
if ($this->onError) {
try {
call_user_func($this->onError, $this, $code, $msg);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
}
/**
* 检查连接是否成功建立
*
* @param resource $socket
* @return void
*/
public function checkConnection()
{
if ($this->_status != self::STATUS_CONNECTING) {
return;
}
// 删除Windows的EV_EXPECT
if(DIRECTORY_SEPARATOR === '\\') {
Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT);
}
// 检查socket状态
if ($address = stream_socket_get_name($this->_socket, true)) {
// 非阻塞
stream_set_blocking($this->_socket, 0);
// 适配 hhvm
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($this->_socket, 0);
}
// 尝试打开tcp的keepalive并禁用Nagle算法
if (function_exists('socket_import_stream') && $this->transport === 'tcp') {
$raw_socket = socket_import_stream($this->_socket);
socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1);
socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1);
}
// 删除写侦听器
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
// SSL握手
if ($this->transport === 'ssl') {
$this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket);
} else {
// 等待发送的数据
if ($this->_sendBuffer) {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
}
}
// 注册侦听器等待读取事件。
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
$this->_status = self::STATUS_ESTABLISHED;
$this->_remoteAddress = $address;
// 调用onConnect回调
if ($this->onConnect) {
try {
call_user_func($this->onConnect, $this);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
// 调用protocol::onConnect
if (method_exists($this->protocol, 'onConnect')) {
try {
call_user_func(array($this->protocol, 'onConnect'), $this);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
}
} else {
// 连接失败
$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;
}
}
}
}