1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Async sockets |
4
|
|
|
* |
5
|
|
|
* @copyright Copyright (c) 2015-2017, Efimov Evgenij <[email protected]> |
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\BadResourceException; |
14
|
|
|
use AsyncSockets\Exception\ConnectionException; |
15
|
|
|
use AsyncSockets\Frame\FramePickerInterface; |
16
|
|
|
use AsyncSockets\Socket\Io\Context; |
17
|
|
|
use AsyncSockets\Socket\Io\DisconnectedIo; |
18
|
|
|
use AsyncSockets\Socket\Io\IoInterface; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Class AbstractSocket |
22
|
|
|
*/ |
23
|
|
|
abstract class AbstractSocket implements SocketInterface |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* Tcp socket type |
27
|
|
|
*/ |
28
|
|
|
const SOCKET_TYPE_TCP = 'tcp'; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Udp socket type |
32
|
|
|
*/ |
33
|
|
|
const SOCKET_TYPE_UDP = 'udp'; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Unix socket type |
37
|
|
|
*/ |
38
|
|
|
const SOCKET_TYPE_UNIX = 'unix'; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Unix datagram socket type |
42
|
|
|
*/ |
43
|
|
|
const SOCKET_TYPE_UDG = 'udg'; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Unknown type of socket |
47
|
|
|
*/ |
48
|
|
|
const SOCKET_TYPE_UNKNOWN = ''; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Array of socket resources |
52
|
|
|
* |
53
|
|
|
* @var resource[] |
54
|
|
|
*/ |
55
|
|
|
private static $resources; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Flag if we have shutdown function |
59
|
|
|
* |
60
|
|
|
* @var bool |
61
|
|
|
*/ |
62
|
|
|
private static $hasShutdownClearer = false; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Gracefully disconnects sockets when application terminating |
66
|
|
|
* |
67
|
|
|
* @return void |
68
|
|
|
*/ |
69
|
|
|
public static function shutdownSocketCleaner() |
70
|
|
|
{ |
71
|
|
|
foreach (self::$resources as $resource) { |
72
|
|
|
try { |
73
|
|
|
$socket = self::fromResource($resource); |
74
|
|
|
if (!($socket instanceof PersistentClientSocket)) { |
75
|
|
|
$socket->close(); |
76
|
|
|
} |
77
|
|
|
} catch (BadResourceException $e) { |
78
|
|
|
// nothing required |
79
|
|
|
} |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Creates socket object from given resource |
85
|
|
|
* |
86
|
|
|
* @param resource $resource Opened socket resource |
87
|
|
|
* |
88
|
|
|
* @return SocketInterface |
89
|
|
|
*/ |
90
|
|
|
public static function fromResource($resource) |
91
|
|
|
{ |
92
|
|
|
$map = [ |
93
|
|
|
'stream' => 0, |
94
|
|
|
'persistent stream' => 1, |
95
|
|
|
]; |
96
|
|
|
|
97
|
|
|
$type = get_resource_type($resource); |
98
|
|
|
if (!isset($map[$type])) { |
99
|
|
|
throw BadResourceException::notSocketResource($type); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$isServer = 0; |
103
|
|
|
$isPersistent = $map[$type]; |
104
|
|
|
$localAddress = stream_socket_get_name($resource, false); |
105
|
|
|
if ($localAddress === false) { |
106
|
|
|
throw BadResourceException::canNotObtainLocalAddress(); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
try { |
110
|
|
|
if (($s = stream_socket_client( |
111
|
|
|
$localAddress, |
112
|
|
|
$errno, |
113
|
|
|
$errstr, |
114
|
|
|
(int) ini_get('default_socket_timeout'), |
115
|
|
|
STREAM_CLIENT_CONNECT, |
116
|
|
|
stream_context_get_default() |
117
|
|
|
)) !== false |
118
|
|
|
) { |
119
|
|
|
$isServer = 1; |
120
|
|
|
fclose($s); |
121
|
|
|
} |
122
|
|
|
} catch (\Exception $e) { |
123
|
|
|
// do nothing |
124
|
|
|
} catch (\Throwable $e) { |
|
|
|
|
125
|
|
|
// do nothing |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
// [isServer][isPersistent] => class |
|
|
|
|
129
|
|
|
$objectMap = [ |
130
|
|
|
0 => [ |
131
|
|
|
0 => 'AsyncSockets\Socket\ClientSocket', |
132
|
|
|
1 => 'AsyncSockets\Socket\PersistentClientSocket', |
133
|
|
|
], |
134
|
|
|
1 => [ |
135
|
|
|
0 => 'AsyncSockets\Socket\ServerSocket', |
136
|
|
|
1 => 'AsyncSockets\Socket\ServerSocket', |
137
|
|
|
] |
138
|
|
|
]; |
139
|
|
|
|
140
|
|
|
/** @var AbstractSocket $result */ |
141
|
|
|
$result = new $objectMap[$isServer][$isPersistent](); |
142
|
|
|
$result->setupSocketResource($resource, (string) stream_socket_get_name($resource, true)); |
143
|
|
|
|
144
|
|
|
return $result; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* This socket resource |
149
|
|
|
* |
150
|
|
|
* @var resource |
151
|
|
|
*/ |
152
|
|
|
private $resource; |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* I/O interface |
156
|
|
|
* |
157
|
|
|
* @var IoInterface |
158
|
|
|
*/ |
159
|
|
|
private $ioInterface; |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Socket address |
163
|
|
|
* |
164
|
|
|
* @var string |
165
|
|
|
*/ |
166
|
|
|
private $remoteAddress; |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Context for this socket |
170
|
|
|
* |
171
|
|
|
* @var Context |
172
|
|
|
*/ |
173
|
|
|
private $context; |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* AbstractSocket constructor. |
177
|
|
|
*/ |
178
|
139 |
|
public function __construct() |
179
|
|
|
{ |
180
|
139 |
|
$this->setDisconnectedState(); |
181
|
139 |
|
$this->context = new Context(); |
182
|
|
|
|
183
|
139 |
|
if (!self::$hasShutdownClearer) { |
184
|
1 |
|
self::$hasShutdownClearer = true; |
185
|
1 |
|
register_shutdown_function(['AsyncSockets\Socket\AbstractSocket', 'shutdownSocketCleaner']); |
186
|
1 |
|
} |
187
|
139 |
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Set disconnected state for socket |
191
|
|
|
* |
192
|
|
|
* @return void |
193
|
|
|
*/ |
194
|
139 |
|
private function setDisconnectedState() |
195
|
|
|
{ |
196
|
139 |
|
$this->ioInterface = new DisconnectedIo($this); |
197
|
139 |
|
} |
198
|
|
|
|
199
|
|
|
/** {@inheritdoc} */ |
200
|
75 |
|
public function open($address, $context = null) |
201
|
|
|
{ |
202
|
75 |
|
$resource = $this->createSocketResource( |
203
|
75 |
|
$address, |
204
|
63 |
|
$context ?: stream_context_get_default() |
205
|
75 |
|
); |
206
|
|
|
|
207
|
69 |
|
$this->setupSocketResource($resource, $address); |
208
|
60 |
|
} |
209
|
|
|
|
210
|
|
|
/** {@inheritdoc} */ |
211
|
12 |
|
public function close() |
212
|
|
|
{ |
213
|
12 |
|
if ($this->resource) { |
214
|
8 |
|
unset(self::$resources[(string) $this->resource]); |
215
|
8 |
|
$this->setDisconnectedState(); |
216
|
8 |
|
stream_socket_shutdown($this->resource, STREAM_SHUT_RDWR); |
217
|
8 |
|
fclose($this->resource); |
218
|
8 |
|
$this->resource = null; |
219
|
8 |
|
$this->remoteAddress = null; |
220
|
8 |
|
} |
221
|
12 |
|
} |
222
|
|
|
|
223
|
|
|
/** {@inheritdoc} */ |
224
|
31 |
|
public function read(FramePickerInterface $picker, $isOutOfBand = false) |
225
|
|
|
{ |
226
|
|
|
try { |
227
|
31 |
|
return $this->ioInterface->read($picker, $this->context, $isOutOfBand); |
228
|
10 |
|
} catch (ConnectionException $e) { |
229
|
5 |
|
$this->setDisconnectedState(); |
230
|
5 |
|
throw $e; |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** {@inheritdoc} */ |
235
|
10 |
|
public function write($data, $isOutOfBand = false) |
236
|
|
|
{ |
237
|
|
|
try { |
238
|
10 |
|
return $this->ioInterface->write($data, $this->context, $isOutOfBand); |
239
|
10 |
|
} catch (ConnectionException $e) { |
240
|
5 |
|
$this->setDisconnectedState(); |
241
|
5 |
|
throw $e; |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** {@inheritdoc} */ |
246
|
45 |
|
public function getStreamResource() |
247
|
|
|
{ |
248
|
45 |
|
return $this->resource; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* @inheritDoc |
253
|
|
|
*/ |
254
|
10 |
|
public function __toString() |
255
|
|
|
{ |
256
|
10 |
|
return $this->remoteAddress ? |
257
|
10 |
|
sprintf( |
258
|
5 |
|
'[#%s, %s]', |
259
|
5 |
|
preg_replace('/Resource id #(\d+)/i', '$1', (string) $this->resource), |
260
|
5 |
|
$this->remoteAddress |
261
|
5 |
|
) : |
262
|
10 |
|
'[closed socket]'; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Create certain socket resource |
267
|
|
|
* |
268
|
|
|
* @param string $address Network address to open in form transport://path:port |
269
|
|
|
* @param resource $context Valid stream context created by function stream_context_create or null |
270
|
|
|
* |
271
|
|
|
* @return resource |
272
|
|
|
*/ |
273
|
|
|
abstract protected function createSocketResource($address, $context); |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Create I/O interface for socket |
277
|
|
|
* |
278
|
|
|
* @param string $type Type of this socket, one of SOCKET_TYPE_* consts |
279
|
|
|
* @param string $address Address passed to open method |
280
|
|
|
* |
281
|
|
|
* @return IoInterface |
282
|
|
|
*/ |
283
|
|
|
abstract protected function createIoInterface($type, $address); |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Get current socket type |
287
|
|
|
* |
288
|
|
|
* @return string One of SOCKET_TYPE_* consts |
289
|
|
|
*/ |
290
|
64 |
|
private function resolveSocketType() |
291
|
|
|
{ |
292
|
64 |
|
$info = stream_get_meta_data($this->resource); |
293
|
64 |
|
if (!isset($info['stream_type'])) { |
294
|
4 |
|
return self::SOCKET_TYPE_UNKNOWN; |
295
|
|
|
} |
296
|
|
|
|
297
|
60 |
|
$parts = explode('/', $info['stream_type']); |
298
|
|
|
$map = [ |
299
|
60 |
|
'tcp' => self::SOCKET_TYPE_TCP, |
300
|
60 |
|
'udp' => self::SOCKET_TYPE_UDP, |
301
|
60 |
|
'udg' => self::SOCKET_TYPE_UDG, |
302
|
60 |
|
'unix' => self::SOCKET_TYPE_UNIX, |
303
|
60 |
|
]; |
304
|
|
|
|
305
|
60 |
|
$regexp = '#^('. implode('|', array_keys($map)) . ')_socket$#'; |
306
|
60 |
|
foreach ($parts as $part) { |
307
|
60 |
|
if (preg_match($regexp, $part, $pockets)) { |
308
|
45 |
|
return $map[$pockets[1]]; |
309
|
|
|
} |
310
|
15 |
|
} |
311
|
|
|
|
312
|
15 |
|
return self::SOCKET_TYPE_UNKNOWN; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Set up internal data for given resource |
317
|
|
|
* |
318
|
|
|
* @param resource $resource Socket resource |
319
|
|
|
* @param string $address Socket address |
320
|
|
|
* |
321
|
|
|
* @return void |
322
|
|
|
*/ |
323
|
69 |
|
private function setupSocketResource($resource, $address) |
324
|
|
|
{ |
325
|
69 |
|
if (!is_resource($resource)) { |
326
|
5 |
|
throw new ConnectionException( |
327
|
5 |
|
$this, |
328
|
|
|
'Can not allocate socket resource.' |
329
|
5 |
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
64 |
|
$this->resource = $resource; |
333
|
64 |
|
$this->remoteAddress = $address; |
334
|
|
|
|
335
|
|
|
// https://bugs.php.net/bug.php?id=51056 |
336
|
64 |
|
stream_set_blocking($this->resource, 0); |
337
|
|
|
|
338
|
|
|
// https://bugs.php.net/bug.php?id=52602 |
339
|
64 |
|
stream_set_timeout($this->resource, 0, 0); |
340
|
64 |
|
stream_set_chunk_size($this->resource, IoInterface::SOCKET_BUFFER_SIZE); |
341
|
|
|
|
342
|
64 |
|
$this->ioInterface = $this->createIoInterface( |
343
|
64 |
|
$this->resolveSocketType(), |
344
|
|
|
$address |
345
|
64 |
|
); |
346
|
|
|
|
347
|
60 |
|
self::$resources[(string) $resource] = $resource; |
348
|
60 |
|
$this->context->reset(); |
349
|
60 |
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* @inheritDoc |
353
|
|
|
*/ |
354
|
8 |
|
public function isConnected() |
355
|
|
|
{ |
356
|
8 |
|
return $this->ioInterface->isConnected(); |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
|
Scrutinizer analyzes your
composer.json
/composer.lock
file if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.