1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* \AppserverIo\Server\Sockets\StreamSocket |
5
|
|
|
* |
6
|
|
|
* NOTICE OF LICENSE |
7
|
|
|
* |
8
|
|
|
* This source file is subject to the Open Software License (OSL 3.0) |
9
|
|
|
* that is available through the world-wide-web at this URL: |
10
|
|
|
* http://opensource.org/licenses/osl-3.0.php |
11
|
|
|
* |
12
|
|
|
* PHP version 5 |
13
|
|
|
* |
14
|
|
|
* @author Johann Zelger <[email protected]> |
15
|
|
|
* @copyright 2015 TechDivision GmbH <[email protected]> |
16
|
|
|
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
17
|
|
|
* @link https://github.com/appserver-io/server |
18
|
|
|
* @link http://www.appserver.io |
19
|
|
|
*/ |
20
|
|
|
|
21
|
|
|
namespace AppserverIo\Server\Sockets; |
22
|
|
|
|
23
|
|
|
use AppserverIo\Psr\Socket\SocketInterface; |
24
|
|
|
use AppserverIo\Psr\Socket\SocketReadException; |
25
|
|
|
use AppserverIo\Psr\Socket\SocketReadTimeoutException; |
26
|
|
|
use AppserverIo\Psr\Socket\SocketServerException; |
27
|
|
|
use AppserverIo\Psr\Socket\AppserverIo\Psr\Socket; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Class StreamSocket |
31
|
|
|
* |
32
|
|
|
* @author Johann Zelger <[email protected]> |
33
|
|
|
* @copyright 2015 TechDivision GmbH <[email protected]> |
34
|
|
|
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
35
|
|
|
* @link https://github.com/appserver-io/server |
36
|
|
|
* @link http://www.appserver.io |
37
|
|
|
*/ |
38
|
|
|
class StreamSocket implements SocketInterface |
39
|
|
|
{ |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Holds the connection resource itself. |
43
|
|
|
* |
44
|
|
|
* @var resource |
45
|
|
|
*/ |
46
|
|
|
protected $connectionResource; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Holds the actual resource id |
50
|
|
|
* |
51
|
|
|
* @var int |
52
|
|
|
*/ |
53
|
|
|
protected $connectionResourceId; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Holds the peer name of the client who connected |
57
|
|
|
* |
58
|
|
|
* @var string |
59
|
|
|
*/ |
60
|
|
|
protected $connectionPeername; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Creates a stream socket server and returns a instance of Stream implementation with server socket in it. |
64
|
|
|
* |
65
|
|
|
* @param string $socket The address incl. transport the server should be listening to. For example 0.0.0.0:8080 |
66
|
|
|
* @param string $flags The flags to be set on server create |
67
|
|
|
* @param resource $context The context to be set on stream create |
68
|
|
|
* |
69
|
|
|
* @return \AppserverIo\Server\Sockets\StreamSocket The Stream instance with a server socket created. |
70
|
|
|
* |
71
|
|
|
* @throws \AppserverIo\Psr\Socket\SocketServerException |
72
|
|
|
*/ |
73
|
|
|
public static function getServerInstance($socket, $flags = null, $context = null) |
74
|
|
|
{ |
75
|
|
|
// init flags if none were given |
76
|
|
|
if (is_null($flags)) { |
77
|
|
|
$flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
// init context if none was given |
81
|
|
|
if (is_null($context)) { |
82
|
|
|
// set socket backlog to 1024 for perform many concurrent connections |
83
|
|
|
$opts = array( |
84
|
|
|
'socket' => array( |
85
|
|
|
'backlog' => 1024 |
86
|
|
|
), |
87
|
|
|
'ssl' => array( |
88
|
|
|
'crypto_method' => STREAM_CRYPTO_METHOD_TLS_SERVER |
89
|
|
|
) |
90
|
|
|
); |
91
|
|
|
// get default stream context for server connection with socket backlog preset |
92
|
|
|
$context = stream_context_get_default($opts); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
// create stream socket server resource |
96
|
|
|
$serverResource = stream_socket_server($socket, $errno, $errstr, $flags, $context); |
97
|
|
|
|
98
|
|
|
// throw exception if it was not possible to create server socket binding |
99
|
|
|
if (!$serverResource) { |
100
|
|
|
throw new SocketServerException($errstr, $errno); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
// set blocking mode |
104
|
|
|
stream_set_blocking($serverResource, 1); |
105
|
|
|
// create instance and return it. |
106
|
|
|
return self::getInstance($serverResource); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Creates a stream socket client and returns a instance of Stream implementation with client socket in it. |
111
|
|
|
* |
112
|
|
|
* @param string $socket The address incl. transport the server should be listening to. For example 0.0.0.0:8080 |
113
|
|
|
* @param string $flags The flags to be set on client create |
114
|
|
|
* @param resource $context The context to be set on stream create |
115
|
|
|
* |
116
|
|
|
* @return \AppserverIo\Server\Sockets\StreamSocket The Stream instance with a client socket created. |
117
|
|
|
* |
118
|
|
|
* @throws \AppserverIo\Psr\Socket\SocketServerException |
119
|
|
|
*/ |
120
|
|
|
public static function getClientInstance($socket, $flags = null, $context = null) |
121
|
|
|
{ |
122
|
|
|
// init flags if none were given |
123
|
|
|
if (is_null($flags)) { |
124
|
|
|
$flags = STREAM_CLIENT_CONNECT; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
// init context if none was given |
128
|
|
|
if (is_null($context)) { |
129
|
|
|
// create default stream context object |
130
|
|
|
$context = stream_context_get_default(); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
// create a stream socket client resource |
134
|
|
|
$clientResource = stream_socket_client($socket, $errno, $errstr, ini_get('default_socket_timeout'), $flags, $context); |
135
|
|
|
|
136
|
|
|
// throw exception if it was not possible to create server socket binding |
137
|
|
|
if (!$clientResource) { |
138
|
|
|
throw new SocketServerException($errstr, $errno); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
// set blocking mode |
142
|
|
|
stream_set_blocking($clientResource, 1); |
143
|
|
|
// create instance and return it |
144
|
|
|
return self::getInstance($clientResource); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Returns an instance of Stream with preset resource in it. |
149
|
|
|
* |
150
|
|
|
* @param resource $connectionResource The resource to use |
151
|
|
|
* |
152
|
|
|
* @return \AppserverIo\Server\Sockets\StreamSocket |
153
|
|
|
*/ |
154
|
|
|
public static function getInstance($connectionResource) |
155
|
|
|
{ |
156
|
|
|
$connection = new self(); |
157
|
|
|
$connection->setConnectionResource($connectionResource); |
158
|
|
|
return $connection; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Accepts connections from clients and build up a instance of Stream with connection resource in it. |
163
|
|
|
* |
164
|
|
|
* @param int $acceptTimeout The timeout in seconds to wait for accepting connections. |
165
|
|
|
* @param int $receiveTimeout The timeout in seconds to wait for read a line. |
166
|
|
|
* |
167
|
|
|
* @return \AppserverIo\Server\Sockets\StreamSocket|bool The Stream instance with the connection socket |
168
|
|
|
* accepted or bool false if timeout or error occurred. |
169
|
|
|
*/ |
170
|
|
|
public function accept($acceptTimeout = 600, $receiveTimeout = 60) |
171
|
|
|
{ |
172
|
|
|
// init local ref vars |
173
|
|
|
$peername = null; |
174
|
|
|
|
175
|
|
|
$connectionResource = @stream_socket_accept($this->getConnectionResource(), $acceptTimeout, $peername); |
176
|
|
|
// if timeout or error occurred return false as accept function does |
177
|
|
|
if ($connectionResource === false) { |
178
|
|
|
return false; |
179
|
|
|
} |
180
|
|
|
// set timeout for read data fom client |
181
|
|
|
stream_set_timeout($connectionResource, $receiveTimeout); |
182
|
|
|
$connection = $this->getInstance($connectionResource); |
183
|
|
|
$connection->setPeername($peername); |
184
|
|
|
return $connection; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Returns the line read from connection resource |
189
|
|
|
* |
190
|
|
|
* @param int $readLength The max length to read for a line. |
191
|
|
|
* @param int $receiveTimeout The max time to wait for read the next line |
192
|
|
|
* |
193
|
|
|
* @return string |
194
|
|
|
* @throws \AppserverIo\Psr\Socket\SocketReadTimeoutException |
195
|
|
|
*/ |
196
|
|
View Code Duplication |
public function readLine($readLength = 1024, $receiveTimeout = null) |
|
|
|
|
197
|
|
|
{ |
198
|
|
|
if ($receiveTimeout) { |
|
|
|
|
199
|
|
|
// set timeout for read data fom client |
200
|
|
|
@stream_set_timeout($this->getConnectionResource(), $receiveTimeout); |
|
|
|
|
201
|
|
|
} |
202
|
|
|
$line = @fgets($this->getConnectionResource(), $readLength); |
203
|
|
|
|
204
|
|
|
// check if read error occured |
205
|
|
|
if ($line === false) { |
206
|
|
|
throw new SocketReadException(); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
// check if timeout occurred |
210
|
|
|
if (strlen($line) === 0) { |
211
|
|
|
throw new SocketReadTimeoutException(); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
return $line; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Reads the given length from connection resource |
219
|
|
|
* |
220
|
|
|
* @param int $readLength The max length to read for a line. |
221
|
|
|
* @param int $receiveTimeout The max time to wait for read the next line |
222
|
|
|
* |
223
|
|
|
* @return string |
224
|
|
|
* |
225
|
|
|
* @throws \AppserverIo\Psr\Socket\SocketReadTimeoutException |
226
|
|
|
* @throws \AppserverIo\Psr\Socket\SocketReadException |
227
|
|
|
*/ |
228
|
|
View Code Duplication |
public function read($readLength = 1024, $receiveTimeout = null) |
|
|
|
|
229
|
|
|
{ |
230
|
|
|
if ($receiveTimeout) { |
|
|
|
|
231
|
|
|
// set timeout for read data fom client |
232
|
|
|
@stream_set_timeout($this->getConnectionResource(), $receiveTimeout); |
|
|
|
|
233
|
|
|
} |
234
|
|
|
// read in line from client |
235
|
|
|
$line = @fread($this->getConnectionResource(), $readLength); |
236
|
|
|
// check if false is response |
237
|
|
|
if (false === $line) { |
238
|
|
|
// throw new socket exception |
239
|
|
|
throw new SocketReadException(); |
240
|
|
|
} |
241
|
|
|
// check if timeout occurred |
242
|
|
|
if (strlen($line) === 0) { |
243
|
|
|
throw new SocketReadTimeoutException(); |
244
|
|
|
} |
245
|
|
|
return $line; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Writes the given message to the connection resource. |
250
|
|
|
* |
251
|
|
|
* @param string $message The message to write to the connection resource. |
252
|
|
|
* |
253
|
|
|
* @return int |
254
|
|
|
*/ |
255
|
|
|
public function write($message) |
256
|
|
|
{ |
257
|
|
|
return @fwrite($this->getConnectionResource(), $message, strlen($message)); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Receives data from a socket, connected or not. |
262
|
|
|
* |
263
|
|
|
* This method HAS to be used when working with an UDP socket. |
264
|
|
|
* |
265
|
|
|
* @param integer $length The number of bytes to receive from the |
266
|
|
|
* @param integer $flags The value of flags, can be STREAM_OOB or STREAM_PEEK |
267
|
|
|
* |
268
|
|
|
* @return string Returns the read data, as a string |
269
|
|
|
* @see http://docs.php.net/manual/de/function.stream-socket-recvfrom.php |
270
|
|
|
*/ |
271
|
|
|
public function receiveFrom($length = 512, $flags = null) |
272
|
|
|
{ |
273
|
|
|
|
274
|
|
|
// initialize the peername |
275
|
|
|
$peername = ''; |
276
|
|
|
|
277
|
|
|
// receive the data from the socket |
278
|
|
|
$buffer = stream_socket_recvfrom($this->getConnectionResource(), $length, $flags, $peername); |
279
|
|
|
|
280
|
|
|
// check if receiving the data has been succeeded |
281
|
|
|
if ($buffer === false) { |
282
|
|
|
// throw new socket exception |
283
|
|
|
throw new SocketReadException(); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
// set the peername |
287
|
|
|
$this->setPeername($peername); |
288
|
|
|
|
289
|
|
|
// return the received data |
290
|
|
|
return $buffer; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Sends a message to a socket, whether it is connected or not. |
295
|
|
|
* |
296
|
|
|
*This method HAS to be used when working with an UDP socket. |
297
|
|
|
* |
298
|
|
|
* @param string $message The data to be sent |
299
|
|
|
* @param integer $flags The value of flags can be STREAM_OOB |
300
|
|
|
* |
301
|
|
|
* @return integer Returns a result code, as an integer |
302
|
|
|
* @see http://docs.php.net/manual/de/function.stream-socket-sendto.php |
303
|
|
|
*/ |
304
|
|
|
public function sendTo($message, $flags = 0) |
|
|
|
|
305
|
|
|
{ |
306
|
|
|
return @stream_socket_sendto($this->getConnectionResource(), $message, 0, $this->getPeername()); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Copies data from a stream |
311
|
|
|
* |
312
|
|
|
* @param resource $sourceResource The source stream |
313
|
|
|
* |
314
|
|
|
* @return int The total count of bytes copied. |
315
|
|
|
*/ |
316
|
|
|
public function copyStream($sourceResource) |
317
|
|
|
{ |
318
|
|
|
// first try to rewind source resource stream |
319
|
|
|
@rewind($sourceResource); |
|
|
|
|
320
|
|
|
// call internal stream copy function |
321
|
|
|
return @stream_copy_to_stream($sourceResource, $this->getConnectionResource()); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Closes the connection resource |
326
|
|
|
* |
327
|
|
|
* @return bool |
328
|
|
|
*/ |
329
|
|
|
public function close() |
330
|
|
|
{ |
331
|
|
|
// check if resource still exists |
332
|
|
|
if (is_resource($this->getConnectionResource())) { |
333
|
|
|
return @fclose($this->getConnectionResource()); |
334
|
|
|
} |
335
|
|
|
return false; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Returns the stream socket's status |
340
|
|
|
* |
341
|
|
|
* @return bool|array The status information as array or false in case of an invalid stream socket resource |
342
|
|
|
*/ |
343
|
|
|
public function getStatus() |
344
|
|
|
{ |
345
|
|
|
return @socket_get_status($this->getConnectionResource()); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Returns the meta information of the stream socket |
350
|
|
|
* |
351
|
|
|
* @return bool|array The meta informations of the stream socket |
352
|
|
|
*/ |
353
|
|
|
public function getMetaInfo() |
354
|
|
|
{ |
355
|
|
|
return @stream_get_meta_data($this->getConnectionResource()); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Sets the connection resource |
360
|
|
|
* |
361
|
|
|
* @param resource $connectionResource The resource to socket file descriptor |
362
|
|
|
* |
363
|
|
|
* @return void |
364
|
|
|
*/ |
365
|
|
|
public function setConnectionResource($connectionResource) |
366
|
|
|
{ |
367
|
|
|
$this->connectionResourceId = (int)$connectionResource; |
368
|
|
|
$this->connectionResource = $connectionResource; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Sets the peer name |
373
|
|
|
* |
374
|
|
|
* @param string $peername The peername in format ip:port |
375
|
|
|
* |
376
|
|
|
* @return void |
377
|
|
|
*/ |
378
|
|
|
public function setPeername($peername) |
379
|
|
|
{ |
380
|
|
|
$this->connectionPeername = $peername; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Returns the peer name in format ip:port (e.g. 10.20.30.40:57128) |
385
|
|
|
* |
386
|
|
|
* @return string |
387
|
|
|
*/ |
388
|
|
|
public function getPeername() |
389
|
|
|
{ |
390
|
|
|
return $this->connectionPeername; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Returns the address of connection |
395
|
|
|
* |
396
|
|
|
* @return string |
397
|
|
|
*/ |
398
|
|
|
public function getAddress() |
399
|
|
|
{ |
400
|
|
|
return strstr($this->getPeername(), ':', true); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Returns the port of connection |
405
|
|
|
* |
406
|
|
|
* @return string |
407
|
|
|
*/ |
408
|
|
|
public function getPort() |
409
|
|
|
{ |
410
|
|
|
return str_replace(':', '', strstr($this->getPeername(), ':')); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Returns the connection resource |
415
|
|
|
* |
416
|
|
|
* @return mixed |
417
|
|
|
*/ |
418
|
|
|
public function getConnectionResource() |
419
|
|
|
{ |
420
|
|
|
return $this->connectionResource; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* Returns connection resource id |
425
|
|
|
* |
426
|
|
|
* @return int |
427
|
|
|
*/ |
428
|
|
|
public function getConnectionResourceId() |
429
|
|
|
{ |
430
|
|
|
return $this->connectionResourceId; |
431
|
|
|
} |
432
|
|
|
} |
433
|
|
|
|
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.