|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Navarr\Socket; |
|
4
|
|
|
|
|
5
|
|
|
use Navarr\Socket\Exception\SocketException; |
|
6
|
|
|
|
|
7
|
|
|
/** |
|
8
|
|
|
* Class Socket. |
|
9
|
|
|
* |
|
10
|
|
|
* A simple wrapper for PHP's socket functions |
|
11
|
|
|
*/ |
|
12
|
|
|
class Socket |
|
13
|
|
|
{ |
|
14
|
|
|
protected $resource = null; |
|
15
|
|
|
protected $domain = null; |
|
16
|
|
|
protected $type = null; |
|
17
|
|
|
protected $protocol = null; |
|
18
|
|
|
protected static $map = []; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* Sets up the Socket Resource. |
|
22
|
|
|
* |
|
23
|
|
|
* @param resource $resource |
|
24
|
|
|
*/ |
|
25
|
3 |
|
protected function __construct($resource) |
|
26
|
|
|
{ |
|
27
|
3 |
|
$this->resource = $resource; |
|
28
|
3 |
|
self::$map[(string) $resource] = $this; |
|
29
|
3 |
|
} |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* Cleans up the Socket. |
|
33
|
|
|
*/ |
|
34
|
|
|
public function __destruct() |
|
35
|
|
|
{ |
|
36
|
|
|
$this->close(); |
|
37
|
|
|
$this->resource = null; |
|
38
|
|
|
} |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* Return the resource name. |
|
42
|
|
|
* |
|
43
|
|
|
* @return string |
|
44
|
|
|
*/ |
|
45
|
|
|
public function __toString() |
|
46
|
|
|
{ |
|
47
|
|
|
return (string) $this->resource; |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* Accept a connection. |
|
52
|
|
|
* |
|
53
|
|
|
* @throws Exception\SocketException |
|
54
|
|
|
* |
|
55
|
|
|
* @return Socket |
|
56
|
|
|
*/ |
|
57
|
|
|
public function accept() |
|
|
|
|
|
|
58
|
|
|
{ |
|
59
|
|
|
$return = @socket_accept($this->resource); |
|
60
|
|
|
|
|
61
|
|
|
if ($return === false) { |
|
62
|
|
|
throw new SocketException($this->resource); |
|
63
|
|
|
} |
|
64
|
|
|
|
|
65
|
|
|
return new self($return); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
/** |
|
69
|
|
|
* @param string $address |
|
70
|
|
|
* @param int $port |
|
71
|
|
|
* |
|
72
|
|
|
* @throws Exception\SocketException |
|
73
|
|
|
* |
|
74
|
|
|
* @return bool |
|
75
|
|
|
*/ |
|
76
|
1 |
|
public function bind($address, $port = 0) |
|
77
|
|
|
{ |
|
78
|
1 |
|
$return = @socket_bind($this->resource, $address, $port); |
|
79
|
|
|
|
|
80
|
1 |
|
if ($return === false) { |
|
81
|
|
|
throw new SocketException($this->resource); |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
1 |
|
return true; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
/** |
|
88
|
|
|
* Close the socket. |
|
89
|
|
|
* |
|
90
|
|
|
* @return void |
|
91
|
|
|
*/ |
|
92
|
|
|
public function close() |
|
93
|
|
|
{ |
|
94
|
|
|
unset(self::$map[(string) $this->resource]); |
|
95
|
|
|
@socket_close($this->resource); |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* Connect to a socket. |
|
100
|
|
|
* |
|
101
|
|
|
* @param $address |
|
102
|
|
|
* @param int $port |
|
103
|
|
|
* |
|
104
|
|
|
* @throws Exception\SocketException |
|
105
|
|
|
* |
|
106
|
|
|
* @return bool |
|
107
|
|
|
*/ |
|
108
|
|
|
public function connect($address, $port = 0) |
|
109
|
|
|
{ |
|
110
|
|
|
$return = @socket_connect($this->resource, $address, $port); |
|
111
|
|
|
|
|
112
|
|
|
if ($return === false) { |
|
113
|
|
|
throw new SocketException($this->resource); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
return true; |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
/** |
|
120
|
|
|
* @param array $resources |
|
121
|
|
|
* |
|
122
|
|
|
* @return Socket[] |
|
123
|
|
|
*/ |
|
124
|
|
|
protected static function constructFromResources(array $resources) |
|
125
|
|
|
{ |
|
126
|
|
|
$sockets = []; |
|
127
|
|
|
|
|
128
|
|
|
foreach ($resources as $resource) { |
|
129
|
|
|
$sockets[] = new self($resource); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
return $sockets; |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
/** |
|
136
|
|
|
* Create a socket. |
|
137
|
|
|
* |
|
138
|
|
|
* @param int $domain |
|
139
|
|
|
* @param int $type |
|
140
|
|
|
* @param int $protocol |
|
141
|
|
|
* |
|
142
|
|
|
* @throws Exception\SocketException |
|
143
|
|
|
* |
|
144
|
|
|
* @return Socket |
|
145
|
|
|
*/ |
|
146
|
4 |
|
public static function create($domain, $type, $protocol) |
|
147
|
|
|
{ |
|
148
|
4 |
|
$return = @socket_create($domain, $type, $protocol); |
|
149
|
|
|
|
|
150
|
4 |
|
if ($return === false) { |
|
151
|
1 |
|
throw new SocketException(); |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
3 |
|
$socket = new self($return); |
|
155
|
3 |
|
$socket->domain = $domain; |
|
156
|
3 |
|
$socket->type = $type; |
|
157
|
3 |
|
$socket->protocol = $protocol; |
|
158
|
|
|
|
|
159
|
3 |
|
return $socket; |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
/** |
|
163
|
|
|
* @param $port |
|
164
|
|
|
* @param int $backlog |
|
165
|
|
|
* |
|
166
|
|
|
* @throws Exception\SocketException |
|
167
|
|
|
* |
|
168
|
|
|
* @return Socket |
|
169
|
|
|
*/ |
|
170
|
|
|
public static function createListen($port, $backlog = 128) |
|
171
|
|
|
{ |
|
172
|
|
|
$return = @socket_create_listen($port, $backlog); |
|
173
|
|
|
|
|
174
|
|
|
if ($return === false) { |
|
175
|
|
|
throw new SocketException(); |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
$socket = new self($return); |
|
179
|
|
|
$socket->domain = AF_INET; |
|
180
|
|
|
|
|
181
|
|
|
return $socket; |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
/** |
|
185
|
|
|
* @param $domain |
|
186
|
|
|
* @param $type |
|
187
|
|
|
* @param $protocol |
|
188
|
|
|
* |
|
189
|
|
|
* @throws Exception\SocketException |
|
190
|
|
|
* |
|
191
|
|
|
* @return Socket[] |
|
192
|
|
|
*/ |
|
193
|
|
|
public static function createPair($domain, $type, $protocol) |
|
194
|
|
|
{ |
|
195
|
|
|
$array = []; |
|
196
|
|
|
$return = @socket_create_pair($domain, $type, $protocol, $array); |
|
197
|
|
|
|
|
198
|
|
|
if ($return === false) { |
|
199
|
|
|
throw new SocketException(); |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
$sockets = self::constructFromResources($array); |
|
203
|
|
|
|
|
204
|
|
|
foreach ($sockets as $socket) { |
|
205
|
|
|
$socket->domain = $domain; |
|
206
|
|
|
$socket->type = $type; |
|
207
|
|
|
$socket->protocol = $protocol; |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
return $sockets; |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
/** |
|
214
|
|
|
* @param $level |
|
215
|
|
|
* @param $optname |
|
216
|
|
|
* |
|
217
|
|
|
* @throws Exception\SocketException |
|
218
|
|
|
* |
|
219
|
|
|
* @return mixed |
|
220
|
|
|
*/ |
|
221
|
|
|
public function getOption($level, $optname) |
|
222
|
|
|
{ |
|
223
|
|
|
$return = @socket_get_option($this->resource, $level, $optname); |
|
224
|
|
|
|
|
225
|
|
|
if ($return === false) { |
|
226
|
|
|
throw new SocketException($this->resource); |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
return $return; |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
/** |
|
233
|
|
|
* @param $address |
|
234
|
|
|
* @param $port |
|
235
|
|
|
* |
|
236
|
|
|
* @throws Exception\SocketException |
|
237
|
|
|
* |
|
238
|
|
|
* @return bool |
|
239
|
|
|
*/ |
|
240
|
|
|
public function getPeerName(&$address, &$port) |
|
241
|
|
|
{ |
|
242
|
|
|
$return = @socket_getpeername($this->resource, $address, $port); |
|
243
|
|
|
|
|
244
|
|
|
if ($return === false) { |
|
245
|
|
|
throw new SocketException($this->resource); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
return $return; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* @param string $address |
|
253
|
|
|
* @param int $port |
|
254
|
|
|
* |
|
255
|
|
|
* @throws Exception\SocketException |
|
256
|
|
|
* |
|
257
|
|
|
* @return bool |
|
258
|
|
|
*/ |
|
259
|
1 |
|
public function getSockName(&$address, &$port) |
|
260
|
|
|
{ |
|
261
|
1 |
|
if (!in_array($this->domain, [AF_UNIX, AF_INET, AF_INET6])) { |
|
262
|
|
|
return false; |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
1 |
|
$return = @socket_getsockname($this->resource, $address, $port); |
|
266
|
|
|
|
|
267
|
1 |
|
if ($return === false) { |
|
268
|
|
|
throw new SocketException($this->resource); |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
1 |
|
return $return; |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
/** |
|
275
|
|
|
* @param $stream |
|
276
|
|
|
* |
|
277
|
|
|
* @throws Exception\SocketException |
|
278
|
|
|
* |
|
279
|
|
|
* @return Socket |
|
280
|
|
|
*/ |
|
281
|
|
|
public static function importStream($stream) |
|
282
|
|
|
{ |
|
283
|
|
|
$return = @socket_import_stream($stream); |
|
284
|
|
|
|
|
285
|
|
|
if ($return === false) { |
|
286
|
|
|
throw new SocketException($stream); |
|
287
|
|
|
} |
|
288
|
|
|
|
|
289
|
|
|
return new self($return); |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* @param int $backlog |
|
294
|
|
|
* |
|
295
|
|
|
* @throws Exception\SocketException |
|
296
|
|
|
* |
|
297
|
|
|
* @return bool |
|
298
|
|
|
*/ |
|
299
|
1 |
|
public function listen($backlog = 0) |
|
300
|
|
|
{ |
|
301
|
1 |
|
$return = socket_listen($this->resource, $backlog); |
|
302
|
|
|
|
|
303
|
1 |
|
if ($return === false) { |
|
304
|
|
|
throw new SocketException($this->resource); |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
1 |
|
return true; |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
/** |
|
311
|
|
|
* @param int $length |
|
312
|
|
|
* @param int $type |
|
313
|
|
|
* |
|
314
|
|
|
* @throws Exception\SocketException |
|
315
|
|
|
* |
|
316
|
|
|
* @return string |
|
317
|
|
|
*/ |
|
318
|
|
|
public function read($length, $type = PHP_BINARY_READ) |
|
319
|
|
|
{ |
|
320
|
|
|
$return = @socket_read($this->resource, $length, $type); |
|
321
|
|
|
|
|
322
|
|
|
if ($return === false) { |
|
323
|
|
|
throw new SocketException($this->resource); |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
return $return; |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
/** |
|
330
|
|
|
* @param $buffer |
|
331
|
|
|
* @param int $length |
|
332
|
|
|
* @param int $flags |
|
333
|
|
|
* |
|
334
|
|
|
* @throws Exception\SocketException |
|
335
|
|
|
* |
|
336
|
|
|
* @return int |
|
337
|
|
|
*/ |
|
338
|
|
|
public function receive(&$buffer, $length, $flags) |
|
339
|
|
|
{ |
|
340
|
|
|
$return = @socket_recv($this->resource, $buffer, $length, $flags); |
|
341
|
|
|
|
|
342
|
|
|
if ($return === false) { |
|
343
|
|
|
throw new SocketException($this->resource); |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
return $return; |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
/** |
|
350
|
|
|
* @param Socket[] &$read |
|
351
|
|
|
* @param Socket[] &$write |
|
352
|
|
|
* @param Socket[] &$except |
|
353
|
|
|
* @param int $timeoutSeconds |
|
354
|
|
|
* @param int $timeoutMilliseconds |
|
355
|
|
|
* @param Socket[] $read |
|
356
|
|
|
* |
|
357
|
|
|
* @throws SocketException |
|
358
|
|
|
* |
|
359
|
|
|
* @return int |
|
360
|
|
|
*/ |
|
361
|
|
|
public static function select( |
|
362
|
|
|
&$read, |
|
363
|
|
|
&$write, |
|
364
|
|
|
&$except, |
|
365
|
|
|
$timeoutSeconds, |
|
366
|
|
|
$timeoutMilliseconds = 0 |
|
367
|
|
|
) { |
|
368
|
|
|
$readSockets = null; |
|
369
|
|
|
$writeSockets = null; |
|
370
|
|
|
$exceptSockets = null; |
|
371
|
|
|
|
|
372
|
|
|
if (!is_null($read)) { |
|
373
|
|
|
$readSockets = self::mapClassToRawSocket($read); |
|
374
|
|
|
} |
|
375
|
|
|
if (!is_null($write)) { |
|
376
|
|
|
$writeSockets = self::mapClassToRawSocket($write); |
|
377
|
|
|
} |
|
378
|
|
|
if (!is_null($except)) { |
|
379
|
|
|
$exceptSockets = self::mapClassToRawSocket($except); |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
$return = @socket_select( |
|
383
|
|
|
$readSockets, |
|
384
|
|
|
$writeSockets, |
|
385
|
|
|
$exceptSockets, |
|
386
|
|
|
$timeoutSeconds, |
|
387
|
|
|
$timeoutMilliseconds |
|
388
|
|
|
); |
|
389
|
|
|
|
|
390
|
|
|
if ($return === false) { |
|
391
|
|
|
throw new SocketException(); |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
$read = []; |
|
395
|
|
|
$write = []; |
|
396
|
|
|
$except = []; |
|
397
|
|
|
|
|
398
|
|
|
if ($readSockets) { |
|
399
|
|
|
$read = static::mapRawSocketToClass($readSockets); |
|
400
|
|
|
} |
|
401
|
|
|
if ($writeSockets) { |
|
402
|
|
|
$write = static::mapRawSocketToClass($writeSockets); |
|
403
|
|
|
} |
|
404
|
|
|
if ($exceptSockets) { |
|
405
|
|
|
$except = static::mapRawSocketToClass($exceptSockets); |
|
406
|
|
|
} |
|
407
|
|
|
|
|
408
|
|
|
return $return; |
|
409
|
|
|
} |
|
410
|
|
|
|
|
411
|
|
|
/** |
|
412
|
|
|
* Maps an array of {@see Socket}s to an array of socket resources. |
|
413
|
|
|
* |
|
414
|
|
|
* @param Socket[] $sockets |
|
415
|
|
|
* |
|
416
|
|
|
* @return resource[] |
|
417
|
|
|
*/ |
|
418
|
|
|
protected static function mapClassToRawSocket($sockets) |
|
419
|
|
|
{ |
|
420
|
|
|
return array_map(function (Socket $socket) { |
|
421
|
|
|
return $socket->resource; |
|
422
|
|
|
}, $sockets); |
|
423
|
|
|
} |
|
424
|
|
|
|
|
425
|
|
|
/** |
|
426
|
|
|
* Maps an array of socket resources to an array of {@see Socket}s. |
|
427
|
|
|
* |
|
428
|
|
|
* @param resource[] $sockets |
|
429
|
|
|
* |
|
430
|
|
|
* @return Socket[] |
|
431
|
|
|
*/ |
|
432
|
|
|
protected static function mapRawSocketToClass($sockets) |
|
433
|
|
|
{ |
|
434
|
|
|
return array_map(function ($rawSocket) { |
|
435
|
|
|
return self::$map[(string) $rawSocket]; |
|
436
|
|
|
}, $sockets); |
|
437
|
|
|
} |
|
438
|
|
|
|
|
439
|
|
|
/** |
|
440
|
|
|
* @param $buffer |
|
441
|
|
|
* @param int $length |
|
442
|
|
|
* |
|
443
|
|
|
* @throws Exception\SocketException |
|
444
|
|
|
* |
|
445
|
|
|
* @return int |
|
446
|
|
|
*/ |
|
447
|
1 |
View Code Duplication |
public function write($buffer, $length = null) |
|
448
|
|
|
{ |
|
449
|
1 |
|
if (null === $length) { |
|
450
|
|
|
$length = strlen($buffer); |
|
451
|
|
|
} |
|
452
|
|
|
|
|
453
|
|
|
// make sure everything is written |
|
454
|
|
|
do { |
|
455
|
1 |
|
$return = @socket_write($this->resource, $buffer, $length); |
|
456
|
|
|
|
|
457
|
1 |
|
if (false !== $return && $return < $length) { |
|
458
|
|
|
$buffer = substr($buffer, $return); |
|
459
|
|
|
$length -= $return; |
|
460
|
|
|
} else { |
|
461
|
1 |
|
break; |
|
462
|
|
|
} |
|
463
|
|
|
} while (true); |
|
464
|
|
|
|
|
465
|
1 |
|
if ($return === false) { |
|
466
|
1 |
|
throw new SocketException($this->resource); |
|
467
|
|
|
} |
|
468
|
|
|
|
|
469
|
|
|
return $return; |
|
470
|
|
|
} |
|
471
|
|
|
|
|
472
|
|
|
/** |
|
473
|
|
|
* Sends data to a connected socket. |
|
474
|
|
|
* |
|
475
|
|
|
* @param $buffer |
|
476
|
|
|
* @param int $flags |
|
477
|
|
|
* @param int $length |
|
478
|
|
|
* |
|
479
|
|
|
* @throws Exception\SocketException |
|
480
|
|
|
* |
|
481
|
|
|
* @return int |
|
482
|
|
|
*/ |
|
483
|
|
View Code Duplication |
public function send($buffer, $flags = 0, $length = null) |
|
484
|
|
|
{ |
|
485
|
|
|
if (null === $length) { |
|
486
|
|
|
$length = strlen($buffer); |
|
487
|
|
|
} |
|
488
|
|
|
|
|
489
|
|
|
// make sure everything is written |
|
490
|
|
|
do { |
|
491
|
|
|
$return = @socket_send($this->resource, $buffer, $length, $flags); |
|
492
|
|
|
|
|
493
|
|
|
if (false !== $return && $return < $length) { |
|
494
|
|
|
$buffer = substr($buffer, $return); |
|
495
|
|
|
$length -= $return; |
|
496
|
|
|
} else { |
|
497
|
|
|
break; |
|
498
|
|
|
} |
|
499
|
|
|
} while (true); |
|
500
|
|
|
|
|
501
|
|
|
if ($return === false) { |
|
502
|
|
|
throw new SocketException($this->resource); |
|
503
|
|
|
} |
|
504
|
|
|
|
|
505
|
|
|
return $return; |
|
506
|
|
|
} |
|
507
|
|
|
|
|
508
|
|
|
/** |
|
509
|
|
|
* Set the socket to blocking / non blocking. |
|
510
|
|
|
* |
|
511
|
|
|
* @param bool |
|
512
|
|
|
* |
|
513
|
|
|
* @return void |
|
514
|
|
|
*/ |
|
515
|
|
|
public function setBlocking($bool) |
|
516
|
|
|
{ |
|
517
|
|
|
if ($bool) { |
|
518
|
|
|
@socket_set_block($this->resource); |
|
519
|
|
|
} else { |
|
520
|
|
|
@socket_set_nonblock($this->resource); |
|
521
|
|
|
} |
|
522
|
|
|
} |
|
523
|
|
|
} |
|
524
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.