├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADE_02_03.md ├── composer.json └── src ├── Configuration ├── Configuration.php └── StreamContext.php ├── Event ├── AbstractEvent.php ├── AcceptEvent.php ├── DataAlertEvent.php ├── Event.php ├── EventType.php ├── IoEvent.php ├── ReadEvent.php ├── SocketExceptionEvent.php ├── TimeoutEvent.php └── WriteEvent.php ├── Exception ├── AcceptException.php ├── BadResourceException.php ├── ConnectionException.php ├── DisconnectException.php ├── FrameException.php ├── NetworkSocketException.php ├── RecvDataException.php ├── SendDataException.php ├── SlowSpeedTransferException.php ├── SocketException.php ├── SslHandshakeException.php ├── StopRequestExecuteException.php ├── StopSocketOperationException.php ├── TimeoutException.php ├── TransferException.php ├── UnmanagedSocketException.php └── UnsupportedOperationException.php ├── Frame ├── AbstractFramePicker.php ├── AcceptedFrame.php ├── EmptyFrame.php ├── EmptyFramePicker.php ├── FixedLengthFramePicker.php ├── Frame.php ├── FrameInterface.php ├── FramePickerInterface.php ├── MarkerFramePicker.php ├── PartialFrame.php └── RawFramePicker.php ├── Operation ├── DelayedOperation.php ├── InProgressWriteOperation.php ├── NullOperation.php ├── OperationInterface.php ├── ReadOperation.php ├── ReadWriteOperation.php ├── SslHandshakeOperation.php └── WriteOperation.php ├── RequestExecutor ├── AbstractRequestExecutor.php ├── CallbackEventHandler.php ├── ConstantLimitationSolver.php ├── EventHandlerFromSymfonyEventDispatcher.php ├── EventHandlerInterface.php ├── EventMultiHandler.php ├── ExecutionContext.php ├── IoHandlerInterface.php ├── LibEvent │ ├── LeBase.php │ ├── LeCallbackInterface.php │ └── LeEvent.php ├── LibEventRequestExecutor.php ├── LimitationSolverInterface.php ├── Metadata │ ├── RequestDescriptor.php │ ├── SocketBag.php │ └── SpeedRateCounter.php ├── NativeRequestExecutor.php ├── NoLimitationSolver.php ├── Pipeline │ ├── AbstractOobHandler.php │ ├── AbstractStage.php │ ├── AbstractTimeAwareStage.php │ ├── BaseStageFactory.php │ ├── CompositeStage.php │ ├── ConnectStage.php │ ├── ConnectStageReturningAllActiveSockets.php │ ├── DelayStage.php │ ├── DelegatingIoHandler.php │ ├── DisconnectStage.php │ ├── EventCaller.php │ ├── ExcludedOperationsStage.php │ ├── GuardianStage.php │ ├── IoStage.php │ ├── NullIoHandler.php │ ├── Pipeline.php │ ├── PipelineFactory.php │ ├── PipelineStageInterface.php │ ├── PushbackIterator.php │ ├── ReadIoHandler.php │ ├── ReadWriteIoHandler.php │ ├── SelectStage.php │ ├── SslHandshakeIoHandler.php │ ├── StageFactoryInterface.php │ ├── TimeoutStage.php │ └── WriteIoHandler.php ├── RemoveFinishedSocketsEventHandler.php ├── RequestExecutorInterface.php ├── SocketBagInterface.php ├── Specification │ ├── ConnectionLessSocketSpecification.php │ └── SpecificationInterface.php └── SslDataFlushEventHandler.php └── Socket ├── AbstractClientSocket.php ├── AbstractSocket.php ├── AcceptedSocket.php ├── AsyncSelector.php ├── AsyncSocketFactory.php ├── ClientSocket.php ├── Io ├── AbstractClientIo.php ├── AbstractIo.php ├── AbstractServerIo.php ├── Context.php ├── DatagramClientIo.php ├── DatagramMemorizedIo.php ├── DatagramServerIo.php ├── DisconnectedIo.php ├── IoInterface.php ├── StreamedClientIo.php └── StreamedServerIo.php ├── PersistentClientSocket.php ├── SelectContext.php ├── ServerSocket.php ├── SocketInterface.php ├── StreamResourceInterface.php ├── UdpClientSocket.php └── WithoutConnectionInterface.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 0.3.0 (Apr 10, 2016) 4 | -------------------- 5 | ### New features: 6 | - persistent connections support 7 | - multiple persistent connections to the same host:port 8 | - synchronization support between sockets 9 | - added RequestExecutor engine based on [libevent](https://pecl.php.net/package/libevent) 10 | - processing TLS handshake asynchronous 11 | - added Configuration support 12 | 13 | ### Changes: 14 | - added a possibility to receive remote ip address during read event 15 | 16 | 0.2.2 (Mar 12, 2016) 17 | -------------------- 18 | ### Changes: 19 | - fix: properly work on php versions when _socket_ extension is not available 20 | - bug [#1](https://github.com/edefimov/async-sockets/issues/1): fixes bug leading to CPU overloading 21 | 22 | 0.2.1 (Dec 20, 2015) 23 | -------------------- 24 | ### Changes: 25 | - fixes incorrect select operation processing on server sockets 26 | 27 | 0.2.0 (Jul 25, 2015) 28 | -------------------- 29 | ### Changes: 30 | - Removed support of synchronous I/O 31 | - Server socket support 32 | - Support all transports returned by `stream_get_transports` 33 | - Distinguish frame boundaries 34 | - Determine datagram size for udp sockets 35 | - Improved working with TLS sockets 36 | 37 | 38 | 0.2.0-alpha (Jul 1, 2015) 39 | -------------------- 40 | ### New features: 41 | - Server socket support 42 | - Support all transports returned by `stream_get_transports` 43 | - Distinguish frame boundaries 44 | - Determine datagram size for udp sockets 45 | 46 | 0.1.1 (May 25, 2015) 47 | -------------------- 48 | ### Changes: 49 | - Added additional checks due to https://bugs.php.net/bug.php?id=64803 50 | 51 | 0.1.0 (May 19, 2015) 52 | -------------------- 53 | - First library release 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Efimov Evgenij 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /UPGRADE_02_03.md: -------------------------------------------------------------------------------- 1 | UPGRADE from 0.2 to 0.3 2 | ----------------------- 3 | 4 | - The `event` parameter was removed from `AsyncSockets\Event\SocketExceptionEvent`, all calls to `$exceptionEvent->getOriginalEvent()` method should be removed. 5 | - Implementations of the `OperationInterface` have changed namespace from `AsyncSockets\RequestExecutor` to `AsyncSockets\Operation`. All usages of `ReadOperation` and `WriteOperation` must be replaced: 6 | 7 | instead of: `AsyncSockets\RequestExecutor\ReadOperation` 8 | should be: `AsyncSockets\Operation\ReadOperation` 9 | 10 | instead of: `AsyncSockets\RequestExecutor\WriteOperation` 11 | should be: `AsyncSockets\Operation\WriteOperation` 12 | 13 | - The `$remoteAddress` parameter was added into method `pickUpData()` from `AsyncSockets\Frame\FramePickerInterface`. If you have implemented this method you need to change its signature to: 14 | 15 | ```php 16 | public function pickUpData($chunk, $remoteAddress); 17 | ``` 18 | 19 | - The `getRemoteAddress()` method was added into `AsyncSockets\Frame\FrameInterface`. If you have implemented this interface add the corresponding implementation. Use the return value of `pickUpData()` method from `FramePickerInterface` 20 | 21 | ```php 22 | public function getRemoteAddress(); 23 | ``` 24 | 25 | - The `getClientAddress()` method was removed from `AsyncSockets\Frame\AcceptedFrame`. Use `getRemoteAddress()` method instead. 26 | 27 | - The `$picker` parameter is now required in the read() method from `AsyncSockets\Socket\SocketInterface` 28 | 29 | - `NullFramePicker` was removed. If you used it somewhere explicitly you should replace it with other corresponding implemntation of `FramePickerInterface` 30 | 31 | - The `FrameSocketException` is renamed into `FrameException`. 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edefimov/async-sockets", 3 | "homepage": "https://github.com/edefimov/async-sockets", 4 | "description": "Library for asynchronous work with sockets based on php streams", 5 | "keywords": [ "async", "stream", "socket" ], 6 | "minimum-stability": "stable", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Efimov Evgenij", 11 | "email": "edefimov.it@gmail.com" 12 | } 13 | ], 14 | "autoload": { 15 | "psr-4": { 16 | "AsyncSockets\\": "src/" 17 | } 18 | }, 19 | "require": { 20 | "php": ">=5.4" 21 | }, 22 | "require-dev": { 23 | "symfony/event-dispatcher": "~2.7 || ~3.0", 24 | "symfony/console": "~2.7 || ~3.0", 25 | "symfony/yaml": "~2.7 || ~3.0" 26 | }, 27 | "suggest": { 28 | "ext-libevent": "For using RequestExecutor engine based on libevent", 29 | "symfony/event-dispatcher": "Allows to notify system about socket events" 30 | }, 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "0.4.x-dev" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Configuration/StreamContext.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Configuration; 12 | 13 | /** 14 | * Class StreamContext 15 | */ 16 | class StreamContext 17 | { 18 | /** 19 | * Stream context resource 20 | * 21 | * @var resource 22 | */ 23 | private $resource; 24 | 25 | /** 26 | * StreamContext constructor. 27 | * 28 | * @param array|resource|\Traversable|null $settings Context options 29 | */ 30 | public function __construct($settings) 31 | { 32 | $this->resource = $this->createResource($settings); 33 | } 34 | 35 | /** 36 | * Return stream context resource 37 | * 38 | * @return resource 39 | */ 40 | public function getResource() 41 | { 42 | return $this->resource; 43 | } 44 | 45 | /** 46 | * Creates resource from given settings 47 | * 48 | * @param array|resource|\Traversable|null $settings Context settings 49 | * 50 | * @return resource 51 | */ 52 | protected function createResource($settings) 53 | { 54 | if ($settings instanceof \Traversable) { 55 | $settings = iterator_to_array($settings); 56 | } 57 | 58 | $map = [ 59 | 'null' => [ $this, 'createFromNull' ], 60 | 'resource' => [ $this, 'createFromResource' ], 61 | 'array' => [ $this, 'createFromArray' ], 62 | ]; 63 | 64 | $type = strtolower(gettype($settings)); 65 | if (!isset($map[$type])) { 66 | throw new \InvalidArgumentException( 67 | sprintf( 68 | 'Can not create stream context for variable type %s', 69 | is_object($settings) ? get_class($settings) : $type 70 | ) 71 | ); 72 | } 73 | 74 | 75 | return $map[$type]($settings); 76 | } 77 | 78 | /** 79 | * Create context from resource 80 | * 81 | * @param resource $resource Context resource 82 | * 83 | * @return resource 84 | */ 85 | private function createFromResource($resource) 86 | { 87 | return $resource; 88 | } 89 | 90 | /** 91 | * Create context from resource 92 | * 93 | * @return resource 94 | */ 95 | private function createFromNull() 96 | { 97 | return stream_context_get_default(); 98 | } 99 | 100 | /** 101 | * Create context from array 102 | * 103 | * @param array $settings Context settings 104 | * 105 | * @return resource 106 | */ 107 | private function createFromArray(array $settings) 108 | { 109 | return stream_context_create( 110 | isset($settings[ 'options' ]) ? $settings[ 'options' ] : [], 111 | isset($settings[ 'params' ]) ? $settings[ 'params' ] : [] 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Event/AbstractEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Event; 11 | 12 | /** 13 | * Define AbstractEvent as the alias for Symfony event object, so if you have installed 14 | * symfony EventDispatcher component then AbstractEvent will be fully compatible with 15 | * EventDispatcher's event system 16 | */ 17 | if (!class_alias('Symfony\Component\EventDispatcher\Event', 'AsyncSockets\Event\AbstractEvent', true)) { 18 | /** 19 | * Class AbstractEvent 20 | */ 21 | abstract class AbstractEvent 22 | { 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Event/AcceptEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Event; 11 | 12 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class AcceptEvent 17 | */ 18 | class AcceptEvent extends Event 19 | { 20 | /** 21 | * Client sockets 22 | * 23 | * @var SocketInterface 24 | */ 25 | private $clientSocket; 26 | 27 | /** 28 | * Remote address 29 | * 30 | * @var string 31 | */ 32 | private $remoteAddress; 33 | 34 | /** 35 | * Constructor 36 | * 37 | * @param RequestExecutorInterface $executor Request executor object 38 | * @param SocketInterface $serverSocket Server socket for this request 39 | * @param mixed $context Any optional user data for event 40 | * @param SocketInterface $clientSocket Accepted client socket 41 | * @param string $remoteAddress Remote address 42 | */ 43 | public function __construct( 44 | RequestExecutorInterface $executor, 45 | SocketInterface $serverSocket, 46 | $context, 47 | SocketInterface $clientSocket, 48 | $remoteAddress 49 | ) { 50 | parent::__construct($executor, $serverSocket, $context, EventType::ACCEPT); 51 | $this->clientSocket = $clientSocket; 52 | $this->remoteAddress = $remoteAddress; 53 | } 54 | 55 | /** 56 | * Return accepted client socket 57 | * 58 | * @return SocketInterface 59 | */ 60 | public function getClientSocket() 61 | { 62 | return $this->clientSocket; 63 | } 64 | 65 | /** 66 | * Return remote address 67 | * 68 | * @return string 69 | */ 70 | public function getRemoteAddress() 71 | { 72 | return $this->remoteAddress; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Event/DataAlertEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Event; 11 | 12 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class DataAlertEvent 17 | */ 18 | class DataAlertEvent extends IoEvent 19 | { 20 | /** 21 | * Total amount of attempts before closing connection 22 | * 23 | * @var int 24 | */ 25 | private $totalAttempts; 26 | 27 | /** 28 | * Current attempt number, the first is 1 29 | * 30 | * @var int 31 | */ 32 | private $attempt; 33 | 34 | /** 35 | * DataAlertEvent constructor 36 | * 37 | * @param RequestExecutorInterface $executor Request executor object 38 | * @param SocketInterface $socket Socket for this request 39 | * @param mixed $context Any optional user data for event 40 | * @param int $attempt Current attempt from 1 41 | * @param int $totalAttempts Total amount of attempts 42 | */ 43 | public function __construct( 44 | RequestExecutorInterface $executor, 45 | SocketInterface $socket, 46 | $context, 47 | $attempt, 48 | $totalAttempts 49 | ) { 50 | parent::__construct($executor, $socket, $context, EventType::DATA_ALERT); 51 | $this->totalAttempts = $totalAttempts; 52 | $this->attempt = $attempt; 53 | } 54 | 55 | /** 56 | * Return TotalAttempts 57 | * 58 | * @return int 59 | */ 60 | public function getTotalAttempts() 61 | { 62 | return $this->totalAttempts; 63 | } 64 | 65 | /** 66 | * Return Attempt 67 | * 68 | * @return int 69 | */ 70 | public function getAttempt() 71 | { 72 | return $this->attempt; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Event/Event.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Event; 12 | 13 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 14 | use AsyncSockets\Socket\SocketInterface; 15 | 16 | /** 17 | * Class Event 18 | * 19 | * @api 20 | */ 21 | class Event extends AbstractEvent 22 | { 23 | /** 24 | * Socket linked to this event 25 | * 26 | * @var SocketInterface 27 | */ 28 | private $socket; 29 | 30 | /** 31 | * User data for this event 32 | * 33 | * @var mixed 34 | */ 35 | private $context; 36 | 37 | /** 38 | * Request executor 39 | * 40 | * @var RequestExecutorInterface 41 | */ 42 | private $executor; 43 | 44 | /** 45 | * This event type 46 | * 47 | * @var string 48 | */ 49 | private $type; 50 | 51 | /** 52 | * Flag to stop this socket request 53 | * 54 | * @var bool 55 | */ 56 | private $isCancelled; 57 | 58 | /** 59 | * Constructor 60 | * 61 | * @param RequestExecutorInterface $executor Request executor object 62 | * @param SocketInterface $socket Socket for this request 63 | * @param mixed $context Any optional user data for event 64 | * @param string $type One of EventType::* constants 65 | */ 66 | public function __construct(RequestExecutorInterface $executor, SocketInterface $socket, $context, $type) 67 | { 68 | $this->executor = $executor; 69 | $this->socket = $socket; 70 | $this->context = $context; 71 | $this->type = $type; 72 | $this->isCancelled = false; 73 | } 74 | 75 | /** 76 | * Return associated socket 77 | * 78 | * @return SocketInterface 79 | */ 80 | public function getSocket() 81 | { 82 | return $this->socket; 83 | } 84 | 85 | /** 86 | * Return user specified context 87 | * 88 | * @return mixed 89 | */ 90 | public function getContext() 91 | { 92 | return $this->context; 93 | } 94 | 95 | /** 96 | * Return RequestExecutor 97 | * 98 | * @return RequestExecutorInterface 99 | */ 100 | public function getExecutor() 101 | { 102 | return $this->executor; 103 | } 104 | 105 | /** 106 | * Return type of this event 107 | * 108 | * @return string One of EventType::* consts 109 | */ 110 | public function getType() 111 | { 112 | return $this->type; 113 | } 114 | 115 | /** 116 | * Return flag whether this socket operation is cancelled 117 | * 118 | * @return boolean 119 | */ 120 | public function isOperationCancelled() 121 | { 122 | return $this->isCancelled; 123 | } 124 | 125 | /** 126 | * Sets cancel this operation flag 127 | * 128 | * @param boolean $isCancelled Cancel flag 129 | * 130 | * @return void 131 | */ 132 | public function cancelThisOperation($isCancelled = true) 133 | { 134 | $this->isCancelled = $isCancelled; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Event/EventType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Event; 12 | 13 | /** 14 | * Class EventType 15 | * 16 | * @api 17 | */ 18 | final class EventType 19 | { 20 | /** 21 | * It is the first event, which you receive for socket. Event object will be given as an argument. 22 | * This is useful for making some initialization work. You should call setSocketMetaData method and fill 23 | * RequestExecutorInterface::META_ADDRESS with value. You can omit handling of this event, if you 24 | * set RequestExecutorInterface::META_ADDRESS in socket metadata. 25 | * You can also call open method manually on the socket 26 | * 27 | * @see Event 28 | * @see RequestExecutorInterface::META_ADDRESS 29 | * @see SocketBag::setSocketMetaData 30 | * @see SocketInterface::open 31 | * 32 | * @see Event 33 | */ 34 | const INITIALIZE = 'socket.event.initialize'; 35 | 36 | /** 37 | * Socket has connected to server. Event object will be given as an argument. 38 | * No special action required here. Do NOT try to read or write data at this point. 39 | * This event is not dispatched if socket is connected. 40 | * 41 | * @see Event 42 | */ 43 | const CONNECTED = 'socket.event.connected'; 44 | 45 | /** 46 | * Event is fired by server sockets when new client was connected. AcceptEvent will be given as an argument 47 | * 48 | * @see AcceptEvent 49 | */ 50 | const ACCEPT = 'socket.event.accept'; 51 | 52 | /** 53 | * Socket data has been read into event object. ReadEvent object will be given as an argument. Use event object 54 | * to get received data 55 | * 56 | * @see ReadEvent 57 | */ 58 | const READ = 'socket.event.read'; 59 | 60 | /** 61 | * Socket contains out of band data. ReadEvent object will be given as an argument. Use event object 62 | * to get received data 63 | * 64 | * @see ReadEvent 65 | */ 66 | const OOB = 'socket.event.oob'; 67 | 68 | /** 69 | * Socket data can be written. WriteEvent object will be given as an argument. Use event object 70 | * for writing data 71 | * 72 | * @see WriteEvent 73 | */ 74 | const WRITE = 'socket.event.write'; 75 | 76 | /** 77 | * There are new data in socket, but read operation is not set. In the response to 78 | * this event you should either close a connection, or set appropriate read operation. If none of 79 | * these is done, socket will be automatically closed with UnmanagedSocketException thrown. 80 | * 81 | * @see DataAlertEvent 82 | */ 83 | const DATA_ALERT = 'socket.event.data_alert'; 84 | 85 | /** 86 | * Socket has disconnected. Event object will be given as an argument. 87 | * This event is not dispatched if socket is marked as forgotten. 88 | * 89 | * @see Event 90 | */ 91 | const DISCONNECTED = 'socket.event.disconnected'; 92 | 93 | /** 94 | * It is the last event, which you receive for socket. This event is useful for cleaning stuff after initialization. 95 | * You will receive this event even in case of errors in socket processing. Event object will be given 96 | * as an argument. 97 | * 98 | * @see Event 99 | */ 100 | const FINALIZE = 'socket.event.finalize'; 101 | 102 | /** 103 | * Connect / read / write operation timeout for socket. TimeoutEvent object will be given as an argument. 104 | * 105 | * @see TimeoutEvent 106 | */ 107 | const TIMEOUT = 'socket.event.timeout'; 108 | 109 | /** 110 | * Exception occurred during another event. SocketExceptionEvent will be given as an argument. 111 | * 112 | * @see SocketExceptionEvent 113 | */ 114 | const EXCEPTION = 'socket.event.exception'; 115 | } 116 | -------------------------------------------------------------------------------- /src/Event/IoEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Event; 12 | 13 | use AsyncSockets\Frame\FramePickerInterface; 14 | use AsyncSockets\Operation\OperationInterface; 15 | use AsyncSockets\Operation\ReadOperation; 16 | use AsyncSockets\Operation\WriteOperation; 17 | 18 | /** 19 | * Class IoEvent 20 | * 21 | * @api 22 | */ 23 | class IoEvent extends Event 24 | { 25 | /** 26 | * Next operation to perform on socket 27 | * 28 | * @var OperationInterface|null 29 | */ 30 | private $nextOperation; 31 | 32 | /** 33 | * Return next operation on socket 34 | * 35 | * @return OperationInterface|null Null means no further operation required 36 | */ 37 | public function getNextOperation() 38 | { 39 | return $this->nextOperation; 40 | } 41 | 42 | /** 43 | * Mark next operation as read 44 | * 45 | * @param FramePickerInterface $framePicker Frame picker for reading data 46 | * 47 | * @return void 48 | */ 49 | public function nextIsRead(FramePickerInterface $framePicker = null) 50 | { 51 | $this->nextIs(new ReadOperation($framePicker)); 52 | } 53 | 54 | /** 55 | * Mark next operation as write 56 | * 57 | * @param string $data Data to write 58 | * 59 | * @return void 60 | */ 61 | public function nextIsWrite($data = null) 62 | { 63 | $this->nextIs(new WriteOperation($data)); 64 | } 65 | 66 | /** 67 | * Changed next operation to given one 68 | * 69 | * @param OperationInterface $operation Next operation 70 | * 71 | * @return void 72 | */ 73 | public function nextIs(OperationInterface $operation) 74 | { 75 | $this->nextOperation = $operation; 76 | } 77 | 78 | /** 79 | * Mark next operation as not required 80 | * 81 | * @return void 82 | */ 83 | public function nextOperationNotRequired() 84 | { 85 | $this->nextOperation = null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Event/ReadEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Event; 11 | 12 | use AsyncSockets\Frame\FrameInterface; 13 | use AsyncSockets\Frame\PartialFrame; 14 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 15 | use AsyncSockets\Socket\SocketInterface; 16 | 17 | /** 18 | * Class ReadEvent 19 | */ 20 | class ReadEvent extends IoEvent 21 | { 22 | /** 23 | * Data read from network 24 | * 25 | * @var FrameInterface 26 | */ 27 | private $frame; 28 | 29 | /** 30 | * Constructor 31 | * 32 | * @param RequestExecutorInterface $executor Request executor object 33 | * @param SocketInterface $socket Socket for this request 34 | * @param mixed $context Any optional user data for event 35 | * @param FrameInterface $frame Network data for read operation 36 | * @param bool $isOutOfBand Flag if data are out of band 37 | */ 38 | public function __construct( 39 | RequestExecutorInterface $executor, 40 | SocketInterface $socket, 41 | $context, 42 | FrameInterface $frame, 43 | $isOutOfBand = false 44 | ) { 45 | parent::__construct($executor, $socket, $context, $isOutOfBand ? EventType::OOB : EventType::READ); 46 | $this->frame = $frame; 47 | } 48 | 49 | /** 50 | * Return response frame 51 | * 52 | * @return FrameInterface 53 | */ 54 | public function getFrame() 55 | { 56 | return $this->frame; 57 | } 58 | 59 | /** 60 | * Return true, if frame in this event is partial 61 | * 62 | * @return bool 63 | */ 64 | public function isPartial() 65 | { 66 | return $this->frame instanceof PartialFrame; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Event/SocketExceptionEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Event; 12 | 13 | use AsyncSockets\Exception\SocketException; 14 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 15 | use AsyncSockets\Socket\SocketInterface; 16 | 17 | /** 18 | * Class SocketExceptionEvent 19 | * 20 | * @api 21 | */ 22 | class SocketExceptionEvent extends Event 23 | { 24 | /** 25 | * Exception linked with event 26 | * 27 | * @var SocketException 28 | */ 29 | private $exception; 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param SocketException $exception Exception occurred during request 35 | * @param RequestExecutorInterface $executor Request executor object 36 | * @param SocketInterface $socket Socket for this request 37 | * @param mixed $context Any optional user data for event 38 | */ 39 | public function __construct( 40 | SocketException $exception, 41 | RequestExecutorInterface $executor, 42 | SocketInterface $socket, 43 | $context 44 | ) { 45 | parent::__construct($executor, $socket, $context, EventType::EXCEPTION); 46 | $this->exception = $exception; 47 | } 48 | 49 | /** 50 | * Return thrown exception 51 | * 52 | * @return SocketException 53 | */ 54 | public function getException() 55 | { 56 | return $this->exception; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Event/TimeoutEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Event; 11 | 12 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class TimeoutEvent 17 | */ 18 | class TimeoutEvent extends Event 19 | { 20 | /** 21 | * Timeout during connection 22 | */ 23 | const DURING_CONNECTION = 'connection'; 24 | 25 | /** 26 | * Timeout during I/O operation 27 | */ 28 | const DURING_IO = 'io'; 29 | 30 | /** 31 | * Stage when occured timeout 32 | * 33 | * @var string 34 | */ 35 | private $when; 36 | 37 | /** 38 | * Flag whether we can enable one more I/O attempt for this socket 39 | * 40 | * @var bool 41 | */ 42 | private $isNextAttemptEnabled = false; 43 | 44 | /** 45 | * TimeoutEvent constructor. 46 | * 47 | * @param RequestExecutorInterface $executor Request executor object 48 | * @param SocketInterface $socket Socket for this request 49 | * @param mixed $context Any optional user data for event 50 | * @param string $when One of DURING_* constants 51 | */ 52 | public function __construct(RequestExecutorInterface $executor, SocketInterface $socket, $context, $when) 53 | { 54 | parent::__construct($executor, $socket, $context, EventType::TIMEOUT); 55 | $this->when = $when; 56 | } 57 | 58 | /** 59 | * Return stage when timeout occurred 60 | * 61 | * @return string one of DURING_* consts 62 | */ 63 | public function when() 64 | { 65 | return $this->when; 66 | } 67 | 68 | /** 69 | * Mark operation for try-again 70 | * 71 | * @return void 72 | */ 73 | public function enableOneMoreAttempt() 74 | { 75 | $this->isNextAttemptEnabled = true; 76 | } 77 | 78 | /** 79 | * Return true if we can try to connect / I/O once again 80 | * 81 | * @return boolean 82 | */ 83 | public function isNextAttemptEnabled() 84 | { 85 | return $this->isNextAttemptEnabled; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Event/WriteEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Event; 11 | 12 | use AsyncSockets\Operation\WriteOperation; 13 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 14 | use AsyncSockets\Socket\SocketInterface; 15 | 16 | /** 17 | * Class WriteEvent 18 | */ 19 | class WriteEvent extends IoEvent 20 | { 21 | /** 22 | * Operation to be used in I/O 23 | * 24 | * @var WriteOperation 25 | */ 26 | private $operation; 27 | 28 | /** 29 | * Constructor 30 | * 31 | * @param WriteOperation $operation Operation, which will be used in I/O 32 | * @param RequestExecutorInterface $executor Request executor object 33 | * @param SocketInterface $socket Socket for this request 34 | * @param mixed $context Any optional user data for event 35 | */ 36 | public function __construct( 37 | WriteOperation $operation, 38 | RequestExecutorInterface $executor, 39 | SocketInterface $socket, 40 | $context 41 | ) { 42 | parent::__construct($executor, $socket, $context, EventType::WRITE); 43 | $this->operation = $operation; 44 | } 45 | 46 | /** 47 | * Return operation object 48 | * 49 | * @return WriteOperation 50 | */ 51 | public function getOperation() 52 | { 53 | return $this->operation; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Exception/AcceptException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | /** 13 | * Class AcceptException. Exception is thrown by server sockets when 14 | * client can not be connected 15 | */ 16 | class AcceptException extends NetworkSocketException 17 | { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/BadResourceException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Exception; 12 | 13 | /** 14 | * Class BadResourceException 15 | */ 16 | class BadResourceException extends \InvalidArgumentException 17 | { 18 | /** 19 | * Create error for incorrect resource 20 | * 21 | * @param string $type Resource type 22 | * 23 | * @return BadResourceException 24 | */ 25 | public static function notSocketResource($type) 26 | { 27 | return new self( 28 | sprintf('Can not create socket from resource "%s"', $type) 29 | ); 30 | } 31 | 32 | /** 33 | * Can not get local socket address 34 | * 35 | * @return BadResourceException 36 | */ 37 | public static function canNotObtainLocalAddress() 38 | { 39 | return new self('Can not retrieve local socket address.'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Exception/ConnectionException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | /** 13 | * Class ConnectionException. Indicates problems with socket connection 14 | */ 15 | class ConnectionException extends NetworkSocketException 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/DisconnectException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class DisconnectException. Socket has been unexpectedly disconnected 16 | */ 17 | class DisconnectException extends ConnectionException 18 | { 19 | /** 20 | * Creates lost remote connection exception 21 | * 22 | * @param SocketInterface $socket 23 | * 24 | * @return DisconnectException 25 | */ 26 | public static function lostRemoteConnection(SocketInterface $socket) 27 | { 28 | return new self($socket, 'Remote connection has been lost.'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/FrameException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | use AsyncSockets\Frame\FramePickerInterface; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class FrameException 17 | */ 18 | class FrameException extends NetworkSocketException 19 | { 20 | /** 21 | * Failed FramePicker 22 | * 23 | * @var FramePickerInterface 24 | */ 25 | private $picker; 26 | 27 | /** 28 | * Construct the exception. 29 | * 30 | * @param FramePickerInterface $picker Corrupted framePicker 31 | * @param SocketInterface $socket Socket object 32 | * @param string $message The Exception message to throw. 33 | * @param int $code The Exception code. 34 | * @param \Exception $previous The previous exception used for the exception chaining. 35 | */ 36 | public function __construct( 37 | FramePickerInterface $picker, 38 | SocketInterface $socket, 39 | $message = '', 40 | $code = 0, 41 | \Exception $previous = null 42 | ) { 43 | parent::__construct($socket, $message, $code, $previous); 44 | $this->picker = $picker; 45 | } 46 | 47 | /** 48 | * Return corrupted framePicker 49 | * 50 | * @return FramePickerInterface 51 | */ 52 | public function getFramePicker() 53 | { 54 | return $this->picker; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Exception/NetworkSocketException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Exception; 12 | 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class NetworkSocketException. 17 | * This exception can be thrown during network operations. 18 | */ 19 | class NetworkSocketException extends SocketException 20 | { 21 | /** 22 | * Socket with this exception 23 | * 24 | * @var SocketInterface 25 | */ 26 | private $socket; 27 | 28 | /** 29 | * Construct the exception. 30 | * 31 | * @param SocketInterface $socket Socket object 32 | * @param string $message The Exception message to throw. 33 | * @param int $code The Exception code. 34 | * @param \Exception $previous The previous exception used for the exception chaining. 35 | */ 36 | public function __construct(SocketInterface $socket, $message = '', $code = 0, \Exception $previous = null) 37 | { 38 | parent::__construct($message, $code, $previous); 39 | $this->socket = $socket; 40 | } 41 | 42 | /** 43 | * Return socket with this exception 44 | * 45 | * @return SocketInterface 46 | */ 47 | public function getSocket() 48 | { 49 | return $this->socket; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Exception/RecvDataException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class RecvDataException. Thrown any time there is error during data receiving process 16 | */ 17 | class RecvDataException extends TransferException 18 | { 19 | /** 20 | * @inheritDoc 21 | */ 22 | public function __construct( 23 | SocketInterface $socket, 24 | $message = '', 25 | $code = 0, 26 | \Exception $previous = null 27 | ) { 28 | parent::__construct($socket, self::DIRECTION_RECV, $message, $code, $previous); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Exception/SendDataException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class SendDataException. Thrown any time there is a problem with sending data to remote side 16 | */ 17 | class SendDataException extends TransferException 18 | { 19 | /** 20 | * Creates data sending failure 21 | * 22 | * @param SocketInterface $socket Socket with error 23 | * 24 | * @return SendDataException 25 | */ 26 | public static function failedToSendData(SocketInterface $socket) 27 | { 28 | return new self($socket, 'Failed to send data.'); 29 | } 30 | 31 | /** 32 | * @inheritDoc 33 | */ 34 | public function __construct( 35 | SocketInterface $socket, 36 | $message = '', 37 | $code = 0, 38 | \Exception $previous = null 39 | ) { 40 | parent::__construct($socket, self::DIRECTION_SEND, $message, $code, $previous); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Exception/SlowSpeedTransferException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class SlowSpeedTransferException. Thrown when transfer speed is slow then given in socket setup 16 | */ 17 | class SlowSpeedTransferException extends TransferException 18 | { 19 | /** 20 | * Speed at the moment of exception in bytes per second 21 | * 22 | * @var double 23 | */ 24 | private $speed; 25 | 26 | /** 27 | * Duration of low speed in seconds 28 | * 29 | * @var int 30 | */ 31 | private $duration; 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | public function __construct( 37 | SocketInterface $socket, 38 | $direction, 39 | $speed, 40 | $duration, 41 | $message = '', 42 | $code = 0, 43 | \Exception $previous = null 44 | ) { 45 | parent::__construct($socket, $direction, $message, $code, $previous); 46 | $this->speed = $speed; 47 | $this->duration = $duration; 48 | } 49 | 50 | /** 51 | * Create exception for slow receive speed 52 | * 53 | * @param SocketInterface $socket Socket 54 | * @param double $speed Speed at the moment of exception in bytes per second 55 | * @param int $duration Duration of low speed in seconds 56 | * 57 | * @return SlowSpeedTransferException 58 | */ 59 | public static function tooSlowDataReceiving(SocketInterface $socket, $speed, $duration) 60 | { 61 | return new self( 62 | $socket, 63 | self::DIRECTION_RECV, 64 | $speed, 65 | $duration, 66 | 'Data transfer is going to be aborted because of too slow speed.' 67 | ); 68 | } 69 | 70 | /** 71 | * Create exception for slow send speed 72 | * 73 | * @param SocketInterface $socket Socket 74 | * @param double $speed Speed at the moment of exception in bytes per second 75 | * @param int $duration Duration of low speed in seconds 76 | * 77 | * @return SlowSpeedTransferException 78 | */ 79 | public static function tooSlowDataSending(SocketInterface $socket, $speed, $duration) 80 | { 81 | return new self( 82 | $socket, 83 | self::DIRECTION_SEND, 84 | $speed, 85 | $duration, 86 | 'Data transfer is going to be aborted because of too slow speed.' 87 | ); 88 | } 89 | 90 | /** 91 | * Return speed at the moment of exception in bytes per second 92 | * 93 | * @return float 94 | */ 95 | public function getSpeed() 96 | { 97 | return $this->speed; 98 | } 99 | 100 | /** 101 | * Return duration of low speed in seconds 102 | * 103 | * @return int 104 | */ 105 | public function getDuration() 106 | { 107 | return $this->duration; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Exception/SocketException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Exception; 12 | 13 | /** 14 | * Class SocketException 15 | */ 16 | class SocketException extends \RuntimeException 17 | { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/SslHandshakeException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | /** 13 | * Class SslHandshakeException 14 | */ 15 | class SslHandshakeException extends ConnectionException 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/StopRequestExecuteException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | /** 13 | * Class StopRequestExecuteException. Internal exception, never thrown outside 14 | */ 15 | final class StopRequestExecuteException extends \Exception 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/StopSocketOperationException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | /** 13 | * Class StopSocketOperationException. Internal exception, never thrown outside 14 | */ 15 | final class StopSocketOperationException extends SocketException 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/TimeoutException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Exception; 12 | 13 | /** 14 | * Class TimeoutException 15 | */ 16 | class TimeoutException extends SocketException 17 | { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Exception/TransferException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Exception; 12 | 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class TransferException. 17 | * This class of exceptions describes failures with network transfers. 18 | */ 19 | class TransferException extends NetworkSocketException 20 | { 21 | /** 22 | * Exception with incoming data 23 | */ 24 | const DIRECTION_RECV = 0; 25 | 26 | /** 27 | * Exception with outgoing data 28 | */ 29 | const DIRECTION_SEND = 1; 30 | 31 | /** 32 | * Transfer direction 33 | * 34 | * @var int 35 | */ 36 | private $direction; 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | public function __construct( 42 | SocketInterface $socket, 43 | $direction, 44 | $message = '', 45 | $code = 0, 46 | \Exception $previous = null 47 | ) { 48 | parent::__construct($socket, $message, $code, $previous); 49 | $this->direction = $direction; 50 | } 51 | 52 | /** 53 | * Return direction of this exception 54 | * 55 | * @return int 56 | */ 57 | public function getDirection() 58 | { 59 | return $this->direction; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Exception/UnmanagedSocketException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class UnmanagedSocketException. Throws when system detects some unexpected conditions and suppose 16 | * that operation couldn't be completed 17 | */ 18 | class UnmanagedSocketException extends NetworkSocketException 19 | { 20 | /** 21 | * Socket data will not be correctly processed by application. 22 | * 23 | * @param SocketInterface $socket Socket object caused exception 24 | * 25 | * @return UnmanagedSocketException 26 | */ 27 | public static function zombieSocketDetected(SocketInterface $socket) 28 | { 29 | return new self( 30 | $socket, 31 | sprintf( 32 | 'System has detected a zombie connection %s and closed it. '. 33 | 'If you see this message it means that application ' . 34 | 'has lost control on one of its sockets.', 35 | (string) $socket 36 | ) 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Exception/UnsupportedOperationException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Exception; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class UnsupportedOperationException 16 | */ 17 | class UnsupportedOperationException extends NetworkSocketException 18 | { 19 | /** 20 | * Throw OOB unsupported error for given socket 21 | * 22 | * @param SocketInterface $socket Socket object tried to perform operation 23 | * 24 | * @return UnsupportedOperationException 25 | */ 26 | public static function oobDataUnsupported(SocketInterface $socket) 27 | { 28 | return new self( 29 | $socket, 30 | sprintf( 31 | 'Out-of-band data are unsupported for "%s".', 32 | (string) $socket 33 | ) 34 | ); 35 | } 36 | 37 | /** 38 | * Throw OOB data size exceeded for given socket 39 | * 40 | * @param SocketInterface $socket Socket object tried to perform operation 41 | * @param int $packetSize Size of packet in bytes 42 | * @param int $size Size of data about to sent 43 | * 44 | * @return UnsupportedOperationException 45 | */ 46 | public static function oobDataPackageSizeExceeded(SocketInterface $socket, $packetSize, $size) 47 | { 48 | return new self( 49 | $socket, 50 | sprintf( 51 | 'Out-of-band data size is exceeded for socket "%s", (%s bytes allowed, %s bytes tried to sent).', 52 | (string) $socket, 53 | $packetSize, 54 | $size 55 | ) 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Frame/AbstractFramePicker.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class AbstractFramePicker 14 | */ 15 | abstract class AbstractFramePicker implements FramePickerInterface 16 | { 17 | /** 18 | * Flag, whether framePicker is finished 19 | * 20 | * @var bool 21 | */ 22 | private $isFinished; 23 | 24 | /** 25 | * Frame with data for this picker 26 | * 27 | * @var FrameInterface 28 | */ 29 | private $frame; 30 | 31 | /** 32 | * Collected data for this frame 33 | * 34 | * @var string 35 | */ 36 | private $buffer; 37 | 38 | /** 39 | * Client's address sent this data 40 | * 41 | * @var string 42 | */ 43 | private $remoteAddress; 44 | 45 | /** 46 | * AbstractFramePicker constructor. 47 | */ 48 | public function __construct() 49 | { 50 | $this->isFinished = false; 51 | $this->buffer = ''; 52 | } 53 | 54 | /** {@inheritdoc} */ 55 | public function isEof() 56 | { 57 | return $this->isFinished; 58 | } 59 | 60 | /** {@inheritdoc} */ 61 | public function pickUpData($chunk, $remoteAddress) 62 | { 63 | if ($this->isFinished) { 64 | return $chunk; 65 | } 66 | 67 | if (!$this->remoteAddress && $remoteAddress) { 68 | $this->remoteAddress = $remoteAddress; 69 | } 70 | 71 | $this->frame = null; 72 | return (string) $this->doHandleData($chunk, $remoteAddress, $this->buffer); 73 | } 74 | 75 | /** 76 | * Process raw network data. Data should be used to determine end of this concrete framePicker 77 | * 78 | * @param string $chunk Chunk read from socket 79 | * @param string $remoteAddress Client's address sent this data 80 | * @param string &$buffer Pointer to internal buffer for collecting data 81 | * 82 | * @return string Unprocessed data after the end of frame 83 | */ 84 | abstract protected function doHandleData($chunk, $remoteAddress, &$buffer); 85 | 86 | /** 87 | * Create frame for picked up data 88 | * 89 | * @param string $buffer Buffer with collected data 90 | * @param string $remoteAddress Remote socket address these data from 91 | * 92 | * @return FrameInterface 93 | */ 94 | abstract protected function doCreateFrame($buffer, $remoteAddress); 95 | 96 | /** 97 | * Sets finished flag 98 | * 99 | * @param boolean $isFinished Flag whether framePicker is finished 100 | * 101 | * @return void 102 | */ 103 | protected function setFinished($isFinished) 104 | { 105 | $this->isFinished = $isFinished; 106 | } 107 | 108 | /** {@inheritdoc} */ 109 | public function createFrame() 110 | { 111 | if (!$this->frame) { 112 | $this->frame = $this->doCreateFrame($this->buffer, $this->remoteAddress); 113 | } 114 | 115 | return $this->frame; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Frame/AcceptedFrame.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class AcceptedFrame 16 | */ 17 | class AcceptedFrame implements FrameInterface 18 | { 19 | /** 20 | * Client address, if available 21 | * 22 | * @var string 23 | */ 24 | private $clientAddress; 25 | 26 | /** 27 | * Connected client socket 28 | * 29 | * @var SocketInterface 30 | */ 31 | private $socket; 32 | 33 | /** 34 | * AcceptResponse constructor. 35 | * 36 | * @param string $clientAddress Remote client address 37 | * @param SocketInterface $socket Remote socket 38 | */ 39 | public function __construct($clientAddress, SocketInterface $socket) 40 | { 41 | $this->clientAddress = $clientAddress; 42 | $this->socket = $socket; 43 | } 44 | 45 | /** {@inheritdoc} */ 46 | public function getData() 47 | { 48 | return $this->clientAddress; 49 | } 50 | 51 | /** {@inheritdoc} */ 52 | public function __toString() 53 | { 54 | return $this->clientAddress; 55 | } 56 | 57 | /** 58 | * Return client socket 59 | * 60 | * @return SocketInterface 61 | */ 62 | public function getClientSocket() 63 | { 64 | return $this->socket; 65 | } 66 | 67 | /** {@inheritdoc} */ 68 | public function getRemoteAddress() 69 | { 70 | return $this->clientAddress; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Frame/EmptyFrame.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class EmptyFrame 14 | */ 15 | class EmptyFrame implements FrameInterface 16 | { 17 | /** 18 | * Remote address 19 | * 20 | * @var string 21 | */ 22 | private $remoteAddress; 23 | 24 | /** 25 | * EmptyFrame constructor. 26 | * 27 | * @param string $remoteAddress Remote host address 28 | */ 29 | public function __construct($remoteAddress) 30 | { 31 | $this->remoteAddress = $remoteAddress; 32 | } 33 | 34 | /** {@inheritdoc} */ 35 | public function getRemoteAddress() 36 | { 37 | return $this->remoteAddress; 38 | } 39 | 40 | /** {@inheritdoc} */ 41 | public function getData() 42 | { 43 | return ''; 44 | } 45 | 46 | /** {@inheritdoc} */ 47 | public function __toString() 48 | { 49 | return ''; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Frame/EmptyFramePicker.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class EmptyFramePicker 14 | */ 15 | class EmptyFramePicker extends AbstractFramePicker 16 | { 17 | /** {@inheritdoc} */ 18 | protected function doHandleData($chunk, $remoteAddress, &$buffer) 19 | { 20 | $this->setFinished(true); 21 | return $chunk; 22 | } 23 | 24 | /** {@inheritdoc} */ 25 | protected function doCreateFrame($buffer, $remoteAddress) 26 | { 27 | return new EmptyFrame($remoteAddress); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Frame/FixedLengthFramePicker.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class FixedLengthFramePicker 14 | */ 15 | class FixedLengthFramePicker extends AbstractFramePicker 16 | { 17 | /** 18 | * Desired length of framePicker 19 | * 20 | * @var int 21 | */ 22 | private $length; 23 | 24 | /** 25 | * FixedLengthFramePicker constructor. 26 | * 27 | * @param int $length Length of data for this framePicker 28 | */ 29 | public function __construct($length) 30 | { 31 | parent::__construct(); 32 | $this->length = (int) $length; 33 | } 34 | 35 | /** {@inheritdoc} */ 36 | protected function doHandleData($chunk, $remoteAddress, &$buffer) 37 | { 38 | $chunkLength = strlen($chunk); 39 | $dataLength = min($this->length - strlen($buffer), $chunkLength); 40 | $buffer .= substr($chunk, 0, $dataLength); 41 | $isEndReached = strlen($buffer) === $this->length; 42 | 43 | if ($isEndReached) { 44 | $this->setFinished(true); 45 | return substr($chunk, $dataLength); 46 | } 47 | 48 | return ''; 49 | } 50 | 51 | /** {@inheritdoc} */ 52 | protected function doCreateFrame($buffer, $remoteAddress) 53 | { 54 | return new Frame($buffer, $remoteAddress); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Frame/Frame.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class Frame 14 | */ 15 | class Frame implements FrameInterface 16 | { 17 | /** 18 | * Data from network for this object 19 | * 20 | * @var string 21 | */ 22 | private $data; 23 | 24 | /** 25 | * The source address of this frame 26 | * 27 | * @var string 28 | */ 29 | private $remoteAddress; 30 | 31 | /** 32 | * SocketResponse constructor. 33 | * 34 | * @param string $data Data from network for this response 35 | * @param string $remoteAddress The source address of this frame 36 | */ 37 | public function __construct($data, $remoteAddress) 38 | { 39 | $this->data = (string) $data; 40 | $this->remoteAddress = $remoteAddress; 41 | } 42 | 43 | /** {@inheritdoc} */ 44 | public function getData() 45 | { 46 | return $this->data; 47 | } 48 | 49 | /** {@inheritdoc} */ 50 | public function __toString() 51 | { 52 | return $this->getData(); 53 | } 54 | 55 | /** {@inheritdoc} */ 56 | public function getRemoteAddress() 57 | { 58 | return $this->remoteAddress; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Frame/FrameInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Interface FrameInterface 14 | */ 15 | interface FrameInterface 16 | { 17 | /** 18 | * Return remote address these data received from 19 | * 20 | * @return string 21 | */ 22 | public function getRemoteAddress(); 23 | 24 | /** 25 | * Return content of this frame 26 | * 27 | * @return string 28 | */ 29 | public function getData(); 30 | 31 | /** 32 | * Standard php __toString method, should return the same result as getData 33 | * 34 | * @return string 35 | * @see getData 36 | */ 37 | public function __toString(); 38 | } 39 | -------------------------------------------------------------------------------- /src/Frame/FramePickerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Interface FramePickerInterface 14 | */ 15 | interface FramePickerInterface 16 | { 17 | /** 18 | * Return true, if end of frame is reached 19 | * 20 | * @return bool 21 | */ 22 | public function isEof(); 23 | 24 | /** 25 | * Process raw network data. Data should be used to determine end of this concrete frame 26 | * 27 | * @param string $chunk Chunk read from socket 28 | * @param string $remoteAddress Remote client address sent this chunk 29 | * 30 | * @return string Unprocessed data after end of frame 31 | */ 32 | public function pickUpData($chunk, $remoteAddress); 33 | 34 | /** 35 | * Create frame from picked data 36 | * 37 | * @return FrameInterface 38 | */ 39 | public function createFrame(); 40 | } 41 | -------------------------------------------------------------------------------- /src/Frame/MarkerFramePicker.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class MarkerFramePicker 14 | */ 15 | class MarkerFramePicker extends AbstractFramePicker 16 | { 17 | /** 18 | * Frame start byte sequence or null 19 | * 20 | * @var string|null 21 | */ 22 | private $startMarker; 23 | 24 | /** 25 | * Frame end marker 26 | * 27 | * @var string 28 | */ 29 | private $endMarker; 30 | 31 | /** 32 | * Offset to search for end marker during data handling 33 | * 34 | * @var int 35 | */ 36 | private $startPos; 37 | 38 | /** 39 | * bool 40 | * 41 | * @var bool 42 | */ 43 | private $isCaseSensitive; 44 | 45 | /** 46 | * MarkerFramePicker constructor. 47 | * 48 | * @param null|string $startMarker Start marker 49 | * @param string $endMarker End marker 50 | * @param bool $isCaseSensitive True, if case is important 51 | */ 52 | public function __construct($startMarker, $endMarker, $isCaseSensitive = true) 53 | { 54 | parent::__construct(); 55 | $this->startMarker = $startMarker; 56 | $this->endMarker = $endMarker; 57 | $this->isCaseSensitive = $isCaseSensitive; 58 | } 59 | 60 | /** 61 | * Find start of data in frame 62 | * 63 | * @param string $buffer Collected data for frame 64 | * 65 | * @return bool True if start of frame is found 66 | */ 67 | protected function resolveStartOfFrame($buffer) 68 | { 69 | if ($this->startPos !== null) { 70 | return true; 71 | } 72 | 73 | if ($this->startMarker === null) { 74 | $this->startPos = 0; 75 | return true; 76 | } 77 | 78 | $pos = $this->findMarker($buffer, $this->startMarker); 79 | if ($pos !== false) { 80 | $this->startPos = $pos; 81 | return true; 82 | } 83 | 84 | return false; 85 | } 86 | 87 | /** {@inheritdoc} */ 88 | protected function doHandleData($chunk, $remoteAddress, &$buffer) 89 | { 90 | $buffer .= $chunk; 91 | if (!$this->resolveStartOfFrame($buffer)) { 92 | return ''; 93 | } 94 | 95 | $pos = $this->findMarker($buffer, $this->endMarker, $this->startPos + strlen($this->startMarker)); 96 | if ($pos === false) { 97 | return ''; 98 | } 99 | 100 | $this->setFinished(true); 101 | $result = substr($buffer, $pos + strlen($this->endMarker)); 102 | $buffer = substr($buffer, $this->startPos, $pos + strlen($this->endMarker) - $this->startPos); 103 | return $result !== false ? $result : ''; 104 | } 105 | 106 | /** {@inheritdoc} */ 107 | protected function doCreateFrame($buffer, $remoteAddress) 108 | { 109 | if ($this->isEof()) { 110 | return new Frame($buffer, $remoteAddress); 111 | } 112 | 113 | $data = $this->startPos === null ? '' : substr($buffer, $this->startPos); 114 | return new Frame($data, $remoteAddress); 115 | } 116 | 117 | /** 118 | * Performs strpos or stripos according to case sensibility 119 | * 120 | * @param string $haystack Where find text 121 | * @param string $needle What to find 122 | * @param int $offset Start offset in $haystack 123 | * 124 | * @return bool|int 125 | */ 126 | protected function findMarker($haystack, $needle, $offset = 0) 127 | { 128 | return $this->isCaseSensitive ? 129 | strpos($haystack, $needle, $offset) : 130 | stripos($haystack, $needle, $offset); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Frame/PartialFrame.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class PartialFrame. Special object indicates that data inside frame is incomplete 14 | */ 15 | class PartialFrame implements FrameInterface 16 | { 17 | /** 18 | * Original frame 19 | * 20 | * @var FrameInterface 21 | */ 22 | private $original; 23 | 24 | /** 25 | * PartialFrame constructor. 26 | * 27 | * @param FrameInterface $original Original frame 28 | */ 29 | public function __construct(FrameInterface $original) 30 | { 31 | $this->original = $original; 32 | } 33 | 34 | /** {@inheritdoc} */ 35 | public function getData() 36 | { 37 | return $this->original->getData(); 38 | } 39 | 40 | /** {@inheritdoc} */ 41 | public function __toString() 42 | { 43 | return (string) $this->original; 44 | } 45 | 46 | /** {@inheritdoc} */ 47 | public function getRemoteAddress() 48 | { 49 | return $this->original->getRemoteAddress(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Frame/RawFramePicker.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Frame; 11 | 12 | /** 13 | * Class RawFramePicker. This FramePicker allows to get chunks from network as it was read by fread 14 | */ 15 | class RawFramePicker extends AbstractFramePicker 16 | { 17 | /** {@inheritdoc} */ 18 | protected function doHandleData($chunk, $remoteAddress, &$buffer) 19 | { 20 | $buffer = $chunk; 21 | $this->setFinished(true); 22 | return ''; 23 | } 24 | 25 | /** {@inheritdoc} */ 26 | protected function doCreateFrame($buffer, $remoteAddress) 27 | { 28 | return new Frame($buffer, $remoteAddress); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Operation/DelayedOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Operation; 11 | 12 | /** 13 | * Class DelayedOperation. Execution of this operation will be delayed until callable returns true. 14 | */ 15 | class DelayedOperation implements OperationInterface 16 | { 17 | /** 18 | * Function to check whether operation is pending: 19 | * bool function(SocketInterface $socket, RequestExecutorInterface $executor) 20 | * 21 | * @var callable 22 | */ 23 | private $callable; 24 | 25 | /** 26 | * Original operation 27 | * 28 | * @var OperationInterface 29 | */ 30 | private $origin; 31 | 32 | /** 33 | * Additional arguments to pass into callback 34 | * 35 | * @var array 36 | */ 37 | private $arguments; 38 | 39 | /** 40 | * DelayedOperation constructor. 41 | * 42 | * @param OperationInterface $origin Original operation which should be delayed 43 | * @param callable $callable Function to check whether operation is pending: 44 | * bool function(SocketInterface $socket, RequestExecutorInterface $executor, ...$arguments) 45 | * @param array $arguments Additional arguments to pass into callback 46 | */ 47 | public function __construct(OperationInterface $origin, callable $callable, array $arguments = []) 48 | { 49 | $this->origin = $origin; 50 | $this->callable = $callable; 51 | $this->arguments = $arguments; 52 | } 53 | 54 | /** {@inheritdoc} */ 55 | public function getTypes() 56 | { 57 | return $this->origin->getTypes(); 58 | } 59 | 60 | /** 61 | * Returns function to invoke 62 | * 63 | * @return callable 64 | */ 65 | public function getCallable() 66 | { 67 | return $this->callable; 68 | } 69 | 70 | /** 71 | * Return original operation to run after delay 72 | * 73 | * @return OperationInterface 74 | */ 75 | public function getOriginalOperation() 76 | { 77 | return $this->origin; 78 | } 79 | 80 | /** 81 | * Return additional arguments to pass into callback 82 | * 83 | * @return array 84 | */ 85 | public function getArguments() 86 | { 87 | return $this->arguments; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Operation/InProgressWriteOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Operation; 11 | 12 | /** 13 | * Class InProgressWriteOperation 14 | */ 15 | class InProgressWriteOperation extends WriteOperation 16 | { 17 | /** 18 | * Next operation 19 | * 20 | * @var OperationInterface 21 | */ 22 | private $nextOperation; 23 | 24 | /** 25 | * WriteOperation constructor. 26 | * 27 | * @param OperationInterface $next Scheduled next write operation 28 | * @param string $data Data to send 29 | */ 30 | public function __construct(OperationInterface $next = null, $data = null) 31 | { 32 | parent::__construct($data); 33 | $this->nextOperation = $next; 34 | } 35 | 36 | /** 37 | * Return NextOperation 38 | * 39 | * @return OperationInterface 40 | */ 41 | public function getNextOperation() 42 | { 43 | return $this->nextOperation; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Operation/NullOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Operation; 11 | 12 | /** 13 | * Class NullOperation. Special no-value operation. Do not use in your code! 14 | */ 15 | class NullOperation implements OperationInterface 16 | { 17 | /** 18 | * Return single instance of operation 19 | * 20 | * @return NullOperation 21 | */ 22 | public static function getInstance() 23 | { 24 | static $instance; 25 | if (!$instance) { 26 | $instance = new self(); 27 | } 28 | 29 | return $instance; 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function getTypes() 36 | { 37 | return [self::OPERATION_READ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Operation/OperationInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Operation; 11 | 12 | /** 13 | * Interface OperationInterface 14 | */ 15 | interface OperationInterface 16 | { 17 | /** 18 | * Read operation 19 | */ 20 | const OPERATION_READ = 'read'; 21 | 22 | /** 23 | * Write operation 24 | */ 25 | const OPERATION_WRITE = 'write'; 26 | 27 | /** 28 | * Return operation types 29 | * 30 | * @return string[] List of OPERATION_* consts 31 | */ 32 | public function getTypes(); 33 | } 34 | -------------------------------------------------------------------------------- /src/Operation/ReadOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Operation; 11 | 12 | use AsyncSockets\Frame\FramePickerInterface; 13 | use AsyncSockets\Frame\RawFramePicker; 14 | 15 | /** 16 | * Class ReadOperation 17 | */ 18 | class ReadOperation implements OperationInterface 19 | { 20 | /** 21 | * Frame picker object 22 | * 23 | * @var FramePickerInterface 24 | */ 25 | private $framePicker; 26 | 27 | /** 28 | * ReadOperation constructor. 29 | * 30 | * @param FramePickerInterface $framePicker Frame picker object 31 | */ 32 | public function __construct(FramePickerInterface $framePicker = null) 33 | { 34 | $this->framePicker = $framePicker ?: new RawFramePicker(); 35 | } 36 | 37 | 38 | /** {@inheritdoc} */ 39 | public function getTypes() 40 | { 41 | return [self::OPERATION_READ]; 42 | } 43 | 44 | /** 45 | * Return FramePicker 46 | * 47 | * @return FramePickerInterface 48 | */ 49 | public function getFramePicker() 50 | { 51 | return $this->framePicker; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Operation/ReadWriteOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Operation; 12 | 13 | /** 14 | * Class ReadWriteOperation. Provides reading and writing at the same time 15 | */ 16 | class ReadWriteOperation implements OperationInterface 17 | { 18 | /** 19 | * Flag if read operations must be processed before write 20 | */ 21 | const READ_FIRST = true; 22 | 23 | /** 24 | * Flag if write operations must be processed before read 25 | */ 26 | const WRITE_FIRST = false; 27 | 28 | /** 29 | * Operation queue indexed by operation type 30 | * 31 | * @var OperationInterface[][] 32 | */ 33 | private $queue = []; 34 | 35 | /** 36 | * Flag whether read operation must be fired before write 37 | * 38 | * @var bool 39 | */ 40 | private $isReadFirst; 41 | 42 | /** 43 | * ReadWriteOperation constructor. 44 | * 45 | * @param bool $isReadFirst Flag whether read operation must be fired before write 46 | * @param OperationInterface[] $operations Array of operations to schedule 47 | */ 48 | public function __construct($isReadFirst, array $operations = []) 49 | { 50 | $this->isReadFirst = $isReadFirst; 51 | foreach ($operations as $operation) { 52 | $this->scheduleOperation($operation); 53 | } 54 | } 55 | 56 | /** 57 | * Set read or write operation 58 | * 59 | * @param OperationInterface $operation Operation to set 60 | * 61 | * @return void 62 | */ 63 | public function scheduleOperation(OperationInterface $operation) 64 | { 65 | $key = spl_object_hash($operation); 66 | switch (true) { 67 | case $operation instanceof ReadOperation: 68 | $this->queue[OperationInterface::OPERATION_READ][$key] = $operation; 69 | break; 70 | case $operation instanceof WriteOperation: 71 | $this->queue[OperationInterface::OPERATION_WRITE][$key] = $operation; 72 | break; 73 | default: 74 | // no action 75 | } 76 | } 77 | 78 | /** 79 | * Mark operation as completed 80 | * 81 | * @param OperationInterface $operation Operation to mark as done 82 | * 83 | * @return void 84 | */ 85 | public function markCompleted(OperationInterface $operation) 86 | { 87 | $key = spl_object_hash($operation); 88 | switch (true) { 89 | case $operation instanceof ReadOperation: 90 | unset($this->queue[OperationInterface::OPERATION_READ][$key]); 91 | break; 92 | case $operation instanceof WriteOperation: 93 | unset($this->queue[OperationInterface::OPERATION_WRITE][$key]); 94 | break; 95 | default: 96 | // no action 97 | } 98 | } 99 | 100 | /** 101 | * Return current read operation 102 | * 103 | * @return ReadOperation|null 104 | */ 105 | public function getReadOperation() 106 | { 107 | return !empty($this->queue[OperationInterface::OPERATION_READ]) ? 108 | reset($this->queue[OperationInterface::OPERATION_READ]) : 109 | null; 110 | } 111 | 112 | /** 113 | * Return current write operation 114 | * 115 | * @return WriteOperation|null 116 | */ 117 | public function getWriteOperation() 118 | { 119 | return !empty($this->queue[OperationInterface::OPERATION_WRITE]) ? 120 | reset($this->queue[OperationInterface::OPERATION_WRITE]) : 121 | null; 122 | } 123 | 124 | /** 125 | * Return true if read must be handled before write 126 | * 127 | * @return bool 128 | */ 129 | public function isReadFirst() 130 | { 131 | return $this->isReadFirst; 132 | } 133 | 134 | /** 135 | * @inheritDoc 136 | */ 137 | public function getTypes() 138 | { 139 | $result = []; 140 | foreach ($this->queue as $type => $operations) { 141 | if (!empty($operations)) { 142 | $result[] = $type; 143 | } 144 | } 145 | 146 | return $result; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Operation/SslHandshakeOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Operation; 11 | 12 | /** 13 | * Class SslHandshakeOperation 14 | */ 15 | class SslHandshakeOperation implements OperationInterface 16 | { 17 | /** 18 | * Cipher to use for SSL encryption 19 | * 20 | * @var int 21 | */ 22 | private $cipher; 23 | 24 | /** 25 | * I/O operation after handshake will complete 26 | * 27 | * @var OperationInterface 28 | */ 29 | private $nextOperation; 30 | 31 | /** 32 | * SslHandshakeOperation constructor. 33 | * 34 | * @param OperationInterface $nextOperation I/O operation after handshake will complete 35 | * @param int $cipher Cipher to use for SSL encryption 36 | */ 37 | public function __construct(OperationInterface $nextOperation = null, $cipher = STREAM_CRYPTO_METHOD_TLS_CLIENT) 38 | { 39 | $this->nextOperation = $nextOperation; 40 | $this->cipher = $cipher; 41 | } 42 | 43 | /** {@inheritdoc} */ 44 | public function getTypes() 45 | { 46 | return [self::OPERATION_WRITE]; 47 | } 48 | 49 | /** 50 | * Return Cipher 51 | * 52 | * @return int 53 | */ 54 | public function getCipher() 55 | { 56 | return $this->cipher; 57 | } 58 | 59 | /** 60 | * Return NextOperation 61 | * 62 | * @return OperationInterface 63 | */ 64 | public function getNextOperation() 65 | { 66 | return $this->nextOperation; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Operation/WriteOperation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Operation; 11 | 12 | /** 13 | * Class WriteOperation 14 | */ 15 | class WriteOperation implements OperationInterface 16 | { 17 | /** 18 | * Data to write 19 | * 20 | * @var string|array|\Traversable 21 | */ 22 | private $data; 23 | 24 | /** 25 | * Flag if this is an out-of-band writing 26 | * 27 | * @var bool 28 | */ 29 | private $isOutOfBand; 30 | 31 | /** 32 | * WriteOperation constructor. 33 | * 34 | * @param string|array|\Traversable $data Data to send 35 | * @param bool $isOutOfBand Flag if this is an out-of-band writing 36 | */ 37 | public function __construct($data = null, $isOutOfBand = false) 38 | { 39 | $this->data = $data !== null ? $data : null; 40 | $this->isOutOfBand = $isOutOfBand; 41 | } 42 | 43 | /** 44 | * Return flag if this is an out-of-band writing 45 | * 46 | * @return bool 47 | */ 48 | public function isOutOfBand() 49 | { 50 | return $this->isOutOfBand; 51 | } 52 | 53 | /** 54 | * Set out-of-band flag 55 | * 56 | * @param bool $isOutOfBand Flag if this is an out-of-band writing 57 | * 58 | * @return void 59 | */ 60 | public function setOutOfBand($isOutOfBand) 61 | { 62 | $this->isOutOfBand = $isOutOfBand; 63 | } 64 | 65 | /** {@inheritdoc} */ 66 | public function getTypes() 67 | { 68 | return [self::OPERATION_WRITE]; 69 | } 70 | 71 | /** 72 | * Return Data 73 | * 74 | * @return string|array|\Traversable 75 | */ 76 | public function getData() 77 | { 78 | return $this->data; 79 | } 80 | 81 | /** 82 | * Sets Data 83 | * 84 | * @param string|array|\Traversable $data Data to send 85 | * 86 | * @return void 87 | */ 88 | public function setData($data) 89 | { 90 | $this->data = $data; 91 | } 92 | 93 | /** 94 | * Checks whether request has data 95 | * 96 | * @return bool 97 | */ 98 | public function hasData() 99 | { 100 | return $this->data !== null; 101 | } 102 | 103 | /** 104 | * Clear send data 105 | * 106 | * @return void 107 | */ 108 | public function clearData() 109 | { 110 | $this->data = null; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/RequestExecutor/CallbackEventHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class CallbackEventHandler 17 | */ 18 | class CallbackEventHandler implements \Countable, EventHandlerInterface 19 | { 20 | /** 21 | * List of callables in this bag indexed by event name 22 | * 23 | * @var array 24 | */ 25 | private $handlers = []; 26 | 27 | /** 28 | * CallbackEventHandler constructor. 29 | * 30 | * @param array $events Events to handle: [ "eventName" => callable|callable[], ... ] 31 | */ 32 | public function __construct(array $events = []) 33 | { 34 | $this->addHandler($events); 35 | } 36 | 37 | 38 | /** 39 | * Add handler into this bag 40 | * 41 | * @param array $events Events to handle: [ "eventName" => callable|callable[], ... ] 42 | * 43 | * @return void 44 | */ 45 | public function addHandler(array $events) 46 | { 47 | foreach ($events as $eventName => $subscriber) { 48 | $this->handlers[$eventName] = array_merge( 49 | isset($this->handlers[$eventName]) ? $this->handlers[$eventName] : [], 50 | is_callable($subscriber) ? [$subscriber] : $subscriber 51 | ); 52 | } 53 | } 54 | 55 | /** 56 | * Remove specified handlers from this bag 57 | * 58 | * @param array $events Events to remove: [ "eventName" => callable|callable[], ... ] 59 | * 60 | * @return void 61 | */ 62 | public function removeHandler(array $events) 63 | { 64 | foreach ($events as $eventName => $subscribers) { 65 | if (!isset($this->handlers[$eventName])) { 66 | continue; 67 | } 68 | 69 | $subscribers = is_callable($subscribers) ? [$subscribers] : $subscribers; 70 | foreach ($subscribers as $subscriber) { 71 | $key = array_search($subscriber, $this->handlers[$eventName], true); 72 | if ($key !== false) { 73 | unset($this->handlers[$eventName][$key]); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Remove all handlers from bag 81 | * 82 | * @return void 83 | */ 84 | public function removeAll() 85 | { 86 | $this->handlers = []; 87 | } 88 | 89 | /** 90 | * Remove all handlers for given event name 91 | * 92 | * @param string $eventName Event name to remove handlers 93 | * 94 | * @return void 95 | */ 96 | public function removeForEvent($eventName) 97 | { 98 | if (isset($this->handlers[$eventName])) { 99 | unset($this->handlers[$eventName]); 100 | } 101 | } 102 | 103 | /** {@inheritdoc} */ 104 | public function invokeEvent( 105 | Event $event, 106 | RequestExecutorInterface $executor, 107 | SocketInterface $socket, 108 | ExecutionContext $context 109 | ) { 110 | $eventName = $event->getType(); 111 | $subscribers = isset($this->handlers[$eventName]) ? $this->handlers[$eventName] : []; 112 | foreach ($subscribers as $subscriber) { 113 | call_user_func($subscriber, $event, $executor, $socket, $context); 114 | } 115 | } 116 | 117 | /** {@inheritdoc} */ 118 | public function count() 119 | { 120 | return count($this->handlers); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/RequestExecutor/ConstantLimitationSolver.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Event\EventType; 14 | use AsyncSockets\Socket\SocketInterface; 15 | 16 | /** 17 | * Class ConstantLimitationSolver 18 | */ 19 | class ConstantLimitationSolver implements LimitationSolverInterface, EventHandlerInterface 20 | { 21 | /** 22 | * Limit of running requests 23 | * 24 | * @var int 25 | */ 26 | private $limit; 27 | 28 | /** 29 | * Key with number of active requests in execution context 30 | * 31 | * @var string 32 | */ 33 | private $key; 34 | 35 | /** 36 | * ConstantLimitationSolver constructor. 37 | * 38 | * @param int $limit Limit of running requests 39 | */ 40 | public function __construct($limit) 41 | { 42 | $this->limit = $limit; 43 | $this->key = uniqid(__CLASS__, true); 44 | } 45 | 46 | /** {@inheritdoc} */ 47 | public function initialize(RequestExecutorInterface $executor, ExecutionContext $executionContext) 48 | { 49 | $executionContext->inNamespace($this->key)->set('active_requests', 0); 50 | } 51 | 52 | /** {@inheritdoc} */ 53 | public function finalize(RequestExecutorInterface $executor, ExecutionContext $executionContext) 54 | { 55 | // empty body 56 | } 57 | 58 | /** {@inheritdoc} */ 59 | public function decide( 60 | RequestExecutorInterface $executor, 61 | SocketInterface $socket, 62 | ExecutionContext $executionContext, 63 | $totalSockets 64 | ) { 65 | $activeRequests = $executionContext->inNamespace($this->key)->get('active_requests'); 66 | if ($activeRequests + 1 <= $this->limit) { 67 | return self::DECISION_OK; 68 | } else { 69 | return self::DECISION_PROCESS_SCHEDULED; 70 | } 71 | } 72 | 73 | /** {@inheritdoc} */ 74 | public function invokeEvent( 75 | Event $event, 76 | RequestExecutorInterface $executor, 77 | SocketInterface $socket, 78 | ExecutionContext $context 79 | ) { 80 | switch ($event->getType()) { 81 | case EventType::INITIALIZE: 82 | $this->onSocketRequestInitialize($context); 83 | break; 84 | case EventType::FINALIZE: 85 | $this->onSocketRequestFinalize($context); 86 | break; 87 | } 88 | } 89 | 90 | /** 91 | * Process socket initialize event 92 | * 93 | * @param ExecutionContext $executionContext Execution context 94 | * 95 | * @return void 96 | */ 97 | private function onSocketRequestInitialize(ExecutionContext $executionContext) 98 | { 99 | $context = $executionContext->inNamespace($this->key); 100 | $context->set( 101 | 'active_requests', 102 | $context->get('active_requests') + 1 103 | ); 104 | } 105 | 106 | /** 107 | * Process request termination 108 | * 109 | * @param ExecutionContext $executionContext Execution context 110 | * 111 | * @return void 112 | */ 113 | private function onSocketRequestFinalize(ExecutionContext $executionContext) 114 | { 115 | $context = $executionContext->inNamespace($this->key); 116 | $context->set( 117 | 'active_requests', 118 | $context->get('active_requests') - 1 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/RequestExecutor/EventHandlerFromSymfonyEventDispatcher.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Socket\SocketInterface; 14 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 15 | 16 | /** 17 | * Class EventHandlerFromSymfonyEventDispatcher 18 | */ 19 | class EventHandlerFromSymfonyEventDispatcher implements EventHandlerInterface 20 | { 21 | /** 22 | * EventDispatcherInterface 23 | * 24 | * @var EventDispatcherInterface 25 | */ 26 | private $eventDispatcher; 27 | 28 | /** 29 | * EventHandlerFromSymfonyEventDispatcher constructor. 30 | * 31 | * @param EventDispatcherInterface $eventDispatcher Symfony event dispatcher 32 | */ 33 | public function __construct(EventDispatcherInterface $eventDispatcher) 34 | { 35 | $this->eventDispatcher = $eventDispatcher; 36 | } 37 | 38 | /** {@inheritdoc} */ 39 | public function invokeEvent( 40 | Event $event, 41 | RequestExecutorInterface $executor, 42 | SocketInterface $socket, 43 | ExecutionContext $context 44 | ) { 45 | $this->eventDispatcher->dispatch($event->getType(), $event); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/RequestExecutor/EventHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Interface EventHandlerInterface 17 | */ 18 | interface EventHandlerInterface 19 | { 20 | /** 21 | * Invokes on each event in RequestExecutor 22 | * 23 | * @param Event $event Event object 24 | * @param RequestExecutorInterface $executor Request executor fired an event 25 | * @param SocketInterface $socket Socket connected with event 26 | * @param ExecutionContext $context Global data context 27 | * 28 | * @return void 29 | */ 30 | public function invokeEvent( 31 | Event $event, 32 | RequestExecutorInterface $executor, 33 | SocketInterface $socket, 34 | ExecutionContext $context 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/RequestExecutor/EventMultiHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class EventMultiHandler 17 | */ 18 | class EventMultiHandler implements EventHandlerInterface 19 | { 20 | /** 21 | * List of handlers 22 | * 23 | * @var EventHandlerInterface[] 24 | */ 25 | private $handlers = []; 26 | 27 | /** 28 | * EventMultiHandler constructor. 29 | * 30 | * @param EventHandlerInterface[] $handlers List of handlers 31 | */ 32 | public function __construct(array $handlers = []) 33 | { 34 | foreach ($handlers as $handler) { 35 | $this->addHandler($handler); 36 | } 37 | } 38 | 39 | /** {@inheritdoc} */ 40 | public function invokeEvent( 41 | Event $event, 42 | RequestExecutorInterface $executor, 43 | SocketInterface $socket, 44 | ExecutionContext $context 45 | ) { 46 | foreach ($this->handlers as $handler) { 47 | $handler->invokeEvent($event, $executor, $socket, $context); 48 | } 49 | } 50 | 51 | /** 52 | * Add handler to list 53 | * 54 | * @param EventHandlerInterface $handler Handler to add 55 | * 56 | * @return void 57 | */ 58 | public function addHandler(EventHandlerInterface $handler) 59 | { 60 | $key = $this->getHandlerKey($handler); 61 | $this->handlers[$key] = $handler; 62 | } 63 | 64 | /** 65 | * Remove handler from list 66 | * 67 | * @param EventHandlerInterface $handler Handler to remove 68 | * 69 | * @return void 70 | */ 71 | public function removeHandler(EventHandlerInterface $handler) 72 | { 73 | $key = $this->getHandlerKey($handler); 74 | if (isset($this->handlers[$key])) { 75 | unset($this->handlers[$key]); 76 | } 77 | } 78 | 79 | /** 80 | * Return key for given handler 81 | * 82 | * @param EventHandlerInterface $handler Handler to get key for 83 | * 84 | * @return string 85 | */ 86 | private function getHandlerKey(EventHandlerInterface $handler) 87 | { 88 | return spl_object_hash($handler); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/RequestExecutor/ExecutionContext.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\RequestExecutor; 12 | 13 | /** 14 | * Class ExecutionContext 15 | */ 16 | class ExecutionContext implements \ArrayAccess 17 | { 18 | /** 19 | * Data items 20 | * 21 | * @var array 22 | */ 23 | private $items; 24 | 25 | /** 26 | * Nested contexts 27 | * 28 | * @var ExecutionContext[] 29 | */ 30 | private $namespaces; 31 | 32 | /** 33 | * ExecutionContext constructor. 34 | * 35 | * @param array $items Initial data for context 36 | */ 37 | public function __construct(array $items = []) 38 | { 39 | $this->items = $items; 40 | $this->namespaces = []; 41 | } 42 | 43 | /** 44 | * Return nested isolated execution context 45 | * 46 | * @param string $namespace Namespace name 47 | * 48 | * @return ExecutionContext 49 | */ 50 | public function inNamespace($namespace) 51 | { 52 | if (!isset($this->namespaces[$namespace])) { 53 | $this->namespaces[$namespace] = new static([]); 54 | } 55 | 56 | return $this->namespaces[$namespace]; 57 | } 58 | 59 | /** 60 | * Clears data inside the context 61 | * 62 | * @return void 63 | */ 64 | public function clear() 65 | { 66 | $this->items = []; 67 | } 68 | 69 | /** 70 | * Set a value 71 | * 72 | * @param string|int $key Key 73 | * @param mixed $value A value 74 | * 75 | * @return void 76 | */ 77 | public function set($key, $value) 78 | { 79 | $this->offsetSet($key, $value); 80 | } 81 | 82 | /** 83 | * Return a value stored under the key 84 | * 85 | * @param string|int $key A key 86 | * @param mixed $default Default value to return if key does not exist 87 | * 88 | * @return mixed 89 | */ 90 | public function get($key, $default = null) 91 | { 92 | return $this->has($key) ? $this->items[$key] : $default; 93 | } 94 | 95 | /** 96 | * Return true if context has value for a given key 97 | * 98 | * @param string|int $key A key 99 | * 100 | * @return bool 101 | */ 102 | public function has($key) 103 | { 104 | return $this->offsetExists($key); 105 | } 106 | 107 | /** 108 | * Removes a value under given key 109 | * 110 | * @param string|int $key A key 111 | * 112 | * @return void 113 | */ 114 | public function remove($key) 115 | { 116 | $this->offsetUnset($key); 117 | } 118 | 119 | /** 120 | * @inheritDoc 121 | */ 122 | public function offsetExists($offset) 123 | { 124 | return isset($this->items[$offset]) || array_key_exists($offset, $this->items); 125 | } 126 | 127 | /** 128 | * @inheritDoc 129 | */ 130 | public function offsetGet($offset) 131 | { 132 | return $this->offsetExists($offset) ? $this->items[$offset] : null; 133 | } 134 | 135 | /** 136 | * @inheritDoc 137 | */ 138 | public function offsetSet($offset, $value) 139 | { 140 | $this->items[$offset] = $value; 141 | } 142 | 143 | /** 144 | * @inheritDoc 145 | */ 146 | public function offsetUnset($offset) 147 | { 148 | unset($this->items[$offset]); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/RequestExecutor/IoHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Operation\OperationInterface; 13 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 14 | 15 | /** 16 | * Interface IoHandlerInterface 17 | */ 18 | interface IoHandlerInterface 19 | { 20 | /** 21 | * Check whether this handler supports given operation 22 | * 23 | * @param OperationInterface $operation Operation to test 24 | * 25 | * @return bool 26 | */ 27 | public function supports(OperationInterface $operation); 28 | 29 | /** 30 | * Process given operation 31 | * 32 | * @param OperationInterface $operation Operation to process 33 | * @param RequestDescriptor $descriptor Request descriptor 34 | * @param RequestExecutorInterface $executor Executor, processing operation 35 | * @param EventHandlerInterface $eventHandler Event handler for this operation 36 | * @param ExecutionContext $executionContext Execution context 37 | * 38 | * @return OperationInterface|null Next operation to pass in socket. Return null, 39 | * if next operation is not required. Return $operation parameter, if operation is not completed yet 40 | */ 41 | public function handle( 42 | OperationInterface $operation, 43 | RequestDescriptor $descriptor, 44 | RequestExecutorInterface $executor, 45 | EventHandlerInterface $eventHandler, 46 | ExecutionContext $executionContext 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/RequestExecutor/LibEvent/LeCallbackInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\LibEvent; 11 | 12 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 13 | 14 | /** 15 | * Interface LeCallbackInterface 16 | */ 17 | interface LeCallbackInterface 18 | { 19 | /** 20 | * Read event 21 | */ 22 | const EVENT_READ = 'read'; 23 | 24 | /** 25 | * Write event 26 | */ 27 | const EVENT_WRITE = 'write'; 28 | 29 | /** 30 | * Timeout event 31 | */ 32 | const EVENT_TIMEOUT = 'timeout'; 33 | 34 | /** 35 | * Handle event from libevent 36 | * 37 | * @param RequestDescriptor $requestDescriptor Request descriptor object 38 | * @param string $type One of EVENT_* consts 39 | * 40 | */ 41 | public function onEvent(RequestDescriptor $requestDescriptor, $type); 42 | } 43 | -------------------------------------------------------------------------------- /src/RequestExecutor/LibEvent/LeEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\LibEvent; 11 | 12 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 13 | 14 | /** 15 | * Class LeEvent 16 | */ 17 | class LeEvent 18 | { 19 | /** 20 | * Libevent event handle 21 | * 22 | * @var resource 23 | */ 24 | private $handle; 25 | 26 | /** 27 | * Callback object 28 | * 29 | * @var LeCallbackInterface 30 | */ 31 | private $callback; 32 | 33 | /** 34 | * RequestDescriptor 35 | * 36 | * @var RequestDescriptor 37 | */ 38 | private $requestDescriptor; 39 | 40 | /** 41 | * Timeout for event 42 | * 43 | * @var int|null 44 | */ 45 | private $timeout; 46 | 47 | /** 48 | * LeEvent constructor. 49 | * 50 | * @param LeCallbackInterface $callback Callback object 51 | * @param RequestDescriptor $requestDescriptor Request descriptor object 52 | * @param int|null $timeout Timeout for event 53 | */ 54 | public function __construct(LeCallbackInterface $callback, RequestDescriptor $requestDescriptor, $timeout) 55 | { 56 | $this->handle = event_new(); 57 | $this->callback = $callback; 58 | $this->requestDescriptor = $requestDescriptor; 59 | $this->timeout = $timeout; 60 | } 61 | 62 | /** 63 | * Destructor 64 | */ 65 | public function __destruct() 66 | { 67 | $this->destroy(); 68 | } 69 | 70 | /** 71 | * Return Timeout 72 | * 73 | * @return int|null 74 | */ 75 | public function getTimeout() 76 | { 77 | return $this->timeout; 78 | } 79 | 80 | /** 81 | * Return Handle 82 | * 83 | * @return resource 84 | */ 85 | public function getHandle() 86 | { 87 | return $this->handle; 88 | } 89 | 90 | /** 91 | * Return RequestDescriptor 92 | * 93 | * @return RequestDescriptor 94 | */ 95 | public function getRequestDescriptor() 96 | { 97 | return $this->requestDescriptor; 98 | } 99 | 100 | /** 101 | * Fire event 102 | * 103 | * @param string $eventType Type of event, one of LeCallbackInterface::EVENT_* consts 104 | * 105 | * @return void 106 | */ 107 | public function fire($eventType) 108 | { 109 | $this->callback->onEvent($this->requestDescriptor, $eventType); 110 | } 111 | 112 | /** 113 | * Destroy event handle 114 | * 115 | * @return void 116 | */ 117 | private function destroy() 118 | { 119 | if ($this->handle) { 120 | event_del($this->handle); 121 | event_free($this->handle); 122 | $this->handle = null; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/RequestExecutor/LimitationSolverInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Interface LimitationSolverInterface. Allows to limit amount of requests processing at once 16 | */ 17 | interface LimitationSolverInterface 18 | { 19 | /** 20 | * Schedule given socket request 21 | */ 22 | const DECISION_OK = 0; 23 | 24 | /** 25 | * Process already scheduled operations 26 | */ 27 | const DECISION_PROCESS_SCHEDULED = 1; 28 | 29 | /** 30 | * Skip given request now 31 | */ 32 | const DECISION_SKIP_CURRENT = 2; 33 | 34 | /** 35 | * Process initialization of request 36 | * 37 | * @param RequestExecutorInterface $executor Request executor 38 | * @param ExecutionContext $executionContext Execution context 39 | * 40 | * @return void 41 | */ 42 | public function initialize(RequestExecutorInterface $executor, ExecutionContext $executionContext); 43 | 44 | /** 45 | * Process finalization of request 46 | * 47 | * @param RequestExecutorInterface $executor Request executor 48 | * @param ExecutionContext $executionContext Execution context 49 | * 50 | * @return void 51 | */ 52 | public function finalize(RequestExecutorInterface $executor, ExecutionContext $executionContext); 53 | 54 | /** 55 | * Decide what to do with current request 56 | * 57 | * @param RequestExecutorInterface $executor Request executor 58 | * @param SocketInterface $socket Socket for operation 59 | * @param ExecutionContext $executionContext Execution context 60 | * @param int $totalSockets Total amount of scheduled sockets at moment of method call 61 | * 62 | * @return int One of DECISION_* consts 63 | */ 64 | public function decide( 65 | RequestExecutorInterface $executor, 66 | SocketInterface $socket, 67 | ExecutionContext $executionContext, 68 | $totalSockets 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /src/RequestExecutor/Metadata/SpeedRateCounter.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Metadata; 11 | 12 | /** 13 | * Class SpeedRateCounter 14 | */ 15 | class SpeedRateCounter 16 | { 17 | /** 18 | * Amount of processed bytes 19 | * 20 | * @var int 21 | */ 22 | private $totalBytesProcessed; 23 | 24 | /** 25 | * Time when request is started 26 | * 27 | * @var double 28 | */ 29 | private $initialTime; 30 | 31 | /** 32 | * Time of last measurement 33 | * 34 | * @var double 35 | */ 36 | private $currentTime; 37 | 38 | /** 39 | * Time when speed felt below minimal 40 | * 41 | * @var double 42 | */ 43 | private $slowStartTime; 44 | 45 | /** 46 | * Minimum allowed speed in bytes per second 47 | * 48 | * @var double 49 | */ 50 | private $minSpeed; 51 | 52 | /** 53 | * Maximum duration of minimum speed in seconds 54 | * 55 | * @var double 56 | */ 57 | private $maxDuration; 58 | 59 | /** 60 | * Current speed 61 | * 62 | * @var double 63 | */ 64 | private $currentSpeed; 65 | 66 | /** 67 | * SpeedRateCounter constructor. 68 | * 69 | * @param double $minSpeed Minimum allowed speed in bytes per second 70 | * @param double $maxDuration Maximum duration of minimum speed in seconds 71 | */ 72 | public function __construct($minSpeed, $maxDuration) 73 | { 74 | $this->minSpeed = $minSpeed; 75 | $this->maxDuration = $maxDuration; 76 | $this->reset(); 77 | } 78 | 79 | /** 80 | * Resets this counter 81 | * 82 | * @return void 83 | */ 84 | public function reset() 85 | { 86 | $this->initialTime = null; 87 | $this->currentTime = null; 88 | $this->totalBytesProcessed = 0; 89 | $this->currentSpeed = 0.0; 90 | $this->slowStartTime = null; 91 | } 92 | 93 | /** 94 | * Process next measurement 95 | * 96 | * @param double $time Time in seconds 97 | * @param double $value Amount of received data in bytes 98 | * 99 | * @return void 100 | * @throws \OverflowException When speed is slower then desired 101 | */ 102 | public function advance($time, $value) 103 | { 104 | $this->measure($time, $value); 105 | $this->currentSpeed = $this->getAverageSpeed(); 106 | $skipCheck = $this->minSpeed === null || 107 | $this->maxDuration === null || 108 | $this->currentSpeed === null || 109 | $this->currentSpeed >= $this->minSpeed; 110 | 111 | if ($skipCheck) { 112 | $this->slowStartTime = null; 113 | return; 114 | } 115 | 116 | $this->slowStartTime = $this->slowStartTime !== null ? $this->slowStartTime : $time; 117 | 118 | if ($time - $this->slowStartTime > $this->maxDuration) { 119 | throw new \OverflowException(); 120 | } 121 | } 122 | 123 | /** 124 | * Adds measure for counter 125 | * 126 | * @param double $time Time for given value in absolute timestamp 127 | * @param double $value A value 128 | * 129 | * @return void 130 | */ 131 | private function measure($time, $value) 132 | { 133 | if ($this->initialTime === null) { 134 | $this->initialTime = $time; 135 | } else { 136 | $this->currentTime = $time; 137 | } 138 | 139 | $this->totalBytesProcessed += $value; 140 | } 141 | 142 | /** 143 | * Return average speed for measurements 144 | * 145 | * @return double|null 146 | */ 147 | private function getAverageSpeed() 148 | { 149 | $timeElapsed = $this->currentTime - $this->initialTime; 150 | 151 | return $timeElapsed > 0 ? ($this->totalBytesProcessed / $timeElapsed) : 0.0; 152 | } 153 | 154 | /** 155 | * Return current speed in bytes per seconds 156 | * 157 | * @return float 158 | */ 159 | public function getCurrentSpeed() 160 | { 161 | return $this->getAverageSpeed(); 162 | } 163 | 164 | /** 165 | * Return duration of current slow speed 166 | * 167 | * @return double 168 | */ 169 | public function getCurrentDuration() 170 | { 171 | return $this->slowStartTime !== null ? $this->currentTime - $this->slowStartTime : 0.0; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/RequestExecutor/NativeRequestExecutor.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\RequestExecutor; 12 | 13 | use AsyncSockets\Configuration\Configuration; 14 | use AsyncSockets\RequestExecutor\Pipeline\EventCaller; 15 | use AsyncSockets\RequestExecutor\Pipeline\Pipeline; 16 | use AsyncSockets\RequestExecutor\Pipeline\PipelineFactory; 17 | 18 | /** 19 | * Class RequestExecutor 20 | */ 21 | class NativeRequestExecutor extends AbstractRequestExecutor 22 | { 23 | /** 24 | * Pipeline 25 | * 26 | * @var Pipeline 27 | */ 28 | private $pipeline; 29 | 30 | /** 31 | * PipelineFactory 32 | * 33 | * @var PipelineFactory 34 | */ 35 | private $pipelineFactory; 36 | 37 | /** 38 | * RequestExecutor constructor. 39 | * 40 | * @param PipelineFactory $pipelineFactory Pipeline factory 41 | * @param Configuration $configuration Configuration for executor 42 | */ 43 | public function __construct(PipelineFactory $pipelineFactory, Configuration $configuration) 44 | { 45 | parent::__construct($configuration); 46 | $this->pipelineFactory = $pipelineFactory; 47 | } 48 | 49 | /** {@inheritdoc} */ 50 | protected function initializeRequest(EventCaller $eventCaller, ExecutionContext $executionContext) 51 | { 52 | parent::initializeRequest($eventCaller, $executionContext); 53 | $this->pipeline = $this->pipelineFactory->createPipeline($this, $executionContext, $eventCaller, $this->solver); 54 | } 55 | 56 | /** {@inheritdoc} */ 57 | protected function terminateRequest(ExecutionContext $executionContext) 58 | { 59 | parent::terminateRequest($executionContext); 60 | $this->pipeline = null; 61 | } 62 | 63 | /** {@inheritdoc} */ 64 | protected function doExecuteRequest(EventCaller $eventCaller, ExecutionContext $executionContext) 65 | { 66 | $this->pipeline->process($this->socketBag); 67 | } 68 | 69 | /** {@inheritdoc} */ 70 | protected function disconnectItems(array $items) 71 | { 72 | $this->pipeline->disconnectSockets($items); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/RequestExecutor/NoLimitationSolver.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class NoLimitationSolver 16 | */ 17 | class NoLimitationSolver implements LimitationSolverInterface 18 | { 19 | /** {@inheritdoc} */ 20 | public function initialize(RequestExecutorInterface $executor, ExecutionContext $executionContext) 21 | { 22 | // empty body 23 | } 24 | 25 | /** {@inheritdoc} */ 26 | public function finalize(RequestExecutorInterface $executor, ExecutionContext $executionContext) 27 | { 28 | // empty body 29 | } 30 | 31 | /** {@inheritdoc} */ 32 | public function decide( 33 | RequestExecutorInterface $executor, 34 | SocketInterface $socket, 35 | ExecutionContext $executionContext, 36 | $totalSockets 37 | ) { 38 | return self::DECISION_OK; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/AbstractStage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Exception\SocketException; 14 | use AsyncSockets\RequestExecutor\ExecutionContext; 15 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 16 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 17 | 18 | /** 19 | * Class AbstractStage 20 | */ 21 | abstract class AbstractStage implements PipelineStageInterface 22 | { 23 | /** 24 | * Event caller 25 | * 26 | * @var EventCaller 27 | */ 28 | protected $eventCaller; 29 | 30 | /** 31 | * Request executor 32 | * 33 | * @var RequestExecutorInterface 34 | */ 35 | protected $executor; 36 | 37 | /** 38 | * Execution context 39 | * 40 | * @var ExecutionContext 41 | */ 42 | protected $executionContext; 43 | 44 | /** 45 | * AbstractStage constructor. 46 | * 47 | * @param RequestExecutorInterface $executor Request executor 48 | * @param EventCaller $eventCaller Event caller 49 | * @param ExecutionContext $executionContext Execution context 50 | */ 51 | public function __construct( 52 | RequestExecutorInterface $executor, 53 | EventCaller $eventCaller, 54 | ExecutionContext $executionContext 55 | ) { 56 | $this->executor = $executor; 57 | $this->eventCaller = $eventCaller; 58 | $this->executionContext = $executionContext; 59 | } 60 | 61 | /** 62 | * Notify handlers about given event 63 | * 64 | * @param RequestDescriptor $requestDescriptor Request descriptor 65 | * @param Event $event Event object 66 | * 67 | * @return void 68 | * @throws \Exception 69 | */ 70 | protected function callSocketSubscribers(RequestDescriptor $requestDescriptor, Event $event) 71 | { 72 | try { 73 | $this->eventCaller->setCurrentOperation($requestDescriptor); 74 | $this->eventCaller->callSocketSubscribers( 75 | $requestDescriptor, 76 | $event, 77 | $this->executor, 78 | $this->executionContext 79 | ); 80 | $this->eventCaller->clearCurrentOperation(); 81 | } catch (\Exception $e) { 82 | $this->eventCaller->clearCurrentOperation(); 83 | throw $e; 84 | } 85 | } 86 | 87 | /** 88 | * Notify handlers about exception 89 | * 90 | * @param RequestDescriptor $requestDescriptor Socket operation object 91 | * @param SocketException $exception Thrown exception 92 | * 93 | * @return void 94 | * @throws \Exception 95 | */ 96 | protected function callExceptionSubscribers( 97 | RequestDescriptor $requestDescriptor, 98 | SocketException $exception 99 | ) { 100 | try { 101 | $this->eventCaller->setCurrentOperation($requestDescriptor); 102 | $this->eventCaller->callExceptionSubscribers( 103 | $requestDescriptor, 104 | $exception, 105 | $this->executor, 106 | $this->executionContext 107 | ); 108 | $this->eventCaller->clearCurrentOperation(); 109 | } catch (\Exception $e) { 110 | $this->eventCaller->clearCurrentOperation(); 111 | throw $e; 112 | } 113 | } 114 | 115 | /** 116 | * Create simple event 117 | * 118 | * @param RequestDescriptor $operation Operation item 119 | * @param string $eventName Event name for object 120 | * 121 | * @return Event 122 | */ 123 | protected function createEvent(RequestDescriptor $operation, $eventName) 124 | { 125 | $meta = $operation->getMetadata(); 126 | 127 | return new Event( 128 | $this->executor, 129 | $operation->getSocket(), 130 | $meta[ RequestExecutorInterface::META_USER_CONTEXT ], 131 | $eventName 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/AbstractTimeAwareStage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 13 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 14 | 15 | /** 16 | * Class AbstractTimeAwareStage 17 | */ 18 | abstract class AbstractTimeAwareStage extends AbstractStage 19 | { 20 | /** 21 | * Return true, if connect time settings should be used for time operations, false otherwise 22 | * 23 | * @param RequestDescriptor $operation Operation object 24 | * 25 | * @return bool 26 | */ 27 | protected function hasConnected(RequestDescriptor $operation) 28 | { 29 | $meta = $operation->getMetadata(); 30 | return $meta[RequestExecutorInterface::META_CONNECTION_FINISH_TIME] !== null; 31 | } 32 | 33 | /** 34 | * Return timeout for current socket state 35 | * 36 | * @param RequestDescriptor $operation Operation object 37 | * 38 | * @return double 39 | */ 40 | protected function timeoutSetting(RequestDescriptor $operation) 41 | { 42 | $meta = $operation->getMetadata(); 43 | return !$this->hasConnected($operation) ? 44 | $meta[ RequestExecutorInterface::META_CONNECTION_TIMEOUT ] : 45 | $meta[ RequestExecutorInterface::META_IO_TIMEOUT ]; 46 | } 47 | 48 | /** 49 | * Return time since last I/O for current socket state 50 | * 51 | * @param RequestDescriptor $operation Operation object 52 | * 53 | * @return double|null 54 | */ 55 | protected function timeSinceLastIo(RequestDescriptor $operation) 56 | { 57 | $meta = $operation->getMetadata(); 58 | return !$this->hasConnected($operation) ? 59 | $meta[ RequestExecutorInterface::META_CONNECTION_START_TIME ] : 60 | $meta[ RequestExecutorInterface::META_LAST_IO_START_TIME ]; 61 | } 62 | 63 | /** 64 | * Set start or finish time in metadata of the socket 65 | * 66 | * @param RequestDescriptor $requestDescriptor Socket meta data 67 | * @param string $key Metadata key to set 68 | * 69 | * @return void 70 | * @throws \InvalidArgumentException 71 | */ 72 | protected function setSocketOperationTime(RequestDescriptor $requestDescriptor, $key) 73 | { 74 | $meta = $requestDescriptor->getMetadata(); 75 | $table = [ 76 | RequestExecutorInterface::META_CONNECTION_START_TIME => 77 | $meta[ RequestExecutorInterface::META_CONNECTION_START_TIME ] === null, 78 | 79 | RequestExecutorInterface::META_CONNECTION_FINISH_TIME => 80 | $meta[RequestExecutorInterface::META_CONNECTION_FINISH_TIME] === null, 81 | 82 | RequestExecutorInterface::META_LAST_IO_START_TIME => 83 | $meta[RequestExecutorInterface::META_CONNECTION_FINISH_TIME] !== null 84 | ]; 85 | 86 | if (isset($table[$key]) && $table[$key]) { 87 | $requestDescriptor->setMetadata($key, microtime(true)); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/BaseStageFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\ExecutionContext; 13 | use AsyncSockets\RequestExecutor\LimitationSolverInterface; 14 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 15 | use AsyncSockets\Socket\AsyncSelector; 16 | 17 | /** 18 | * Class BaseStageFactory 19 | */ 20 | class BaseStageFactory implements StageFactoryInterface 21 | { 22 | /** {@inheritdoc} */ 23 | public function createConnectStage( 24 | RequestExecutorInterface $executor, 25 | ExecutionContext $executionContext, 26 | EventCaller $caller, 27 | LimitationSolverInterface $limitationSolver 28 | ) { 29 | return new ConnectStageReturningAllActiveSockets($executor, $caller, $limitationSolver, $executionContext); 30 | } 31 | 32 | /** {@inheritdoc} */ 33 | public function createDelayStage( 34 | RequestExecutorInterface $executor, 35 | ExecutionContext $executionContext, 36 | EventCaller $caller 37 | ) { 38 | return new DelayStage($executor, $caller, $executionContext); 39 | } 40 | 41 | /** {@inheritdoc} */ 42 | public function createIoStage( 43 | RequestExecutorInterface $executor, 44 | ExecutionContext $executionContext, 45 | EventCaller $caller 46 | ) { 47 | $duplexHandler = new ReadWriteIoHandler(); 48 | $handler = new DelegatingIoHandler( 49 | [ 50 | new ReadIoHandler(), 51 | new WriteIoHandler(), 52 | new SslHandshakeIoHandler(), 53 | new NullIoHandler(), 54 | new ReadWriteIoHandler() 55 | ] 56 | ); 57 | 58 | $duplexHandler->setHandler($handler); 59 | 60 | return new IoStage( 61 | $executor, 62 | $caller, 63 | $executionContext, 64 | $handler 65 | ); 66 | } 67 | 68 | /** {@inheritdoc} */ 69 | public function createDisconnectStage( 70 | RequestExecutorInterface $executor, 71 | ExecutionContext $executionContext, 72 | EventCaller $caller, 73 | AsyncSelector $selector = null 74 | ) { 75 | $disconnectStage = new DisconnectStage($executor, $caller, $executionContext, $selector); 76 | $guardianStage = new GuardianStage($executor, $caller, $executionContext, $disconnectStage); 77 | 78 | return new CompositeStage( 79 | [ 80 | $disconnectStage, 81 | $guardianStage 82 | ] 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/CompositeStage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | /** 13 | * Class CompositeStage 14 | */ 15 | class CompositeStage implements PipelineStageInterface 16 | { 17 | /** 18 | * Stages 19 | * 20 | * @var PipelineStageInterface[] 21 | */ 22 | private $stages; 23 | 24 | /** 25 | * CompositeStage constructor. 26 | * 27 | * @param PipelineStageInterface[] $stages Stages to process by this composite 28 | */ 29 | public function __construct(array $stages) 30 | { 31 | $this->stages = $stages; 32 | } 33 | 34 | /** 35 | * @inheritDoc 36 | */ 37 | public function processStage(array $requestDescriptors) 38 | { 39 | $result = $requestDescriptors; 40 | foreach ($this->stages as $stage) { 41 | $result = $stage->processStage($result); 42 | } 43 | 44 | return $result; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/ConnectStageReturningAllActiveSockets.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 13 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 14 | 15 | /** 16 | * Class ConnectStageReturningAllActiveSockets 17 | */ 18 | class ConnectStageReturningAllActiveSockets extends ConnectStage 19 | { 20 | /** {@inheritdoc} */ 21 | public function processStage(array $requestDescriptors) 22 | { 23 | parent::processStage($requestDescriptors); 24 | return $this->getActiveOperations($requestDescriptors); 25 | } 26 | 27 | /** 28 | * Return array of keys for socket waiting for processing 29 | * 30 | * @param RequestDescriptor[] $requestDescriptors List of all requestDescriptors 31 | * 32 | * @return RequestDescriptor[] 33 | */ 34 | private function getActiveOperations(array $requestDescriptors) 35 | { 36 | $result = []; 37 | foreach ($requestDescriptors as $key => $descriptor) { 38 | if ($this->isDescriptorActive($descriptor)) { 39 | $result[$key] = $descriptor; 40 | } 41 | } 42 | 43 | return $result; 44 | } 45 | 46 | /** 47 | * Check whether given descriptor is active 48 | * 49 | * @param RequestDescriptor $descriptor 50 | * 51 | * @return bool 52 | */ 53 | private function isDescriptorActive(RequestDescriptor $descriptor) 54 | { 55 | $meta = $descriptor->getMetadata(); 56 | return !$meta[ RequestExecutorInterface::META_REQUEST_COMPLETE ] && 57 | $descriptor->isRunning() && 58 | !$descriptor->isForgotten(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/DelayStage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\Operation\DelayedOperation; 13 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 14 | 15 | /** 16 | * Class DelayStage 17 | */ 18 | class DelayStage extends AbstractStage 19 | { 20 | /** {@inheritdoc} */ 21 | public function processStage(array $requestDescriptors) 22 | { 23 | $result = []; 24 | 25 | /** @var RequestDescriptor[] $requestDescriptors */ 26 | foreach ($requestDescriptors as $requestDescriptor) { 27 | $operation = $requestDescriptor->getOperation(); 28 | if ($operation instanceof DelayedOperation) { 29 | if ($this->checkDelayIsFinished($requestDescriptor)) { 30 | $requestDescriptor->setOperation($operation->getOriginalOperation()); 31 | $result[] = $requestDescriptor; 32 | } 33 | } else { 34 | $result[] = $requestDescriptor; 35 | } 36 | } 37 | 38 | return $result; 39 | } 40 | 41 | /** 42 | * Check whether socket waiting is finished 43 | * 44 | * @param RequestDescriptor $descriptor Request descriptor to test 45 | * 46 | * @return bool True if delay is complete, false otherwise 47 | */ 48 | private function checkDelayIsFinished(RequestDescriptor $descriptor) 49 | { 50 | /** @var DelayedOperation $socketOperation */ 51 | $socketOperation = $descriptor->getOperation(); 52 | $arguments = $socketOperation->getArguments(); 53 | array_unshift($arguments, $descriptor->getSocket(), $this->executor); 54 | 55 | return !call_user_func_array( 56 | $socketOperation->getCallable(), 57 | $arguments 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/DelegatingIoHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\RequestExecutor\Pipeline; 12 | 13 | use AsyncSockets\Operation\OperationInterface; 14 | use AsyncSockets\RequestExecutor\EventHandlerInterface; 15 | use AsyncSockets\RequestExecutor\ExecutionContext; 16 | use AsyncSockets\RequestExecutor\IoHandlerInterface; 17 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 18 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 19 | 20 | /** 21 | * Class DelegatingIoHandler 22 | */ 23 | class DelegatingIoHandler implements IoHandlerInterface 24 | { 25 | /** 26 | * List of nested handlers 27 | * 28 | * @var IoHandlerInterface[] 29 | */ 30 | private $handlers; 31 | 32 | /** 33 | * DelegatingIoHandler constructor. 34 | * 35 | * @param IoHandlerInterface[] $handlers List of nested handlers 36 | */ 37 | public function __construct(array $handlers) 38 | { 39 | $this->handlers = $handlers; 40 | } 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | public function supports(OperationInterface $operation) 46 | { 47 | return $this->getHandler($operation) !== null; 48 | } 49 | 50 | /** 51 | * @inheritDoc 52 | */ 53 | public function handle( 54 | OperationInterface $operation, 55 | RequestDescriptor $descriptor, 56 | RequestExecutorInterface $executor, 57 | EventHandlerInterface $eventHandler, 58 | ExecutionContext $executionContext 59 | ) { 60 | $handler = $this->getHandler($operation); 61 | return $handler ? 62 | $handler->handle($operation, $descriptor, $executor, $eventHandler, $executionContext) : 63 | null; 64 | } 65 | 66 | /** 67 | * Return handler for given operation if there is any 68 | * 69 | * @param OperationInterface $operation Operation to get handler for 70 | * 71 | * @return IoHandlerInterface|null 72 | */ 73 | private function getHandler(OperationInterface $operation) 74 | { 75 | foreach ($this->handlers as $handler) { 76 | if ($handler->supports($operation)) { 77 | return $handler; 78 | } 79 | } 80 | 81 | return null; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/EventCaller.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Event\SocketExceptionEvent; 14 | use AsyncSockets\Exception\SocketException; 15 | use AsyncSockets\Exception\StopSocketOperationException; 16 | use AsyncSockets\RequestExecutor\EventHandlerInterface; 17 | use AsyncSockets\RequestExecutor\ExecutionContext; 18 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 19 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 20 | use AsyncSockets\Socket\SocketInterface; 21 | 22 | /** 23 | * Class EventCaller 24 | */ 25 | class EventCaller implements EventHandlerInterface 26 | { 27 | /** 28 | * List of handlers 29 | * 30 | * @var EventHandlerInterface[] 31 | */ 32 | private $handlers = []; 33 | 34 | /** 35 | * Request executor 36 | * 37 | * @var RequestExecutorInterface 38 | */ 39 | private $executor; 40 | 41 | /** 42 | * RequestDescriptor 43 | * 44 | * @var RequestDescriptor 45 | */ 46 | private $currentOperation; 47 | 48 | /** 49 | * EventCaller constructor. 50 | * 51 | * @param RequestExecutorInterface $executor Request executor 52 | */ 53 | public function __construct(RequestExecutorInterface $executor) 54 | { 55 | $this->executor = $executor; 56 | } 57 | 58 | /** 59 | * Add given handler to list 60 | * 61 | * @param EventHandlerInterface $handler 62 | * 63 | * @return void 64 | */ 65 | public function addHandler(EventHandlerInterface $handler) 66 | { 67 | $this->handlers[] = $handler; 68 | } 69 | 70 | /** 71 | * Sets CurrentOperation 72 | * 73 | * @param RequestDescriptor $currentOperation New value for CurrentOperation 74 | * 75 | * @return void 76 | */ 77 | public function setCurrentOperation(RequestDescriptor $currentOperation) 78 | { 79 | $this->currentOperation = $currentOperation; 80 | } 81 | 82 | /** 83 | * Clear current operation object 84 | * 85 | * @return void 86 | */ 87 | public function clearCurrentOperation() 88 | { 89 | $this->currentOperation = null; 90 | } 91 | 92 | /** {@inheritdoc} */ 93 | public function invokeEvent( 94 | Event $event, 95 | RequestExecutorInterface $executor, 96 | SocketInterface $socket, 97 | ExecutionContext $context 98 | ) { 99 | $this->callSocketSubscribers($this->currentOperation, $event, $executor, $context); 100 | } 101 | 102 | /** 103 | * Notify handlers about given event 104 | * 105 | * @param RequestDescriptor $requestDescriptor Request descriptor 106 | * @param Event $event Event object 107 | * @param RequestExecutorInterface $executor Request executor 108 | * @param ExecutionContext $context Execution context 109 | * 110 | * @return void 111 | */ 112 | public function callSocketSubscribers( 113 | RequestDescriptor $requestDescriptor, 114 | Event $event, 115 | RequestExecutorInterface $executor, 116 | ExecutionContext $context 117 | ) { 118 | $requestDescriptor->invokeEvent($event, $executor, $requestDescriptor->getSocket(), $context); 119 | 120 | foreach ($this->handlers as $handler) { 121 | $handler->invokeEvent($event, $executor, $requestDescriptor->getSocket(), $context); 122 | } 123 | 124 | if ($event->isOperationCancelled()) { 125 | throw new StopSocketOperationException(); 126 | } 127 | } 128 | 129 | /** 130 | * Notify handlers about exception 131 | * 132 | * @param RequestDescriptor $requestDescriptor Socket operation object 133 | * @param SocketException $exception Thrown exception 134 | * @param RequestExecutorInterface $executor Request executor 135 | * @param ExecutionContext $context Execution context 136 | * 137 | * @return void 138 | */ 139 | public function callExceptionSubscribers( 140 | RequestDescriptor $requestDescriptor, 141 | SocketException $exception, 142 | RequestExecutorInterface $executor, 143 | ExecutionContext $context 144 | ) { 145 | if ($exception instanceof StopSocketOperationException) { 146 | return; 147 | } 148 | 149 | $meta = $requestDescriptor->getMetadata(); 150 | $exceptionEvent = new SocketExceptionEvent( 151 | $exception, 152 | $this->executor, 153 | $requestDescriptor->getSocket(), 154 | $meta[RequestExecutorInterface::META_USER_CONTEXT] 155 | ); 156 | $this->callSocketSubscribers($requestDescriptor, $exceptionEvent, $executor, $context); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/ExcludedOperationsStage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\ExecutionContext; 13 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 14 | 15 | /** 16 | * Class ExcludedOperationsStage 17 | */ 18 | class ExcludedOperationsStage extends AbstractStage 19 | { 20 | /** 21 | * Stages for processing successful descriptors 22 | * 23 | * @var PipelineStageInterface[] 24 | */ 25 | private $stages; 26 | 27 | /** 28 | * AbstractStage constructor. 29 | * 30 | * @param RequestExecutorInterface $executor Request executor 31 | * @param EventCaller $eventCaller Event caller 32 | * @param ExecutionContext $executionContext Execution context 33 | * @param PipelineStageInterface[] $stages Stages for success way 34 | */ 35 | public function __construct( 36 | RequestExecutorInterface $executor, 37 | EventCaller $eventCaller, 38 | ExecutionContext $executionContext, 39 | array $stages 40 | ) { 41 | parent::__construct($executor, $eventCaller, $executionContext); 42 | $this->stages = $stages; 43 | } 44 | 45 | /** {@inheritdoc} */ 46 | public function processStage(array $requestDescriptors) 47 | { 48 | $currentOperations = $requestDescriptors; 49 | foreach ($this->stages as $stage) { 50 | $currentOperations = $stage->processStage($currentOperations); 51 | } 52 | 53 | foreach ($requestDescriptors as $key => $descriptor) { 54 | if (in_array($descriptor, $currentOperations, true)) { 55 | unset($requestDescriptors[ $key]); 56 | } 57 | } 58 | 59 | return $requestDescriptors; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/NullIoHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\Operation\NullOperation; 13 | use AsyncSockets\Operation\OperationInterface; 14 | use AsyncSockets\RequestExecutor\EventHandlerInterface; 15 | use AsyncSockets\RequestExecutor\ExecutionContext; 16 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 17 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 18 | 19 | /** 20 | * Class NullIoHandler 21 | */ 22 | class NullIoHandler extends AbstractOobHandler 23 | { 24 | /** 25 | * @inheritDoc 26 | */ 27 | public function supports(OperationInterface $operation) 28 | { 29 | return $operation instanceof NullOperation; 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | protected function handleOperation( 36 | OperationInterface $operation, 37 | RequestDescriptor $descriptor, 38 | RequestExecutorInterface $executor, 39 | EventHandlerInterface $eventHandler, 40 | ExecutionContext $executionContext 41 | ) { 42 | // empty body 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | protected function getHandlerType() 49 | { 50 | return RequestDescriptor::RDS_READ; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/Pipeline.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 13 | use AsyncSockets\RequestExecutor\Metadata\SocketBag; 14 | 15 | /** 16 | * Class Pipeline 17 | */ 18 | class Pipeline 19 | { 20 | /** 21 | * DisconnectStage 22 | * 23 | * @var PipelineStageInterface 24 | */ 25 | private $disconnectStage; 26 | 27 | /** 28 | * Connect stage 29 | * 30 | * @var PipelineStageInterface 31 | */ 32 | private $connectStage; 33 | 34 | /** 35 | * PipelineStageInterface 36 | * 37 | * @var PipelineStageInterface[] 38 | */ 39 | private $stages; 40 | 41 | /** 42 | * Pipeline constructor 43 | * 44 | * @param PipelineStageInterface $connectStage Connect stage 45 | * @param PipelineStageInterface[] $stages Pipeline stages 46 | * @param PipelineStageInterface $disconnectStage Disconnect stages 47 | */ 48 | public function __construct( 49 | PipelineStageInterface $connectStage, 50 | array $stages, 51 | PipelineStageInterface $disconnectStage 52 | ) { 53 | $this->connectStage = $connectStage; 54 | $this->stages = $stages; 55 | $this->disconnectStage = $disconnectStage; 56 | } 57 | 58 | /** 59 | * Process I/O operations on sockets 60 | * 61 | * @param SocketBag $socketBag Socket bag 62 | * 63 | * @return void 64 | * @throws \Exception 65 | */ 66 | public function process(SocketBag $socketBag) 67 | { 68 | do { 69 | $activeOperations = $this->connectStage->processStage($socketBag->getItems()); 70 | if (!$activeOperations) { 71 | break; 72 | } 73 | 74 | foreach ($this->stages as $stage) { 75 | $activeOperations = $stage->processStage($activeOperations); 76 | } 77 | } while (true); 78 | } 79 | 80 | /** 81 | * Disconnect given list of sockets 82 | * 83 | * @param RequestDescriptor[] $items Sockets' operations 84 | * 85 | * @return void 86 | */ 87 | public function disconnectSockets(array $items) 88 | { 89 | $this->disconnectStage->processStage($items); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/PipelineFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\ExecutionContext; 13 | use AsyncSockets\RequestExecutor\LimitationSolverInterface; 14 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 15 | use AsyncSockets\Socket\AsyncSelector; 16 | 17 | /** 18 | * Class PipelineFactory 19 | */ 20 | class PipelineFactory 21 | { 22 | /** 23 | * Stage factory 24 | * 25 | * @var StageFactoryInterface 26 | */ 27 | private $stageFactory; 28 | 29 | /** 30 | * PipelineFactory constructor. 31 | * 32 | * @param StageFactoryInterface $stageFactory Stage factory 33 | */ 34 | public function __construct(StageFactoryInterface $stageFactory) 35 | { 36 | $this->stageFactory = $stageFactory; 37 | } 38 | 39 | /** 40 | * Create Pipeline 41 | * 42 | * @param RequestExecutorInterface $executor Request executor 43 | * @param ExecutionContext $executionContext Execution context 44 | * @param EventCaller $eventCaller Event caller 45 | * @param LimitationSolverInterface $limitationDecider Limitation solver 46 | * 47 | * @return Pipeline 48 | */ 49 | public function createPipeline( 50 | RequestExecutorInterface $executor, 51 | ExecutionContext $executionContext, 52 | EventCaller $eventCaller, 53 | LimitationSolverInterface $limitationDecider 54 | ) { 55 | $selector = $this->createSelector(); 56 | $disconnectStage = $this->stageFactory->createDisconnectStage($executor, $executionContext, $eventCaller, $selector); 57 | 58 | return new Pipeline( 59 | $this->stageFactory->createConnectStage($executor, $executionContext, $eventCaller, $limitationDecider), 60 | [ 61 | new ExcludedOperationsStage( 62 | $executor, 63 | $eventCaller, 64 | $executionContext, 65 | [ 66 | $this->stageFactory->createDelayStage($executor, $executionContext, $eventCaller), 67 | new SelectStage($executor, $eventCaller, $executionContext, $selector), 68 | $this->stageFactory->createIoStage($executor, $executionContext, $eventCaller), 69 | $disconnectStage 70 | ] 71 | ), 72 | new TimeoutStage($executor, $eventCaller, $executionContext), 73 | $disconnectStage 74 | ], 75 | $disconnectStage 76 | ); 77 | } 78 | 79 | /** 80 | * Create AsyncSelector 81 | * 82 | * @return AsyncSelector 83 | */ 84 | protected function createSelector() 85 | { 86 | return new AsyncSelector(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/PipelineStageInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 13 | 14 | /** 15 | * Interface PipelineStageInterface 16 | */ 17 | interface PipelineStageInterface 18 | { 19 | /** 20 | * Process pipeline stage 21 | * 22 | * @param RequestDescriptor[] $requestDescriptors List of requestDescriptors to process 23 | * 24 | * @return RequestDescriptor[] List of processed requestDescriptors 25 | */ 26 | public function processStage(array $requestDescriptors); 27 | } 28 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/ReadIoHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\Event\AcceptEvent; 13 | use AsyncSockets\Event\ReadEvent; 14 | use AsyncSockets\Exception\AcceptException; 15 | use AsyncSockets\Frame\AcceptedFrame; 16 | use AsyncSockets\Frame\FramePickerInterface; 17 | use AsyncSockets\Frame\PartialFrame; 18 | use AsyncSockets\Operation\OperationInterface; 19 | use AsyncSockets\Operation\ReadOperation; 20 | use AsyncSockets\RequestExecutor\EventHandlerInterface; 21 | use AsyncSockets\RequestExecutor\ExecutionContext; 22 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 23 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 24 | 25 | /** 26 | * Class ReadIoHandler 27 | */ 28 | class ReadIoHandler extends AbstractOobHandler implements FramePickerInterface 29 | { 30 | /** 31 | * Amount of bytes read by last operation 32 | * 33 | * @var int 34 | */ 35 | private $bytesRead; 36 | 37 | /** 38 | * Actual frame picker 39 | * 40 | * @var FramePickerInterface 41 | */ 42 | private $realFramePicker; 43 | 44 | /** {@inheritdoc} */ 45 | public function supports(OperationInterface $operation) 46 | { 47 | return $operation instanceof ReadOperation; 48 | } 49 | 50 | /** {@inheritdoc} */ 51 | protected function handleOperation( 52 | OperationInterface $operation, 53 | RequestDescriptor $descriptor, 54 | RequestExecutorInterface $executor, 55 | EventHandlerInterface $eventHandler, 56 | ExecutionContext $executionContext 57 | ) { 58 | /** @var ReadOperation $operation */ 59 | $socket = $descriptor->getSocket(); 60 | 61 | $meta = $executor->socketBag()->getSocketMetaData($socket); 62 | $context = $meta[RequestExecutorInterface::META_USER_CONTEXT]; 63 | $result = null; 64 | 65 | $this->bytesRead = 0; 66 | $this->realFramePicker = $operation->getFramePicker(); 67 | 68 | try { 69 | $response = $socket->read($this); 70 | switch (true) { 71 | case $response instanceof PartialFrame: 72 | $result = $operation; 73 | break; 74 | case $response instanceof AcceptedFrame: 75 | $event = new AcceptEvent( 76 | $executor, 77 | $socket, 78 | $context, 79 | $response->getClientSocket(), 80 | $response->getRemoteAddress() 81 | ); 82 | 83 | $eventHandler->invokeEvent($event, $executor, $socket, $executionContext); 84 | $result = new ReadOperation(); 85 | break; 86 | default: 87 | $event = new ReadEvent( 88 | $executor, 89 | $socket, 90 | $context, 91 | $response, 92 | false 93 | ); 94 | 95 | $eventHandler->invokeEvent($event, $executor, $socket, $executionContext); 96 | $result = $event->getNextOperation(); 97 | break; 98 | } 99 | } catch (AcceptException $e) { 100 | $result = new ReadOperation(); 101 | } catch (\Exception $e) { 102 | $this->appendReadBytes($descriptor, $this->bytesRead); 103 | unset($this->realFramePicker, $this->bytesRead); 104 | throw $e; 105 | } 106 | 107 | $this->appendReadBytes($descriptor, $this->bytesRead); 108 | unset($this->realFramePicker, $this->bytesRead); 109 | 110 | return $result; 111 | } 112 | 113 | /** 114 | * Append given mount of read bytes to descriptor 115 | * 116 | * @param RequestDescriptor $descriptor The descriptor 117 | * @param int $bytesRead Amount of read bytes 118 | * 119 | * @return void 120 | */ 121 | private function appendReadBytes(RequestDescriptor $descriptor, $bytesRead) 122 | { 123 | $this->handleTransferCounter(RequestDescriptor::COUNTER_RECV_MIN_RATE, $descriptor, $bytesRead); 124 | } 125 | 126 | /** 127 | * {@inheritDoc} 128 | */ 129 | public function isEof() 130 | { 131 | return $this->realFramePicker->isEof(); 132 | } 133 | 134 | /** 135 | * {@inheritDoc} 136 | */ 137 | public function pickUpData($chunk, $remoteAddress) 138 | { 139 | $this->bytesRead += strlen($chunk); 140 | return $this->realFramePicker->pickUpData($chunk, $remoteAddress); 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | public function createFrame() 147 | { 148 | return $this->realFramePicker->createFrame(); 149 | } 150 | 151 | /** 152 | * @inheritDoc 153 | */ 154 | protected function getHandlerType() 155 | { 156 | return RequestDescriptor::RDS_READ; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/ReadWriteIoHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\RequestExecutor\Pipeline; 12 | 13 | use AsyncSockets\Operation\OperationInterface; 14 | use AsyncSockets\Operation\ReadWriteOperation; 15 | use AsyncSockets\RequestExecutor\EventHandlerInterface; 16 | use AsyncSockets\RequestExecutor\ExecutionContext; 17 | use AsyncSockets\RequestExecutor\IoHandlerInterface; 18 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 19 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 20 | 21 | /** 22 | * Class ReadWriteIoHandler 23 | */ 24 | class ReadWriteIoHandler implements IoHandlerInterface 25 | { 26 | /** 27 | * Io handler 28 | * 29 | * @var IoHandlerInterface 30 | */ 31 | private $handler; 32 | 33 | /** 34 | * Initialize I/O handler 35 | * 36 | * @param IoHandlerInterface $handler New value for Handler 37 | * 38 | * @return void 39 | */ 40 | public function setHandler(IoHandlerInterface $handler) 41 | { 42 | $this->handler = $handler; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | public function supports(OperationInterface $operation) 49 | { 50 | return $operation instanceof ReadWriteOperation; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function handle( 57 | OperationInterface $operation, 58 | RequestDescriptor $descriptor, 59 | RequestExecutorInterface $executor, 60 | EventHandlerInterface $eventHandler, 61 | ExecutionContext $executionContext 62 | ) { 63 | /** @var ReadWriteOperation $operation */ 64 | $sequence = $operation->isReadFirst() ? 65 | [$operation->getReadOperation(), $operation->getWriteOperation()] : 66 | [$operation->getWriteOperation(), $operation->getReadOperation()]; 67 | $sequence = array_filter($sequence); 68 | 69 | foreach ($sequence as $op) { 70 | $this->handleOperation( 71 | $op, 72 | $operation, 73 | $descriptor, 74 | $executor, 75 | $eventHandler, 76 | $executionContext 77 | ); 78 | } 79 | 80 | return $operation->getReadOperation() !== null || $operation->getWriteOperation() !== null ? 81 | $operation : 82 | null; 83 | } 84 | 85 | /** 86 | * Handle read operation 87 | * 88 | * @param OperationInterface $nestedOperation Nested I/O operation 89 | * @param ReadWriteOperation $operation Operation to process 90 | * @param RequestDescriptor $descriptor Request descriptor 91 | * @param RequestExecutorInterface $executor Executor, processing operation 92 | * @param EventHandlerInterface $eventHandler Event handler for this operation 93 | * @param ExecutionContext $executionContext Execution context 94 | * 95 | * @return void 96 | */ 97 | private function handleOperation( 98 | OperationInterface $nestedOperation = null, 99 | ReadWriteOperation $operation, 100 | RequestDescriptor $descriptor, 101 | RequestExecutorInterface $executor, 102 | EventHandlerInterface $eventHandler, 103 | ExecutionContext $executionContext 104 | ) { 105 | if (!$nestedOperation) { 106 | return; 107 | } 108 | 109 | $next = $this->handler->handle($nestedOperation, $descriptor, $executor, $eventHandler, $executionContext); 110 | if ($next !== $nestedOperation) { 111 | $operation->markCompleted($nestedOperation); 112 | } 113 | 114 | if (!$next) { 115 | return; 116 | } 117 | 118 | $operation->scheduleOperation($next); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/SslHandshakeIoHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\Exception\SslHandshakeException; 13 | use AsyncSockets\Operation\OperationInterface; 14 | use AsyncSockets\Operation\SslHandshakeOperation; 15 | use AsyncSockets\RequestExecutor\EventHandlerInterface; 16 | use AsyncSockets\RequestExecutor\ExecutionContext; 17 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 18 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 19 | 20 | /** 21 | * Class SslHandshakeIoHandler 22 | */ 23 | class SslHandshakeIoHandler extends AbstractOobHandler 24 | { 25 | /** {@inheritdoc} */ 26 | public function supports(OperationInterface $operation) 27 | { 28 | return $operation instanceof SslHandshakeOperation; 29 | } 30 | 31 | /** {@inheritdoc} */ 32 | protected function handleOperation( 33 | OperationInterface $operation, 34 | RequestDescriptor $descriptor, 35 | RequestExecutorInterface $executor, 36 | EventHandlerInterface $eventHandler, 37 | ExecutionContext $executionContext 38 | ) { 39 | $socket = $descriptor->getSocket(); 40 | 41 | /** @var SslHandshakeOperation $operation */ 42 | $resource = $descriptor->getSocket()->getStreamResource(); 43 | $result = stream_socket_enable_crypto($resource, true, $operation->getCipher()); 44 | if ($result === true) { 45 | return $operation->getNextOperation(); 46 | } elseif ($result === false) { 47 | throw new SslHandshakeException($socket, 'SSL handshake failed.'); 48 | } 49 | 50 | return $operation; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | protected function getHandlerType() 57 | { 58 | return RequestDescriptor::RDS_WRITE; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/StageFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\RequestExecutor\ExecutionContext; 13 | use AsyncSockets\RequestExecutor\LimitationSolverInterface; 14 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 15 | use AsyncSockets\Socket\AsyncSelector; 16 | 17 | /** 18 | * Interface StageFactoryInterface 19 | */ 20 | interface StageFactoryInterface 21 | { 22 | /** 23 | * Create delay stage handler 24 | * 25 | * @param RequestExecutorInterface $executor Request executor 26 | * @param ExecutionContext $executionContext Execution context 27 | * @param EventCaller $caller Event caller 28 | * 29 | * @return PipelineStageInterface 30 | */ 31 | public function createDelayStage( 32 | RequestExecutorInterface $executor, 33 | ExecutionContext $executionContext, 34 | EventCaller $caller 35 | ); 36 | 37 | /** 38 | * Create connect stage handler 39 | * 40 | * @param RequestExecutorInterface $executor Request executor 41 | * @param ExecutionContext $executionContext Execution context 42 | * @param EventCaller $caller Event caller 43 | * @param LimitationSolverInterface $limitationSolver Limitation solver 44 | * 45 | * @return PipelineStageInterface 46 | */ 47 | public function createConnectStage( 48 | RequestExecutorInterface $executor, 49 | ExecutionContext $executionContext, 50 | EventCaller $caller, 51 | LimitationSolverInterface $limitationSolver 52 | ); 53 | 54 | /** 55 | * Create I/O stage 56 | * 57 | * @param RequestExecutorInterface $executor Request executor 58 | * @param ExecutionContext $executionContext Execution context 59 | * @param EventCaller $caller Event caller 60 | * 61 | * @return PipelineStageInterface 62 | */ 63 | public function createIoStage( 64 | RequestExecutorInterface $executor, 65 | ExecutionContext $executionContext, 66 | EventCaller $caller 67 | ); 68 | 69 | /** 70 | * Creates disconnect stage 71 | * 72 | * @param RequestExecutorInterface $executor Request executor 73 | * @param ExecutionContext $executionContext Execution context 74 | * @param EventCaller $caller Event caller 75 | * @param AsyncSelector $selector Selector object 76 | * 77 | * @return PipelineStageInterface 78 | */ 79 | public function createDisconnectStage( 80 | RequestExecutorInterface $executor, 81 | ExecutionContext $executionContext, 82 | EventCaller $caller, 83 | AsyncSelector $selector = null 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/RequestExecutor/Pipeline/TimeoutStage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Pipeline; 11 | 12 | use AsyncSockets\Event\TimeoutEvent; 13 | use AsyncSockets\Exception\SocketException; 14 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 15 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 16 | 17 | /** 18 | * Class TimeoutStage 19 | */ 20 | class TimeoutStage extends AbstractTimeAwareStage 21 | { 22 | /** {@inheritdoc} */ 23 | public function processStage(array $requestDescriptors) 24 | { 25 | /** @var RequestDescriptor[] $requestDescriptors */ 26 | $result = [ ]; 27 | $microTime = microtime(true); 28 | foreach ($requestDescriptors as $key => $descriptor) { 29 | $isTimeout = $this->isSingleSocketTimeout($descriptor, $microTime) && 30 | !$this->handleTimeoutOnDescriptor($descriptor); 31 | if ($isTimeout) { 32 | $result[$key] = $descriptor; 33 | } 34 | } 35 | 36 | return $result; 37 | } 38 | 39 | /** 40 | * Fire timeout event and processes user response 41 | * 42 | * @param RequestDescriptor $descriptor 43 | * 44 | * @return bool True if we may do one more attempt, false otherwise 45 | */ 46 | public function handleTimeoutOnDescriptor(RequestDescriptor $descriptor) 47 | { 48 | $meta = $descriptor->getMetadata(); 49 | $event = new TimeoutEvent( 50 | $this->executor, 51 | $descriptor->getSocket(), 52 | $meta[RequestExecutorInterface::META_USER_CONTEXT], 53 | $meta[RequestExecutorInterface::META_CONNECTION_FINISH_TIME] !== null && 54 | !$descriptor->getSocket()->isServer() ? 55 | TimeoutEvent::DURING_IO : 56 | TimeoutEvent::DURING_CONNECTION 57 | ); 58 | try { 59 | $this->callSocketSubscribers($descriptor, $event); 60 | $result = $event->isNextAttemptEnabled(); 61 | } catch (SocketException $e) { 62 | $this->callExceptionSubscribers($descriptor, $e); 63 | $result = false; 64 | } 65 | 66 | if ($result) { 67 | $this->updateMetadataForAttempt($descriptor, $event->when()); 68 | } 69 | 70 | return $result; 71 | } 72 | 73 | /** 74 | * Update data inside descriptor to make one more attempt 75 | * 76 | * @param RequestDescriptor $descriptor Operation descriptor 77 | * @param string $when When Timeout occurerd, one of TimeoutEvent::DURING_* consts 78 | * 79 | * @return void 80 | */ 81 | private function updateMetadataForAttempt(RequestDescriptor $descriptor, $when) 82 | { 83 | switch ($when) { 84 | case TimeoutEvent::DURING_IO: 85 | $descriptor->setMetadata(RequestExecutorInterface::META_LAST_IO_START_TIME, null); 86 | break; 87 | case TimeoutEvent::DURING_CONNECTION: 88 | $descriptor->setRunning(false); 89 | $descriptor->setMetadata( 90 | [ 91 | RequestExecutorInterface::META_LAST_IO_START_TIME => null, 92 | RequestExecutorInterface::META_CONNECTION_START_TIME => null, 93 | RequestExecutorInterface::META_CONNECTION_FINISH_TIME => null, 94 | ] 95 | ); 96 | break; 97 | } 98 | } 99 | 100 | /** 101 | * Checks whether given params lead to timeout 102 | * 103 | * @param RequestDescriptor $descriptor Descriptor object 104 | * @param double $microTime Current time with microseconds 105 | * 106 | * @return bool True, if socket with this params in timeout, false otherwise 107 | */ 108 | private function isSingleSocketTimeout(RequestDescriptor $descriptor, $microTime) 109 | { 110 | $desiredTimeout = $this->timeoutSetting($descriptor); 111 | $lastOperationTime = $this->timeSinceLastIo($descriptor); 112 | $hasConnected = $this->hasConnected($descriptor); 113 | 114 | return ($desiredTimeout !== RequestExecutorInterface::WAIT_FOREVER) && 115 | ( 116 | ($hasConnected && $lastOperationTime !== null) || 117 | !$hasConnected 118 | ) && 119 | ($microTime - $lastOperationTime >= $desiredTimeout) 120 | ; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/RequestExecutor/RemoveFinishedSocketsEventHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Event\Event; 13 | use AsyncSockets\Event\EventType; 14 | use AsyncSockets\Socket\SocketInterface; 15 | 16 | /** 17 | * Class RemoveFinishedSocketsEventHandler 18 | */ 19 | class RemoveFinishedSocketsEventHandler implements EventHandlerInterface 20 | { 21 | /** 22 | * Target handler 23 | * 24 | * @var EventHandlerInterface 25 | */ 26 | private $handler; 27 | 28 | /** 29 | * RemoveFinishedSocketsEventHandler constructor. 30 | * 31 | * @param EventHandlerInterface $handler Original event handler 32 | */ 33 | public function __construct(EventHandlerInterface $handler = null) 34 | { 35 | $this->handler = $handler; 36 | } 37 | 38 | /** {@inheritdoc} */ 39 | public function invokeEvent( 40 | Event $event, 41 | RequestExecutorInterface $executor, 42 | SocketInterface $socket, 43 | ExecutionContext $context 44 | ) { 45 | if ($this->handler) { 46 | $this->handler->invokeEvent($event, $executor, $socket, $context); 47 | } 48 | 49 | if ($event->getType() === EventType::FINALIZE) { 50 | $bag = $event->getExecutor()->socketBag(); 51 | if ($bag->hasSocket($socket)) { 52 | $bag->removeSocket($socket); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/RequestExecutor/SocketBagInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Operation\OperationInterface; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Interface SocketBagInterface 17 | */ 18 | interface SocketBagInterface extends \Countable 19 | { 20 | /** 21 | * Add socket into this bag 22 | * 23 | * @param SocketInterface $socket Socket to add 24 | * @param OperationInterface $operation Operation to perform on socket 25 | * @param array $metadata Socket metadata information, which will be passed 26 | * to setSocketMetaData during this call 27 | * @param EventHandlerInterface $eventHandlers Optional handlers for this socket 28 | * 29 | * @return void 30 | * @throws \LogicException If socket has been already added 31 | * @see SocketBag::setSocketMetaData 32 | * 33 | * @api 34 | */ 35 | public function addSocket( 36 | SocketInterface $socket, 37 | OperationInterface $operation, 38 | array $metadata = null, 39 | EventHandlerInterface $eventHandlers = null 40 | ); 41 | 42 | /** 43 | * Return operation, associated with this socket 44 | * 45 | * @param SocketInterface $socket Socket to get operation for 46 | * 47 | * @return OperationInterface 48 | * @throws \OutOfBoundsException If given socket is not added to this bag 49 | */ 50 | public function getSocketOperation(SocketInterface $socket); 51 | 52 | /** 53 | * Set new operation for this socket 54 | * 55 | * @param SocketInterface $socket Socket object 56 | * @param OperationInterface $operation New operation 57 | * 58 | * @return void 59 | * @throws \OutOfBoundsException If given socket is not added to this bag 60 | */ 61 | public function setSocketOperation(SocketInterface $socket, OperationInterface $operation); 62 | 63 | /** 64 | * Checks whether given socket was added to this executor 65 | * 66 | * @param SocketInterface $socket Socket object 67 | * 68 | * @return bool 69 | */ 70 | public function hasSocket(SocketInterface $socket); 71 | 72 | /** 73 | * Remove socket from list 74 | * 75 | * @param SocketInterface $socket Socket to remove 76 | * 77 | * @return void 78 | * @throws \LogicException If you try to call this method when request is active and given socket hasn't been yet 79 | * processed 80 | * 81 | * @api 82 | */ 83 | public function removeSocket(SocketInterface $socket); 84 | 85 | /** 86 | * Completes processing this socket in event loop, but keep this socket connection opened. Applicable only for 87 | * sockets having keep-alive flag set to true 88 | * 89 | * @param SocketInterface $socket Socket object 90 | * 91 | * @return void 92 | * 93 | * @api 94 | */ 95 | public function forgetSocket(SocketInterface $socket); 96 | 97 | /** 98 | * Resets transfer rate counter when transfer is actually completed. This is need to be done when using 99 | * META_MIN_RECEIVE_SPEED and META_MIN_RECEIVE_SPEED_DURATION settings; 100 | * 101 | * @param SocketInterface $socket Socket object 102 | * 103 | * @return void 104 | * @api 105 | */ 106 | public function resetTransferRateCounters(SocketInterface $socket); 107 | 108 | /** 109 | * Return array with meta information about socket 110 | * 111 | * @param SocketInterface $socket Socket object 112 | * 113 | * @return array Key-value array where key is one of META_* consts 114 | * @throws \OutOfBoundsException If given socket is not added to this bag 115 | * 116 | * @api 117 | */ 118 | public function getSocketMetaData(SocketInterface $socket); 119 | 120 | /** 121 | * Set metadata for given socket 122 | * 123 | * @param SocketInterface $socket Socket object 124 | * @param string|array $key Either string or key-value array of metadata. If string, then value must be 125 | * passed in third argument, if array, then third argument will be ignored 126 | * @param mixed $value Value for key 127 | * 128 | * @return void 129 | * @throws \OutOfBoundsException If given socket is not added to this executor 130 | * 131 | * @api 132 | */ 133 | public function setSocketMetaData(SocketInterface $socket, $key, $value = null); 134 | } 135 | -------------------------------------------------------------------------------- /src/RequestExecutor/Specification/ConnectionLessSocketSpecification.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Specification; 11 | 12 | use AsyncSockets\Operation\ReadOperation; 13 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 14 | use AsyncSockets\Socket\UdpClientSocket; 15 | 16 | /** 17 | * Class ConnectionLessSocketSpecification 18 | */ 19 | class ConnectionLessSocketSpecification implements SpecificationInterface 20 | { 21 | /** {@inheritdoc} */ 22 | public function isSatisfiedBy(RequestDescriptor $requestDescriptor) 23 | { 24 | return $requestDescriptor->getSocket() instanceof UdpClientSocket && 25 | $requestDescriptor->getOperation() instanceof ReadOperation; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/RequestExecutor/Specification/SpecificationInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor\Specification; 11 | 12 | use AsyncSockets\RequestExecutor\Metadata\RequestDescriptor; 13 | 14 | /** 15 | * Interface SpecificationInterface 16 | */ 17 | interface SpecificationInterface 18 | { 19 | /** 20 | * Check whether given socket is satisfied by this specification 21 | * 22 | * @param RequestDescriptor $requestDescriptor Operation object 23 | * 24 | * @return bool 25 | */ 26 | public function isSatisfiedBy(RequestDescriptor $requestDescriptor); 27 | } 28 | -------------------------------------------------------------------------------- /src/RequestExecutor/SslDataFlushEventHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\RequestExecutor; 11 | 12 | use AsyncSockets\Event\DataAlertEvent; 13 | use AsyncSockets\Event\Event; 14 | use AsyncSockets\Event\EventType; 15 | use AsyncSockets\Event\ReadEvent; 16 | use AsyncSockets\Frame\EmptyFrame; 17 | use AsyncSockets\Frame\EmptyFramePicker; 18 | use AsyncSockets\Operation\ReadOperation; 19 | use AsyncSockets\Socket\SocketInterface; 20 | 21 | /** 22 | * Class SslDataFlushEventHandler. This decorator flushes unread data containing in socket buffer 23 | * which will be evaluated to empty string 24 | */ 25 | class SslDataFlushEventHandler implements EventHandlerInterface 26 | { 27 | /** 28 | * Next handler 29 | * 30 | * @var EventHandlerInterface|null 31 | */ 32 | private $next; 33 | 34 | /** 35 | * Array of socket descriptors currently in flush 36 | * 37 | * @var bool[] 38 | */ 39 | private $inFlushingOperations = []; 40 | 41 | /** 42 | * SslDataFlushEventHandler constructor. 43 | * 44 | * @param EventHandlerInterface|null $next Next event handler 45 | */ 46 | public function __construct(EventHandlerInterface $next = null) 47 | { 48 | $this->next = $next; 49 | } 50 | 51 | /** {@inheritdoc} */ 52 | public function invokeEvent( 53 | Event $event, 54 | RequestExecutorInterface $executor, 55 | SocketInterface $socket, 56 | ExecutionContext $context 57 | ) { 58 | switch ($event->getType()) { 59 | case EventType::DATA_ALERT: 60 | /** @var DataAlertEvent $event */ 61 | $this->onDataAlert($event, $executor, $socket, $context); 62 | break; 63 | case EventType::READ: 64 | /** @var ReadEvent $event */ 65 | $this->onRead($event, $executor, $socket, $context); 66 | break; 67 | default: 68 | $this->callNextHandler($event, $executor, $socket, $context); 69 | } 70 | } 71 | 72 | /** 73 | * Handle first data alert and try to flush ssl data by empty frame 74 | * 75 | * @param DataAlertEvent $event Event object 76 | * @param RequestExecutorInterface $executor Request executor fired an event 77 | * @param SocketInterface $socket Socket connected with event 78 | * @param ExecutionContext $context Global data context 79 | * 80 | * @return void 81 | */ 82 | private function onDataAlert( 83 | DataAlertEvent $event, 84 | RequestExecutorInterface $executor, 85 | SocketInterface $socket, 86 | ExecutionContext $context 87 | ) { 88 | if ($event->getAttempt() !== 1) { 89 | $this->callNextHandler($event, $executor, $socket, $context); 90 | return; 91 | } 92 | 93 | $event->nextIs( 94 | new ReadOperation( 95 | new EmptyFramePicker() 96 | ) 97 | ); 98 | 99 | $key = spl_object_hash($socket); 100 | $this->inFlushingOperations[$key] = true; 101 | } 102 | 103 | /** 104 | * Handle read data 105 | * 106 | * @param ReadEvent $event Read event 107 | * @param RequestExecutorInterface $executor Request executor fired an event 108 | * @param SocketInterface $socket Socket connected with event 109 | * @param ExecutionContext $context Global data context 110 | * 111 | * @return void 112 | */ 113 | private function onRead( 114 | ReadEvent $event, 115 | RequestExecutorInterface $executor, 116 | SocketInterface $socket, 117 | ExecutionContext $context 118 | ) { 119 | $key = spl_object_hash($socket); 120 | $frame = $event->getFrame(); 121 | if (!($frame instanceof EmptyFrame) || !isset($this->inFlushingOperations[$key])) { 122 | $this->callNextHandler($event, $executor, $socket, $context); 123 | return; 124 | } 125 | 126 | unset($this->inFlushingOperations[$key]); 127 | } 128 | 129 | /** 130 | * Call next event handler 131 | * 132 | * @param Event $event Event object 133 | * @param RequestExecutorInterface $executor Request executor fired an event 134 | * @param SocketInterface $socket Socket connected with event 135 | * @param ExecutionContext $context Global data context 136 | * 137 | * @return void 138 | */ 139 | private function callNextHandler( 140 | Event $event, 141 | RequestExecutorInterface $executor, 142 | SocketInterface $socket, 143 | ExecutionContext $context 144 | ) { 145 | if ($this->next) { 146 | $this->next->invokeEvent($event, $executor, $socket, $context); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Socket/AbstractClientSocket.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Socket; 12 | 13 | use AsyncSockets\Socket\Io\DatagramClientIo; 14 | use AsyncSockets\Socket\Io\StreamedClientIo; 15 | 16 | /** 17 | * Class AbstractClientSocket 18 | */ 19 | abstract class AbstractClientSocket extends AbstractSocket 20 | { 21 | /** {@inheritdoc} */ 22 | protected function createIoInterface($type, $address) 23 | { 24 | switch ($type) { 25 | case self::SOCKET_TYPE_UNIX: 26 | return new StreamedClientIo($this, 0); 27 | case self::SOCKET_TYPE_TCP: 28 | return new StreamedClientIo($this, 1); 29 | case self::SOCKET_TYPE_UDG: 30 | return new DatagramClientIo($this, null); 31 | case self::SOCKET_TYPE_UDP: 32 | return new DatagramClientIo($this, $address); 33 | default: 34 | throw new \LogicException("Unsupported socket resource type {$type}"); 35 | } 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | public function isServer() 42 | { 43 | return false; 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | public function isClient() 50 | { 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Socket/AcceptedSocket.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket; 11 | 12 | use AsyncSockets\Exception\NetworkSocketException; 13 | 14 | /** 15 | * Class AcceptedSocket 16 | */ 17 | class AcceptedSocket extends AbstractClientSocket 18 | { 19 | /** 20 | * Accepted client 21 | * 22 | * @var resource 23 | */ 24 | private $acceptedResource; 25 | 26 | /** 27 | * AcceptedSocket constructor. 28 | * 29 | * @param resource $acceptedResource Accepted client socket 30 | */ 31 | public function __construct($acceptedResource) 32 | { 33 | parent::__construct(); 34 | $this->acceptedResource = $acceptedResource; 35 | } 36 | 37 | /** {@inheritdoc} */ 38 | protected function createSocketResource($address, $context) 39 | { 40 | if ($this->acceptedResource) { 41 | $result = $this->acceptedResource; 42 | $this->acceptedResource = null; 43 | 44 | return $result; 45 | } 46 | 47 | throw new NetworkSocketException($this, 'Remote client socket can not be reopened.'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Socket/AsyncSocketFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Socket; 12 | 13 | use AsyncSockets\Configuration\Configuration; 14 | use AsyncSockets\RequestExecutor\LibEventRequestExecutor; 15 | use AsyncSockets\RequestExecutor\NativeRequestExecutor; 16 | use AsyncSockets\RequestExecutor\Pipeline\BaseStageFactory; 17 | use AsyncSockets\RequestExecutor\Pipeline\PipelineFactory; 18 | use AsyncSockets\RequestExecutor\RequestExecutorInterface; 19 | 20 | /** 21 | * Class AsyncSocketFactory 22 | * 23 | * @api 24 | */ 25 | class AsyncSocketFactory 26 | { 27 | /** 28 | * Create client socket 29 | */ 30 | const SOCKET_CLIENT = 'client'; 31 | 32 | /** 33 | * Create server socket 34 | */ 35 | const SOCKET_SERVER = 'server'; 36 | 37 | /** 38 | * Boolean flag whether it is persistent socket, applicable only for SOCKET_CLIENT type 39 | */ 40 | const SOCKET_OPTION_IS_PERSISTENT = 'soIsPersistent'; 41 | 42 | /** 43 | * Key in php storage to allow multiple persistent connections to the same host [a-zA-Z0-9_-] 44 | */ 45 | const SOCKET_OPTION_PERSISTENT_KEY = 'soPersistentKey'; 46 | 47 | /** 48 | * Default configuration for this factory 49 | * 50 | * @var Configuration 51 | */ 52 | private $configuration; 53 | 54 | /** 55 | * AsyncSocketFactory constructor. 56 | * 57 | * @param Configuration $configuration Default configuration for this factory 58 | */ 59 | public function __construct(Configuration $configuration = null) 60 | { 61 | $this->configuration = $configuration ?: new Configuration(); 62 | } 63 | 64 | /** 65 | * Create socket client 66 | * 67 | * @param string $type Socket type to create, one of SOCKET_* consts 68 | * @param array $options Flags with socket settings, see SOCKET_OPTION_* consts 69 | * 70 | * @return SocketInterface 71 | * @api 72 | */ 73 | public function createSocket($type = self::SOCKET_CLIENT, array $options = []) 74 | { 75 | switch ($type) { 76 | case self::SOCKET_CLIENT: 77 | $isPersistent = isset($options[ self::SOCKET_OPTION_IS_PERSISTENT ]) && 78 | $options[ self::SOCKET_OPTION_IS_PERSISTENT ]; 79 | $persistentKey = isset($options[ self::SOCKET_OPTION_PERSISTENT_KEY ]) ? 80 | $options[ self::SOCKET_OPTION_PERSISTENT_KEY ] : 81 | null; 82 | 83 | return $isPersistent ? 84 | new PersistentClientSocket($persistentKey) : 85 | new ClientSocket(); 86 | case self::SOCKET_SERVER: 87 | return new ServerSocket(); 88 | default: 89 | throw new \InvalidArgumentException("Unexpected type {$type} used in " . __FUNCTION__); 90 | } 91 | } 92 | 93 | /** 94 | * Create RequestExecutor object 95 | * 96 | * @return RequestExecutorInterface 97 | * 98 | * @api 99 | */ 100 | public function createRequestExecutor() 101 | { 102 | foreach ($this->configuration->getPreferredEngines() as $engine) { 103 | switch ($engine) { 104 | case 'libevent': 105 | if (extension_loaded('libevent')) { 106 | return new LibEventRequestExecutor(new BaseStageFactory(), $this->configuration); 107 | } 108 | break; 109 | case 'native': 110 | return new NativeRequestExecutor( 111 | new PipelineFactory( 112 | new BaseStageFactory() 113 | ), 114 | $this->configuration 115 | ); 116 | } 117 | } 118 | 119 | throw new \InvalidArgumentException( 120 | 'Provided configuration does not contain any supported RequestExecutor engine.' 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Socket/ClientSocket.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Socket; 12 | 13 | use AsyncSockets\Exception\ConnectionException; 14 | 15 | /** 16 | * Class ClientSocket 17 | */ 18 | class ClientSocket extends AbstractClientSocket 19 | { 20 | /** {@inheritdoc} */ 21 | protected function createSocketResource($address, $context) 22 | { 23 | $resource = stream_socket_client( 24 | $address, 25 | $errno, 26 | $errstr, 27 | null, 28 | STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT, 29 | $context 30 | ); 31 | 32 | if ($errno || $resource === false) { 33 | throw new ConnectionException($this, $errstr, $errno); 34 | } 35 | 36 | return $resource; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Socket/Io/AbstractIo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Socket\SocketInterface; 13 | 14 | /** 15 | * Class AbstractIo 16 | */ 17 | abstract class AbstractIo implements IoInterface 18 | { 19 | /** 20 | * Amount of attempts to set data 21 | */ 22 | const IO_ATTEMPTS = 10; 23 | 24 | /** 25 | * Socket 26 | * 27 | * @var SocketInterface 28 | */ 29 | protected $socket; 30 | 31 | /** 32 | * AbstractIo constructor. 33 | * 34 | * @param SocketInterface $socket Socket object 35 | */ 36 | public function __construct(SocketInterface $socket) 37 | { 38 | $this->socket = $socket; 39 | } 40 | 41 | /** 42 | * Return last php error message as string 43 | * 44 | * @return string 45 | */ 46 | protected function getLastPhpErrorMessage() 47 | { 48 | $lastError = error_get_last(); 49 | if (!empty($lastError)) { 50 | $phpMessage = explode(':', $lastError['message'], 2); 51 | $phpMessage = trim(trim(end($phpMessage)), '.') . '.'; 52 | return $phpMessage; 53 | } 54 | 55 | return ''; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Socket/Io/AbstractServerIo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Exception\NetworkSocketException; 13 | 14 | /** 15 | * Class AbstractServerIo 16 | */ 17 | abstract class AbstractServerIo extends AbstractIo 18 | { 19 | /** {@inheritdoc} */ 20 | public function write($data, Context $context, $isOutOfBand) 21 | { 22 | throw new NetworkSocketException($this->socket, 'Can not write data to tcp/udp server socket.'); 23 | } 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | public function isConnected() 29 | { 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Socket/Io/Context.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | /** 13 | * Class Context 14 | */ 15 | class Context 16 | { 17 | /** 18 | * Unread data in socket 19 | * 20 | * @var string[] 21 | */ 22 | private $unreadData = ['', '']; 23 | 24 | /** 25 | * Return UnreadData 26 | * 27 | * @param bool $isOutOfBand Flag if it is out of band data 28 | * 29 | * @return string 30 | */ 31 | public function getUnreadData($isOutOfBand = false) 32 | { 33 | return $this->unreadData[(int) (bool) $isOutOfBand]; 34 | } 35 | 36 | /** 37 | * Sets UnreadData 38 | * 39 | * @param string $unreadData New value for UnreadData 40 | * @param bool $isOutOfBand Flag if it is out of band data 41 | * 42 | * @return void 43 | */ 44 | public function setUnreadData($unreadData, $isOutOfBand = false) 45 | { 46 | $this->unreadData[(int) (bool) $isOutOfBand] = $unreadData; 47 | } 48 | 49 | /** 50 | * Resets socket context 51 | * 52 | * @return void 53 | */ 54 | public function reset() 55 | { 56 | $this->unreadData = ['', '']; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Socket/Io/DatagramClientIo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Exception\SendDataException; 13 | use AsyncSockets\Frame\FramePickerInterface; 14 | use AsyncSockets\Socket\SocketInterface; 15 | 16 | /** 17 | * Class DatagramClientIo 18 | */ 19 | class DatagramClientIo extends AbstractClientIo 20 | { 21 | /** 22 | * Destination address 23 | * 24 | * @var string 25 | */ 26 | protected $remoteAddress; 27 | 28 | /** 29 | * Constructor 30 | * 31 | * @param SocketInterface $socket Socket object 32 | * @param string|null $remoteAddress Destination address in form scheme://host:port or null for local files io 33 | */ 34 | public function __construct(SocketInterface $socket, $remoteAddress) 35 | { 36 | parent::__construct($socket, 0); 37 | if ($remoteAddress) { 38 | $components = parse_url($remoteAddress); 39 | $this->remoteAddress = $components['host'] . ':' . $components['port']; 40 | } 41 | } 42 | 43 | /** {@inheritdoc} */ 44 | protected function readRawDataIntoPicker(FramePickerInterface $picker, $isOutOfBand) 45 | { 46 | $size = self::SOCKET_BUFFER_SIZE; 47 | $resource = $this->socket->getStreamResource(); 48 | do { 49 | $data = stream_socket_recvfrom($resource, $size, STREAM_PEEK); 50 | if (strlen($data) < $size) { 51 | break; 52 | } 53 | 54 | $size += $size; 55 | } while (true); 56 | 57 | $data = stream_socket_recvfrom($resource, $size, 0, $actualRemoteAddress); 58 | 59 | return $picker->pickUpData($data, $actualRemoteAddress); 60 | } 61 | 62 | /** {@inheritdoc} */ 63 | protected function writeRawData($data, $isOutOfBand) 64 | { 65 | $result = stream_socket_sendto($this->socket->getStreamResource(), $data, 0, $this->remoteAddress); 66 | if ($result < 0) { 67 | throw SendDataException::failedToSendData($this->socket); 68 | } 69 | 70 | return $result; 71 | } 72 | 73 | /** {@inheritdoc} */ 74 | protected function getRemoteAddress() 75 | { 76 | return $this->remoteAddress; 77 | } 78 | 79 | /** {@inheritdoc} */ 80 | public function isConnected() 81 | { 82 | return true; 83 | } 84 | 85 | /** {@inheritdoc} */ 86 | protected function canReachFrame() 87 | { 88 | // in datagram we have no second chance to receive the frame 89 | return false; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Socket/Io/DatagramMemorizedIo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Frame\FramePickerInterface; 13 | use AsyncSockets\Socket\SocketInterface; 14 | 15 | /** 16 | * Class DatagramMemorizedIo 17 | */ 18 | class DatagramMemorizedIo extends DatagramClientIo 19 | { 20 | /** 21 | * Data for this socket 22 | * 23 | * @var string 24 | */ 25 | private $data; 26 | 27 | /** 28 | * Constructor 29 | * 30 | * @param SocketInterface $socket Socket object 31 | * @param string $remoteAddress Destination address in form scheme://host:port 32 | * @param string $data Datagram for this socket 33 | */ 34 | public function __construct(SocketInterface $socket, $remoteAddress, $data) 35 | { 36 | parent::__construct($socket, $remoteAddress); 37 | $this->data = (string) $data; 38 | } 39 | 40 | /** {@inheritdoc} */ 41 | protected function readRawDataIntoPicker(FramePickerInterface $picker, $isOutOfBand) 42 | { 43 | $data = $this->data; 44 | $this->data = ''; 45 | return $picker->pickUpData($data, $this->remoteAddress); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Socket/Io/DatagramServerIo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Exception\AcceptException; 13 | use AsyncSockets\Frame\AcceptedFrame; 14 | use AsyncSockets\Frame\FramePickerInterface; 15 | use AsyncSockets\Frame\RawFramePicker; 16 | use AsyncSockets\Socket\SocketInterface; 17 | use AsyncSockets\Socket\UdpClientSocket; 18 | 19 | /** 20 | * Class DatagramServerIo 21 | */ 22 | class DatagramServerIo extends AbstractServerIo 23 | { 24 | /** 25 | * Flag whether it is local file socket 26 | * 27 | * @var bool 28 | */ 29 | private $isLocalIo; 30 | 31 | /** 32 | * DatagramServerIo constructor. 33 | * 34 | * @param SocketInterface $socket Socket object 35 | * @param bool $isLocal Flag, whether it is local socket 36 | */ 37 | public function __construct(SocketInterface $socket, $isLocal) 38 | { 39 | parent::__construct($socket); 40 | $this->isLocalIo = $isLocal; 41 | } 42 | 43 | /** {@inheritdoc} */ 44 | public function read(FramePickerInterface $picker, Context $context, $isOutOfBand) 45 | { 46 | stream_socket_recvfrom( 47 | $this->socket->getStreamResource(), 48 | self::SOCKET_BUFFER_SIZE, 49 | STREAM_PEEK, 50 | $remoteAddress 51 | ); 52 | 53 | if (!$remoteAddress && !$this->isLocalIo) { 54 | stream_socket_recvfrom($this->socket->getStreamResource(), self::SOCKET_BUFFER_SIZE); 55 | throw new AcceptException($this->socket, 'Can not accept client: failed to receive remote address.'); 56 | } 57 | 58 | $reader = new DatagramClientIo($this->socket, $this->isLocalIo ? null : $remoteAddress); 59 | return new AcceptedFrame( 60 | $remoteAddress, 61 | new UdpClientSocket( 62 | $this->socket, 63 | $remoteAddress, 64 | $reader->read(new RawFramePicker(), $context, $isOutOfBand) 65 | ) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Socket/Io/DisconnectedIo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Exception\NetworkSocketException; 13 | use AsyncSockets\Frame\FramePickerInterface; 14 | 15 | /** 16 | * Class DisconnectedIo 17 | */ 18 | class DisconnectedIo extends AbstractIo 19 | { 20 | /** {@inheritdoc} */ 21 | public function read(FramePickerInterface $picker, Context $context, $isOutOfBand) 22 | { 23 | throw new NetworkSocketException($this->socket, 'Can not start io operation on uninitialized socket.'); 24 | } 25 | 26 | /** {@inheritdoc} */ 27 | public function write($data, Context $context, $isOutOfBand) 28 | { 29 | throw new NetworkSocketException($this->socket, 'Can not start io operation on uninitialized socket.'); 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function isConnected() 36 | { 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Socket/Io/IoInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Frame\FrameInterface; 13 | use AsyncSockets\Frame\FramePickerInterface; 14 | 15 | /** 16 | * Interface IoInterface 17 | */ 18 | interface IoInterface 19 | { 20 | /** 21 | * Socket buffer size 22 | */ 23 | const SOCKET_BUFFER_SIZE = 8192; 24 | 25 | /** 26 | * Perform reading data from socket and fill picker object 27 | * 28 | * @param FramePickerInterface $picker Frame object to read 29 | * @param Context $context Socket context 30 | * @param bool $isOutOfBand Flag if it is out-of-band data 31 | * 32 | * @return FrameInterface 33 | */ 34 | public function read(FramePickerInterface $picker, Context $context, $isOutOfBand); 35 | 36 | /** 37 | * Write data to this socket 38 | * 39 | * @param string $data Data to send 40 | * @param Context $context Socket context 41 | * @param bool $isOutOfBand Flag if it is out-of-band data 42 | * 43 | * @return int Number of written bytes 44 | */ 45 | public function write($data, Context $context, $isOutOfBand); 46 | 47 | /** 48 | * Check whether given socket resource is connected 49 | * 50 | * @return bool 51 | */ 52 | public function isConnected(); 53 | } 54 | -------------------------------------------------------------------------------- /src/Socket/Io/StreamedServerIo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket\Io; 11 | 12 | use AsyncSockets\Exception\AcceptException; 13 | use AsyncSockets\Frame\AcceptedFrame; 14 | use AsyncSockets\Frame\FramePickerInterface; 15 | use AsyncSockets\Socket\AcceptedSocket; 16 | 17 | /** 18 | * Class StreamedServerIo 19 | */ 20 | class StreamedServerIo extends AbstractServerIo 21 | { 22 | /** {@inheritdoc} */ 23 | public function read(FramePickerInterface $picker, Context $context, $isOutOfBand) 24 | { 25 | $client = stream_socket_accept($this->socket->getStreamResource(), 0, $peerName); 26 | if ($client === false) { 27 | throw new AcceptException($this->socket, 'Can not accept client connection.'); 28 | } 29 | 30 | return new AcceptedFrame( 31 | $peerName ?: '', 32 | new AcceptedSocket($client) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Socket/PersistentClientSocket.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Socket; 12 | 13 | use AsyncSockets\Exception\ConnectionException; 14 | 15 | /** 16 | * Class PersistentClientSocket 17 | */ 18 | class PersistentClientSocket extends AbstractClientSocket 19 | { 20 | /** 21 | * Key in php persistent storage to allow multiple persistent connections to the same host 22 | * 23 | * @var null|string 24 | */ 25 | private $persistentKey; 26 | 27 | /** 28 | * PersistentClientSocket constructor. 29 | * 30 | * @param string|null $persistentKey Key in php persistent storage to allow multiple persistent 31 | * connections to the same host [a-zA-Z0-9_-] 32 | */ 33 | public function __construct($persistentKey = null) 34 | { 35 | parent::__construct(); 36 | $this->persistentKey = $persistentKey; 37 | } 38 | 39 | /** {@inheritdoc} */ 40 | protected function createSocketResource($address, $context) 41 | { 42 | $resource = stream_socket_client( 43 | $address . ($this->persistentKey ? '/' . $this->persistentKey : ''), 44 | $errno, 45 | $errstr, 46 | null, 47 | STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_PERSISTENT, 48 | $context 49 | ); 50 | 51 | if ($errno || $resource === false) { 52 | throw new ConnectionException($this, $errstr, $errno); 53 | } 54 | 55 | return $resource; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Socket/SelectContext.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Socket; 12 | 13 | /** 14 | * Class SelectContext 15 | */ 16 | class SelectContext 17 | { 18 | /** 19 | * List os sockets ready to read 20 | * 21 | * @var StreamResourceInterface[] 22 | */ 23 | private $read; 24 | 25 | /** 26 | * List of sockets ready to write 27 | * 28 | * @var StreamResourceInterface[] 29 | */ 30 | private $write; 31 | 32 | /** 33 | * array 34 | * 35 | * @var array 36 | */ 37 | private $oob; 38 | 39 | /** 40 | * Constructor 41 | * 42 | * @param StreamResourceInterface[] $read List of ready to read sockets 43 | * @param StreamResourceInterface[] $write List of ready to write sockets 44 | * @param StreamResourceInterface[] $oob List of sockets having OOB data 45 | */ 46 | public function __construct(array $read, array $write, array $oob) 47 | { 48 | $this->read = $read; 49 | $this->write = $write; 50 | $this->oob = $oob; 51 | } 52 | 53 | /** 54 | * Get ready to read sockets 55 | * 56 | * @return StreamResourceInterface[] 57 | */ 58 | public function getRead() 59 | { 60 | return $this->read; 61 | } 62 | 63 | /** 64 | * Get ready to write sockets 65 | * 66 | * @return StreamResourceInterface[] 67 | */ 68 | public function getWrite() 69 | { 70 | return $this->write; 71 | } 72 | 73 | /** 74 | * Return sockets having OOB data 75 | * 76 | * @return StreamResourceInterface[] 77 | */ 78 | public function getOob() 79 | { 80 | return $this->oob; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Socket/ServerSocket.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket; 11 | 12 | use AsyncSockets\Exception\ConnectionException; 13 | use AsyncSockets\Socket\Io\DatagramServerIo; 14 | use AsyncSockets\Socket\Io\StreamedServerIo; 15 | 16 | /** 17 | * Class ServerSocket 18 | */ 19 | class ServerSocket extends AbstractSocket 20 | { 21 | /** {@inheritdoc} */ 22 | protected function createSocketResource($address, $context) 23 | { 24 | $type = $this->getSocketScheme($address); 25 | $resource = stream_socket_server( 26 | $address, 27 | $errno, 28 | $errstr, 29 | $this->getServerFlagsByType($type), 30 | $context 31 | ); 32 | 33 | if ($errno || $resource === false) { 34 | throw new ConnectionException($this, $errstr, $errno); 35 | } 36 | 37 | return $resource; 38 | } 39 | 40 | /** {@inheritdoc} */ 41 | protected function createIoInterface($type, $address) 42 | { 43 | switch ($type) { 44 | case self::SOCKET_TYPE_UNIX: 45 | return new StreamedServerIo($this); 46 | case self::SOCKET_TYPE_TCP: 47 | return new StreamedServerIo($this); 48 | case self::SOCKET_TYPE_UDG: 49 | return new DatagramServerIo($this, true); 50 | case self::SOCKET_TYPE_UDP: 51 | return new DatagramServerIo($this, false); 52 | default: 53 | throw new \LogicException("Unsupported socket resource type {$type}"); 54 | } 55 | } 56 | 57 | /** 58 | * Return socket scheme 59 | * 60 | * @param string $address Address in form scheme://host:port 61 | * 62 | * @return string|null 63 | */ 64 | private function getSocketScheme($address) 65 | { 66 | $pos = strpos($address, '://'); 67 | if ($pos === false) { 68 | return null; 69 | } 70 | 71 | return substr($address, 0, $pos); 72 | } 73 | 74 | /** 75 | * Return flags for connection 76 | * 77 | * @param string $scheme Socket type being created 78 | * 79 | * @return int 80 | */ 81 | private function getServerFlagsByType($scheme) 82 | { 83 | $connectionLessMap = [ 84 | 'udp' => 1, 85 | 'udg' => 1, 86 | ]; 87 | 88 | return isset($connectionLessMap[ $scheme ]) ? 89 | STREAM_SERVER_BIND : 90 | STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; 91 | } 92 | 93 | /** 94 | * @inheritDoc 95 | */ 96 | public function isServer() 97 | { 98 | return true; 99 | } 100 | 101 | /** 102 | * @inheritDoc 103 | */ 104 | public function isClient() 105 | { 106 | return false; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Socket/SocketInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Socket; 12 | 13 | use AsyncSockets\Exception\NetworkSocketException; 14 | use AsyncSockets\Frame\FrameInterface; 15 | use AsyncSockets\Frame\FramePickerInterface; 16 | 17 | /** 18 | * Interface SocketInterface 19 | * 20 | * @api 21 | */ 22 | interface SocketInterface extends StreamResourceInterface 23 | { 24 | /** 25 | * Tries to connect to server 26 | * 27 | * @param string $address Network address to open in form transport://path:port 28 | * @param resource|null $context Valid stream context created by function stream_context_create or null 29 | * 30 | * @return void 31 | * @throws NetworkSocketException 32 | * 33 | * @api 34 | */ 35 | public function open($address, $context = null); 36 | 37 | /** 38 | * Close connection to socket 39 | * 40 | * @return void 41 | * 42 | * @api 43 | */ 44 | public function close(); 45 | 46 | /** 47 | * Read data from this socket 48 | * 49 | * @param FramePickerInterface $picker Frame data picker, if null then read data until transfer is not complete 50 | * @param bool $isOutOfBand Flag if operation is out of band 51 | * 52 | * @return FrameInterface 53 | * @throws NetworkSocketException 54 | * 55 | * @api 56 | */ 57 | public function read(FramePickerInterface $picker, $isOutOfBand = false); 58 | 59 | /** 60 | * Write data to this socket 61 | * 62 | * @param string $data Data to send 63 | * @param bool $isOutOfBand Flag if operation is out of band 64 | * 65 | * @return int Number of written bytes 66 | * @api 67 | */ 68 | public function write($data, $isOutOfBand = false); 69 | 70 | /** 71 | * Return debug information about this socket 72 | * 73 | * @return string 74 | */ 75 | public function __toString(); 76 | 77 | /** 78 | * Return true if it is server socket 79 | * 80 | * @return bool 81 | */ 82 | public function isServer(); 83 | 84 | /** 85 | * Return true if it is client socket 86 | * 87 | * @return bool 88 | */ 89 | public function isClient(); 90 | 91 | /** 92 | * Return true if this socket is in connected state, false otherwise 93 | * 94 | * @return bool 95 | */ 96 | public function isConnected(); 97 | } 98 | -------------------------------------------------------------------------------- /src/Socket/StreamResourceInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | 11 | namespace AsyncSockets\Socket; 12 | 13 | /** 14 | * Interface StreamResourceInterface 15 | */ 16 | interface StreamResourceInterface 17 | { 18 | /** 19 | * Return socket resource 20 | * 21 | * @return resource 22 | */ 23 | public function getStreamResource(); 24 | } 25 | -------------------------------------------------------------------------------- /src/Socket/UdpClientSocket.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket; 11 | 12 | use AsyncSockets\Frame\FramePickerInterface; 13 | use AsyncSockets\Socket\Io\Context; 14 | use AsyncSockets\Socket\Io\DatagramMemorizedIo; 15 | use AsyncSockets\Socket\Io\IoInterface; 16 | 17 | /** 18 | * Class UdpClientSocket 19 | */ 20 | class UdpClientSocket implements SocketInterface, WithoutConnectionInterface 21 | { 22 | /** 23 | * Original server socket 24 | * 25 | * @var SocketInterface 26 | */ 27 | private $origin; 28 | 29 | /** 30 | * I/O interface 31 | * 32 | * @var IoInterface 33 | */ 34 | private $ioInterface; 35 | 36 | /** 37 | * Socket context 38 | * 39 | * @var Context 40 | */ 41 | private $context; 42 | 43 | /** 44 | * UdpClientSocket constructor. 45 | * 46 | * @param SocketInterface $origin Original server socket 47 | * @param string $remoteAddress Client address 48 | * @param string $data Data for this client 49 | */ 50 | public function __construct(SocketInterface $origin, $remoteAddress, $data) 51 | { 52 | $this->origin = $origin; 53 | $this->ioInterface = new DatagramMemorizedIo($this, $remoteAddress, $data); 54 | $this->context = new Context(); 55 | } 56 | 57 | /** {@inheritdoc} */ 58 | public function open($address, $context = null) 59 | { 60 | // empty body 61 | } 62 | 63 | /** {@inheritdoc} */ 64 | public function close() 65 | { 66 | // empty body 67 | } 68 | 69 | /** {@inheritdoc} */ 70 | public function read(FramePickerInterface $picker, $isOutOfBand = false) 71 | { 72 | return $this->ioInterface->read($picker, $this->context, $isOutOfBand); 73 | } 74 | 75 | /** {@inheritdoc} */ 76 | public function write($data, $isOutOfBand = false) 77 | { 78 | return $this->ioInterface->write($data, $this->context, $isOutOfBand); 79 | } 80 | 81 | /** {@inheritdoc} */ 82 | public function getStreamResource() 83 | { 84 | return $this->origin->getStreamResource(); 85 | } 86 | 87 | /** 88 | * @inheritDoc 89 | */ 90 | public function __toString() 91 | { 92 | return (string) $this->origin; 93 | } 94 | 95 | /** 96 | * @inheritDoc 97 | */ 98 | public function isServer() 99 | { 100 | return false; 101 | } 102 | 103 | /** 104 | * @inheritDoc 105 | */ 106 | public function isClient() 107 | { 108 | return true; 109 | } 110 | 111 | /** 112 | * @inheritDoc 113 | */ 114 | public function isConnected() 115 | { 116 | return $this->ioInterface->isConnected(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Socket/WithoutConnectionInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * This source file is subject to the MIT license that is bundled 8 | * with this source code in the file LICENSE. 9 | */ 10 | namespace AsyncSockets\Socket; 11 | 12 | /** 13 | * Interface WithoutConnectionInterface 14 | */ 15 | interface WithoutConnectionInterface 16 | { 17 | 18 | } 19 | --------------------------------------------------------------------------------