├── .gitattributes
├── .gitignore
├── .noninteractive
├── .scrutinizer.yml
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── example
├── bootstrap
│ └── autoload.php
├── socket_conn_tcp.php
├── socket_conn_tcp_ipv6.php
├── socket_conn_unix.php
├── socket_info.php
├── socket_only_client.php
├── socket_only_server.php
├── socket_quickstart.php
├── socket_ssl.php
├── socket_ssl_only_client.php
├── socket_ssl_only_server.php
└── ssl
│ ├── _ssl_cert.pem
│ └── _ssl_key.pem
├── phpunit.xml
├── src
└── Socket
│ ├── Socket.php
│ ├── SocketInterface.php
│ ├── SocketListener.php
│ └── SocketListenerInterface.php
└── test
├── Callback.php
├── TModule.php
├── TModule
├── SocketTest.php
└── _Data
│ ├── _ssl_cert.pem
│ └── _ssl_key.pem
├── TUnit.php
├── TUnit
├── SocketListenerTest.php
├── SocketTest.php
└── _Data
│ ├── _ssl_cert.pem
│ └── _ssl_key.pem
├── _Simulation
├── Event.php
├── EventCollection.php
├── Simulation.php
└── SimulationInterface.php
└── bootstrap.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor
3 | composer.lock
4 | composer.phar
--------------------------------------------------------------------------------
/.noninteractive:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dazzle-php/socket/7f896d8ebdabbd9cc158fca44a46a6a0a6f0b6e5/.noninteractive
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | excluded_paths:
3 | - 'build/*'
4 | - 'build-ci/*'
5 | - 'example/*'
6 | - 'example-bench/*'
7 | - 'test/*'
8 | - 'vendor/*'
9 |
10 | checks:
11 | php:
12 | return_doc_comments: true
13 | remove_extra_empty_lines: true
14 |
15 | coding_style:
16 | php:
17 | indentation:
18 | general:
19 | use_tabs: false
20 | size: 4
21 | switch:
22 | indent_case: true
23 | spaces:
24 | general:
25 | linefeed_character: newline
26 | before_parentheses:
27 | function_declaration: false
28 | closure_definition: false
29 | function_call: false
30 | if: true
31 | for: true
32 | while: true
33 | switch: true
34 | catch: true
35 | array_initializer: false
36 | around_operators:
37 | assignment: true
38 | logical: true
39 | equality: true
40 | relational: true
41 | bitwise: true
42 | additive: true
43 | multiplicative: true
44 | shift: true
45 | unary_additive: false
46 | concatenation: true
47 | negation: false
48 | before_left_brace:
49 | class: true
50 | function: true
51 | if: true
52 | else: true
53 | for: true
54 | while: true
55 | do: true
56 | switch: true
57 | try: true
58 | catch: true
59 | finally: true
60 | before_keywords:
61 | else: true
62 | while: true
63 | catch: true
64 | finally: true
65 | within:
66 | brackets: false
67 | array_initializer: false
68 | grouping: false
69 | function_call: false
70 | function_declaration: false
71 | if: false
72 | for: false
73 | while: false
74 | switch: false
75 | catch: false
76 | type_cast: false
77 | ternary_operator:
78 | before_condition: true
79 | after_condition: true
80 | before_alternative: true
81 | after_alternative: true
82 | in_short_version: false
83 | other:
84 | before_comma: false
85 | after_comma: true
86 | before_semicolon: false
87 | after_semicolon: true
88 | after_type_cast: true
89 | braces:
90 | classes_functions:
91 | class: new-line
92 | function: new-line
93 | closure: end-of-line
94 | if:
95 | opening: new-line
96 | always: false
97 | else_on_new_line: true
98 | for:
99 | opening: new-line
100 | always: true
101 | while:
102 | opening: new-line
103 | always: true
104 | do_while:
105 | opening: new-line
106 | always: true
107 | while_on_new_line: true
108 | switch:
109 | opening: new-line
110 | try:
111 | opening: new-line
112 | catch_on_new_line: true
113 | finally_on_new_line: true
114 | upper_lower_casing:
115 | keywords:
116 | general: lower
117 | constants:
118 | true_false_null: lower
119 |
120 | tools:
121 | external_code_coverage:
122 | timeout: 1800
123 | runs: 1
124 | php_code_coverage: false
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | dist: trusty
4 | sudo: required
5 |
6 | php:
7 | - 5.6
8 | - 7.0
9 | - 7.1
10 |
11 | before_install:
12 | - export PHP_MAJOR="$(echo $TRAVIS_PHP_VERSION | cut -d '.' -f 1,2)"
13 |
14 | install:
15 | - travis_retry composer self-update
16 | - travis_retry composer install --prefer-source --no-interaction
17 | - php -m
18 |
19 | script:
20 | - vendor/bin/phpunit -d memory_limit=1024M --coverage-text --coverage-clover=coverage.clover
21 |
22 | after_script:
23 | - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi
24 | - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | This changelog references the relevant changes, bug and security fixes done.
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and accepted via **Pull Requests** on [GitHub](https://github.com/dazzle-php/throwable).
4 |
5 | ## Pull Requests
6 |
7 | - **Naming convention** - all pull requests fixing a problem should match "Fix #issue Message" pattern, the new features and non-fix changes should match "Resolve #issue Message", the rest should contain only "Message".
8 | - **Follow our template of code** - all contributions have to follow [PSR-2 coding standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) with an exception of control structures, which have to have opening parenthesis always placed in the next line instead of the same.
9 | - **Add tests** - the contribution won't be accepted if it doesn't have tests.
10 | - **Document any change in behaviour** - make sure the `README.md` is kept up to date.
11 | - **Create feature branches** - don't create pull requests from your master branch.
12 | - **One pull request per feature** - for multiple things that you want to do, send also multiple pull requests.
13 | - **Keep coherent history** - make sure each individual commit in your pull request is meaningful. If you had to make multiple commits during development cycle, please squash them before submitting.
14 |
15 | ## Running Tests
16 |
17 | ```
18 | $> vendor/bin/phpunit
19 | ```
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015-2017 Kamil Jamróz
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dazzle Async Socket
2 |
3 | [](https://travis-ci.org/dazzle-php/socket)
4 | [](https://scrutinizer-ci.com/g/dazzle-php/socket/?branch=master)
5 | [](https://scrutinizer-ci.com/g/dazzle-php/socket/?branch=master)
6 | [](https://packagist.org/packages/dazzle-php/socket)
7 | [](https://packagist.org/packages/dazzle-php/socket)
8 | [](https://packagist.org/packages/dazzle-php/socket/license)
9 |
10 | > **Note:** This repository is part of [Dazzle Project](https://github.com/dazzle-php/dazzle) - the next-gen library for PHP. The project's purpose is to provide PHP developers with a set of complete tools to build functional async applications. Please, make sure you read the attached README carefully and it is guaranteed you will be surprised how easy to use and powerful it is. In the meantime, you might want to check out the rest of our async libraries in [Dazzle repository](https://github.com/dazzle-php) for the full extent of Dazzle experience.
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## Description
18 |
19 | Dazzle Socket is a component that implements asynchronous tcp, udp and unix socket handling for PHP. The library also provides interface for implementing self inter-process communication via external services.
20 |
21 | ## Feature Highlights
22 |
23 | Dazzle Socket features:
24 |
25 | * Asynchronous handling of incoming and outcoming messages,
26 | * Support for TCP, UDP and Unix sockets,
27 | * ...and more.
28 |
29 | ## Provided Example(s)
30 |
31 | ### Quickstart
32 |
33 | Server file which accepts the range in format of $min-$max and returns randomized number.
34 |
35 | ```php
36 | $loop = new Loop(new SelectLoop);
37 | $server = new SocketListener('tcp://127.0.0.1:2080', $loop);
38 |
39 | $server->on('connect', function($server, SocketInterface $client) {
40 | $client->write("Hello!\n");
41 | $client->write("Welcome to Dazzle server!\n");
42 | $client->write("Tell me a range and I will randomize a number for you!\n\n");
43 |
44 | $client->on('data', function(SocketInterface $client, $data) use(&$buffer) {
45 | $client->write("Your number is: " . rand(...explode('-', $data)));
46 | });
47 | });
48 |
49 | $loop->onStart(function() use($server) {
50 | $server->start();
51 | });
52 | $loop->start();
53 | ```
54 |
55 | Client file which sends the $min-$max format to the above server and gets the response.
56 |
57 | ```php
58 | $loop = new Loop(new SelectLoop);
59 | $socket = new Socket('tcp://127.0.0.1:2080', $loop);
60 |
61 | $socket->on('data', function($socket, $data) {
62 | printf("%s", $data);
63 | });
64 | $socket->write('1-100');
65 |
66 | $loop->start();
67 | ```
68 |
69 | ### Additional
70 |
71 | Additional examples can be found in [example](https://github.com/dazzle-php/socket/tree/master/example) directory. Below is the list of provided examples as a reference and preferred consumption order:
72 |
73 | - [Quickstart](https://github.com/dazzle-php/socket/blob/master/example/events_quickstart.php)
74 | - [Using socket client](https://github.com/dazzle-php/socket/blob/master/example/socket_only_client.php)
75 | - [Using socket server](https://github.com/dazzle-php/socket/blob/master/example/socket_only_server.php)
76 | - [Creating TCP IPv4 client-server connection](https://github.com/dazzle-php/socket/blob/master/example/socket_conn_tcp.php)
77 | - [Creating TCP IPv6 client-server connection](https://github.com/dazzle-php/socket/blob/master/example/socket_conn_tcp_ipv6.php)
78 | - [Creating UNIX socket client-server connection](https://github.com/dazzle-php/socket/blob/master/example/socket_conn_unix.php)
79 | - [Getting connection info](https://github.com/dazzle-php/socket/blob/master/example/socket_info.php)
80 | - [Using secure SSL socket client](https://github.com/dazzle-php/socket/blob/master/example/socket_ssl_only_client.php)
81 | - [Using secure SSL socket server](https://github.com/dazzle-php/socket/blob/master/example/socket_ssl_only_server.php)
82 | - [Creating secure SSL client-server connection](https://github.com/dazzle-php/socket/blob/master/example/socket_ssl.php)
83 |
84 | If any of the above examples has left you confused, please take a look in the [tests](https://github.com/dazzle-php/socket/tree/master/test) directory as well.
85 |
86 | ## Requirements
87 |
88 | Dazzle Socket requires:
89 |
90 | * PHP-5.6 or PHP-7.0+,
91 | * UNIX or Windows OS.
92 |
93 | ## Installation
94 |
95 | To install this library make sure you have [composer](https://getcomposer.org/) installed, then run following command:
96 |
97 | ```
98 | $> composer require dazzle-php/socket
99 | ```
100 |
101 | ## Tests
102 |
103 | Tests can be run via:
104 |
105 | ```
106 | $> vendor/bin/phpunit -d memory_limit=1024M
107 | ```
108 |
109 | ## Versioning
110 |
111 | Versioning of Dazzle libraries is being shared between all packages included in [Dazzle Project](https://github.com/dazzle-php/dazzle). That means the releases are being made concurrently for all of them. On one hand this might lead to "empty" releases for some packages at times, but don't worry. In the end it is far much easier for contributors to maintain and -- what's the most important -- much more straight-forward for users to understand the compatibility and inter-operability of the packages.
112 |
113 | ## Contributing
114 |
115 | Thank you for considering contributing to this repository!
116 |
117 | - The contribution guide can be found in the [contribution tips](https://github.com/dazzle-php/socket/blob/master/CONTRIBUTING.md).
118 | - Open tickets can be found in [issues section](https://github.com/dazzle-php/socket/issues).
119 | - Current contributors are listed in [graphs section](https://github.com/dazzle-php/socket/graphs/contributors)
120 | - To contact the author(s) see the information attached in [composer.json](https://github.com/dazzle-php/socket/blob/master/composer.json) file.
121 |
122 | ## License
123 |
124 | Dazzle Socket is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).
125 |
126 |
127 |
128 | "Everything is possible. The impossible just takes longer." ― Dan Brown
129 |
130 |
131 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dazzle-php/socket",
3 | "description": "Dazzle Asynchronous Socket.",
4 | "keywords": [
5 | "dazzle", "dazzle-php", "ipc", "inter", "inter-process", "message-driven", "sock", "socket", "tcp", "udp", "unix"
6 | ],
7 | "license": "MIT",
8 | "support": {
9 | "issues": "https://github.com/dazzle-php/socket/issues",
10 | "source": "https://github.com/dazzle-php/socket"
11 | },
12 | "authors": [
13 | {
14 | "name": "Kamil Jamroz",
15 | "homepage": "https://github.com/khelle"
16 | },
17 | {
18 | "name": "The contributors",
19 | "homepage": "http://github.com/dazzle-php/socket/contributors"
20 | }
21 | ],
22 | "require": {
23 | "php": ">=5.6.7",
24 | "dazzle-php/event": "0.5.*",
25 | "dazzle-php/loop": "0.5.*",
26 | "dazzle-php/stream": "0.5.*",
27 | "dazzle-php/throwable": "0.5.*"
28 | },
29 | "require-dev": {
30 | "phpunit/phpunit": ">=4.8.0 <5.4.0"
31 | },
32 | "autoload": {
33 | "psr-4": {
34 | "Dazzle\\Socket\\": "src/Socket",
35 | "Dazzle\\Socket\\Test\\": "test"
36 | }
37 | },
38 | "extra": {
39 | "branch-alias": {
40 | "dev-master": "0.5-dev"
41 | }
42 | },
43 | "minimum-stability": "dev",
44 | "prefer-stable": true
45 | }
46 |
--------------------------------------------------------------------------------
/example/bootstrap/autoload.php:
--------------------------------------------------------------------------------
1 | 'client',
7 | ];
8 |
9 | foreach ($argv as $argIndex=>$argVal)
10 | {
11 | if ($argIndex && preg_match('#^--([^=]+)=(.+)$#si', $argVal, $matches) && $matches)
12 | {
13 | $options[$matches[1]] = $matches[2];
14 | }
15 | }
16 |
17 | foreach ($options as $optionKey=>$optionVal)
18 | {
19 | $$optionKey = strtolower($optionVal);
20 | }
21 |
--------------------------------------------------------------------------------
/example/socket_conn_tcp.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_conn_tcp.php
15 | *
16 | * Following flags are supported to test example:
17 | *
18 | * --mode : define whether socket or client should be created, default: standard, supported: [ client, server ]
19 | *
20 | * Remember that to see working communication, you need to create server and at least one client!
21 | *
22 | * $> php ./example/socket_conn_tcp.php --mode=server
23 | * $> php ./example/socket_conn_tcp.php --mode=client
24 | *
25 | * ---------------------------------------------------------------------------------------------------------------------
26 | */
27 |
28 | $mode = 'client';
29 |
30 | require_once __DIR__ . '/bootstrap/autoload.php';
31 |
32 | use Dazzle\Loop\Model\SelectLoop;
33 | use Dazzle\Loop\Loop;
34 | use Dazzle\Socket\Socket;
35 | use Dazzle\Socket\SocketInterface;
36 | use Dazzle\Socket\SocketListener;
37 |
38 | $loop = new Loop(new SelectLoop);
39 |
40 | if ($mode === 'server')
41 | {
42 | $server = new SocketListener('tcp://127.0.0.1:2080', $loop);
43 |
44 | $server->on('connect', function($server, SocketInterface $client) {
45 | printf("New connection #%s from %s!\n", $res = $client->getResourceId(), $client->getLocalAddress());
46 |
47 | $client->on('data', function($client, $data) use(&$buffer) {
48 | printf("Received message=\"%s\"\n", $data);
49 | });
50 | $client->on('close', function() use($res) {
51 | printf("Closed connection #$res\n");
52 | });
53 | });
54 | $server->start();
55 | }
56 |
57 | if ($mode === 'client')
58 | {
59 | $socket = new Socket('tcp://127.0.0.1:2080', $loop);
60 | $socket->on('close', function() use($loop) {
61 | printf("Server has closed the connection!\n");
62 | $loop->stop();
63 | });
64 |
65 | $loop->addPeriodicTimer(1, function() use($socket) {
66 | $socket->write('Hello World!');
67 | });
68 | }
69 |
70 | $loop->start();
71 |
--------------------------------------------------------------------------------
/example/socket_conn_tcp_ipv6.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_conn_tcp_ipv6.php
15 | *
16 | * Following flags are supported to test example:
17 | *
18 | * --mode : define whether socket or client should be created, default: standard, supported: [ client, server ]
19 | *
20 | * Remember that to see working communication, you need to create server and at least one client!
21 | *
22 | * $> php ./example/socket_conn_tcp_ipv6.php --mode=server
23 | * $> php ./example/socket_conn_tcp_ipv6.php --mode=client
24 | *
25 | * ---------------------------------------------------------------------------------------------------------------------
26 | */
27 |
28 | $mode = 'client';
29 |
30 | require_once __DIR__ . '/bootstrap/autoload.php';
31 |
32 | use Dazzle\Loop\Model\SelectLoop;
33 | use Dazzle\Loop\Loop;
34 | use Dazzle\Socket\Socket;
35 | use Dazzle\Socket\SocketInterface;
36 | use Dazzle\Socket\SocketListener;
37 |
38 | $loop = new Loop(new SelectLoop);
39 |
40 | if ($mode === 'server')
41 | {
42 | $server = new SocketListener('tcp://[::1]:2080', $loop);
43 |
44 | $server->on('connect', function($server, SocketInterface $client) {
45 | printf("New connection #%s from %s!\n", $res = $client->getResourceId(), $client->getLocalAddress());
46 |
47 | $client->on('data', function($client, $data) use(&$buffer) {
48 | printf("Received message=\"%s\"\n", $data);
49 | });
50 | $client->on('close', function() use($res) {
51 | printf("Closed connection #$res\n");
52 | });
53 | });
54 | $server->start();
55 | }
56 |
57 | if ($mode === 'client')
58 | {
59 | $socket = new Socket('tcp://[::1]:2080', $loop);
60 | $socket->on('close', function() use($loop) {
61 | printf("Server has closed the connection!\n");
62 | $loop->stop();
63 | });
64 |
65 | $loop->addPeriodicTimer(1, function() use($socket) {
66 | $socket->write('Hello World!');
67 | });
68 | }
69 |
70 | $loop->start();
71 |
--------------------------------------------------------------------------------
/example/socket_conn_unix.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_conn_unix.php
16 | *
17 | * Following flags are supported to test example:
18 | *
19 | * --mode : define whether socket or client should be created, default: standard, supported: [ client, server ]
20 | *
21 | * Remember that to see working communication, you need to create server and at least one client!
22 | *
23 | * $> php ./example/socket_conn_unix.php --mode=server
24 | * $> php ./example/socket_conn_unix.php --mode=client
25 | *
26 | * ---------------------------------------------------------------------------------------------------------------------
27 | */
28 |
29 | $mode = 'client';
30 |
31 | require_once __DIR__ . '/bootstrap/autoload.php';
32 |
33 | use Dazzle\Loop\Model\SelectLoop;
34 | use Dazzle\Loop\Loop;
35 | use Dazzle\Socket\Socket;
36 | use Dazzle\Socket\SocketInterface;
37 | use Dazzle\Socket\SocketListener;
38 |
39 | $loop = new Loop(new SelectLoop);
40 |
41 | if ($mode === 'server')
42 | {
43 | $server = new SocketListener('unix://' . __DIR__ . '/mysock.sock', $loop);
44 |
45 | $server->on('connect', function($server, SocketInterface $client) {
46 | printf("New connection #%s from %s!\n", $res = $client->getResourceId(), $client->getLocalAddress());
47 |
48 | $client->on('data', function($client, $data) use(&$buffer) {
49 | printf("Received message=\"%s\"\n", $data);
50 | });
51 | $client->on('close', function() use($res) {
52 | printf("Closed connection #$res\n");
53 | });
54 | });
55 | $server->start();
56 | }
57 |
58 | if ($mode === 'client')
59 | {
60 | $socket = new Socket('unix://' . __DIR__ . '/mysock.sock', $loop);
61 | $socket->on('close', function() use($loop) {
62 | printf("Server has closed the connection!\n");
63 | $loop->stop();
64 | });
65 |
66 | $loop->addPeriodicTimer(1, function() use($socket) {
67 | $socket->write('Hello World!');
68 | });
69 | }
70 |
71 | $loop->start();
72 |
--------------------------------------------------------------------------------
/example/socket_info.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_info.php
16 | *
17 | * Following flags are supported to test example:
18 | *
19 | * --mode : define whether socket or client should be created, default: standard, supported: [ client, server ]
20 | *
21 | * Remember that to see working communication, you need to create server and at least one client!
22 | *
23 | * $> php ./example/socket_info.php --mode=server
24 | * $> php ./example/socket_info.php --mode=client
25 | *
26 | * ---------------------------------------------------------------------------------------------------------------------
27 | */
28 |
29 | $mode = 'client';
30 |
31 | require_once __DIR__ . '/bootstrap/autoload.php';
32 |
33 | use Dazzle\Loop\Model\SelectLoop;
34 | use Dazzle\Loop\Loop;
35 | use Dazzle\Socket\Socket;
36 | use Dazzle\Socket\SocketInterface;
37 | use Dazzle\Socket\SocketListener;
38 |
39 | $loop = new Loop(new SelectLoop);
40 |
41 | if ($mode === 'server')
42 | {
43 | $server = new SocketListener('tcp://127.0.0.1:2080', $loop);
44 |
45 | $server->on('connect', function($server, SocketInterface $client) {
46 |
47 | printf("%s\n", str_repeat('-', 42));
48 | printf("%s\n", 'Client info:');
49 | printf("%s\n", str_repeat('-', 42));
50 | printf("%-20s%s\n", 'Resource ID:', '#' . $client->getResourceId());
51 | printf("%-20s%s\n", 'Local endpoint:', $client->getLocalEndpoint());
52 | printf("%-20s%s\n", 'Local protocol:', $client->getLocalProtocol());
53 | printf("%-20s%s\n", 'Local address:', $client->getLocalAddress());
54 | printf("%-20s%s\n", 'Local host:', $client->getLocalHost());
55 | printf("%-20s%s\n", 'Local port:', $client->getLocalPort());
56 | printf("%-20s%s\n", 'Remote endpoint:', $client->getRemoteEndpoint());
57 | printf("%-20s%s\n", 'Remote protocol:', $client->getRemoteProtocol());
58 | printf("%-20s%s\n", 'Remote address:', $client->getRemoteAddress());
59 | printf("%-20s%s\n", 'Remote host:', $client->getRemoteHost());
60 | printf("%-20s%s\n", 'Remote port:', $client->getRemotePort());
61 | printf("%s\n", str_repeat('-', 42));
62 |
63 | $client->close();
64 | });
65 | $server->start();
66 | }
67 |
68 | if ($mode === 'client')
69 | {
70 | $socket = new Socket('tcp://127.0.0.1:2080', $loop);
71 | $socket->on('close', function() use($loop) {
72 | $loop->stop();
73 | });
74 | }
75 |
76 | $loop->start();
77 |
--------------------------------------------------------------------------------
/example/socket_only_client.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_only_client.php
15 | *
16 | * ---------------------------------------------------------------------------------------------------------------------
17 | */
18 |
19 | require_once __DIR__ . '/bootstrap/autoload.php';
20 |
21 | use Dazzle\Loop\Model\SelectLoop;
22 | use Dazzle\Loop\Loop;
23 | use Dazzle\Socket\Socket;
24 | use Dazzle\Socket\SocketInterface;
25 |
26 | $loop = new Loop(new SelectLoop);
27 |
28 | $socket = new Socket('tcp://127.0.0.1:2080', $loop);
29 |
30 | $socket->on('data', function(SocketInterface $client, $data) use(&$buffer) {
31 | printf("%s\n", $data);
32 | $client->close();
33 | });
34 | $socket->on('close', function() use($loop) {
35 | $loop->stop();
36 | });
37 |
38 | $loop->onStart(function() use($socket) {
39 | $socket->write('Hello!');
40 | });
41 |
42 | $loop->start();
43 |
--------------------------------------------------------------------------------
/example/socket_only_server.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_only_server.php
15 | *
16 | * ---------------------------------------------------------------------------------------------------------------------
17 | */
18 |
19 | require_once __DIR__ . '/bootstrap/autoload.php';
20 |
21 | use Dazzle\Loop\Model\SelectLoop;
22 | use Dazzle\Loop\Loop;
23 | use Dazzle\Socket\SocketInterface;
24 | use Dazzle\Socket\SocketListener;
25 |
26 | $loop = new Loop(new SelectLoop);
27 |
28 | $server = new SocketListener('tcp://127.0.0.1:2080', $loop);
29 |
30 | $server->on('connect', function($server, SocketInterface $client) {
31 | printf("New secure connection #%s from %s!\n", $res = $client->getResourceId(), $client->getLocalAddress());
32 |
33 | $client->on('data', function(SocketInterface $client, $data) use(&$buffer) {
34 | printf("%s\n", $data);
35 | $client->write('secret answer!');
36 | });
37 | $client->on('close', function() use($res) {
38 | printf("Closed connection #$res\n");
39 | });
40 | });
41 | $server->start();
42 |
43 | $loop->start();
44 |
--------------------------------------------------------------------------------
/example/socket_quickstart.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_quickstart.php
15 | *
16 | * Following flags are supported to test example:
17 | *
18 | * --mode : define whether socket or client should be created, default: standard, supported: [ client, server ]
19 | *
20 | * Remember that to see working communication, you need to create server and at least one client!
21 | *
22 | * $> php ./example/socket_quickstart.php --mode=server
23 | * $> php ./example/socket_quickstart.php --mode=client
24 | *
25 | * ---------------------------------------------------------------------------------------------------------------------
26 | */
27 |
28 | $mode = 'client';
29 |
30 | require_once __DIR__ . '/bootstrap/autoload.php';
31 |
32 | use Dazzle\Loop\Model\SelectLoop;
33 | use Dazzle\Loop\Loop;
34 | use Dazzle\Socket\Socket;
35 | use Dazzle\Socket\SocketInterface;
36 | use Dazzle\Socket\SocketListener;
37 |
38 | $loop = new Loop(new SelectLoop);
39 |
40 | if ($mode === 'server')
41 | {
42 | $server = new SocketListener('tcp://127.0.0.1:2080', $loop);
43 |
44 | $server->on('connect', function($server, SocketInterface $client) {
45 | $client->write("Hello!\n");
46 | $client->write("Welcome to Dazzle server!\n");
47 | $client->write("Tell me a range and I will randomize a number for you!\n\n");
48 |
49 | $client->on('data', function(SocketInterface $client, $data) use(&$buffer) {
50 | $client->write("Your number is: " . rand(...explode('-', $data)));
51 | });
52 | });
53 | $server->start();
54 | }
55 |
56 | if ($mode === 'client')
57 | {
58 | $socket = new Socket('tcp://127.0.0.1:2080', $loop);
59 | $socket->on('data', function($socket, $data) {
60 | printf("%s", $data);
61 | });
62 | $socket->write('1-100');
63 | }
64 |
65 | $loop->start();
66 |
--------------------------------------------------------------------------------
/example/socket_ssl.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_ssl.php
16 | *
17 | * Following flags are supported to test example:
18 | *
19 | * --mode : define whether socket or client should be created, default: standard, supported: [ client, server ]
20 | *
21 | * Remember that to see working communication, you need to create server and at least one client!
22 | *
23 | * $> php ./example/socket_ssl.php --mode=server
24 | * $> php ./example/socket_ssl.php --mode=client
25 | *
26 | * ---------------------------------------------------------------------------------------------------------------------
27 | */
28 |
29 | $mode = 'client';
30 |
31 | require_once __DIR__ . '/bootstrap/autoload.php';
32 |
33 | use Dazzle\Loop\Model\SelectLoop;
34 | use Dazzle\Loop\Loop;
35 | use Dazzle\Socket\Socket;
36 | use Dazzle\Socket\SocketInterface;
37 | use Dazzle\Socket\SocketListener;
38 |
39 | $loop = new Loop(new SelectLoop);
40 |
41 | if ($mode === 'server')
42 | {
43 | // additional supported configuration options:
44 | // ssl_method
45 | // ssl_name
46 | // ssl_verify_sign
47 | // ssl_verify_peer
48 | // ssl_verify_depth
49 | $config = [
50 | 'ssl' => true,
51 | 'ssl_cert' => __DIR__ . '/ssl/_ssl_cert.pem',
52 | 'ssl_key' => __DIR__ . '/ssl/_ssl_key.pem',
53 | 'ssl_secret' => 'secret',
54 | ];
55 | $server = new SocketListener('tcp://127.0.0.1:2080', $loop, $config);
56 |
57 | $server->on('connect', function($server, SocketInterface $client) {
58 | printf("New secure connection #%s from %s!\n", $res = $client->getResourceId(), $client->getLocalAddress());
59 |
60 | $client->on('data', function(SocketInterface $client, $data) use(&$buffer) {
61 | printf("Received question=\"%s\"\n", $data);
62 | $client->write('secret answer!');
63 | });
64 | $client->on('close', function() use($res) {
65 | printf("Closed connection #$res\n");
66 | });
67 | });
68 | $server->start();
69 | }
70 |
71 | if ($mode === 'client')
72 | {
73 | // additional supported configuration options:
74 | // ssl_method
75 | // ssl_name
76 | // ssl_verify_sign
77 | // ssl_verify_peer
78 | // ssl_verify_depth
79 | $config = [
80 | 'ssl' => true
81 | ];
82 | $socket = new Socket('tcp://127.0.0.1:2080', $loop, $config);
83 |
84 | $socket->on('data', function(SocketInterface $client, $data) use(&$buffer) {
85 | printf("Received answer=\"%s\"\n", $data);
86 | });
87 | $socket->on('close', function() use($loop) {
88 | printf("Server has closed the connection!\n");
89 | $loop->stop();
90 | });
91 |
92 | $loop->addPeriodicTimer(1, function() use($socket) {
93 | $socket->write('secret question!');
94 | });
95 | }
96 |
97 | $loop->start();
98 |
--------------------------------------------------------------------------------
/example/socket_ssl_only_client.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_ssl_only_client.php
15 | *
16 | * ---------------------------------------------------------------------------------------------------------------------
17 | */
18 |
19 | require_once __DIR__ . '/bootstrap/autoload.php';
20 |
21 | use Dazzle\Loop\Model\SelectLoop;
22 | use Dazzle\Loop\Loop;
23 | use Dazzle\Socket\Socket;
24 | use Dazzle\Socket\SocketInterface;
25 |
26 | $loop = new Loop(new SelectLoop);
27 |
28 | $config = [
29 | 'ssl' => true
30 | ];
31 | $socket = new Socket('tcp://127.0.0.1:2080', $loop, $config);
32 |
33 | $socket->on('data', function(SocketInterface $client, $data) use(&$buffer) {
34 | printf("%s\n", $data);
35 | $client->close();
36 | });
37 | $socket->on('close', function() use($loop) {
38 | $loop->stop();
39 | });
40 |
41 | $loop->onStart(function() use($socket) {
42 | $socket->write('Hello!');
43 | });
44 |
45 | $loop->start();
46 |
--------------------------------------------------------------------------------
/example/socket_ssl_only_server.php:
--------------------------------------------------------------------------------
1 | php ./example/socket_ssl_only_server.php
15 | *
16 | * ---------------------------------------------------------------------------------------------------------------------
17 | */
18 |
19 | require_once __DIR__ . '/bootstrap/autoload.php';
20 |
21 | use Dazzle\Loop\Model\SelectLoop;
22 | use Dazzle\Loop\Loop;
23 | use Dazzle\Socket\SocketInterface;
24 | use Dazzle\Socket\SocketListener;
25 |
26 | $loop = new Loop(new SelectLoop);
27 |
28 | // additional supported configuration options:
29 | // ssl_method
30 | // ssl_name
31 | // ssl_verify_sign
32 | // ssl_verify_peer
33 | // ssl_verify_depth
34 | $config = [
35 | 'ssl' => true,
36 | 'ssl_cert' => __DIR__ . '/ssl/_ssl_cert.pem',
37 | 'ssl_key' => __DIR__ . '/ssl/_ssl_key.pem',
38 | 'ssl_secret' => 'secret',
39 | ];
40 | $server = new SocketListener('tcp://127.0.0.1:2080', $loop, $config);
41 |
42 | $server->on('connect', function($server, SocketInterface $client) {
43 | printf("New secure connection #%s from %s!\n", $res = $client->getResourceId(), $client->getLocalAddress());
44 |
45 | $client->on('data', function(SocketInterface $client, $data) use(&$buffer) {
46 | printf("%s\n", $data);
47 | $client->write('secret answer!');
48 | });
49 | $client->on('close', function() use($res) {
50 | printf("Closed connection #$res\n");
51 | });
52 | });
53 | $server->start();
54 |
55 | $loop->start();
56 |
--------------------------------------------------------------------------------
/example/ssl/_ssl_cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC9jCCAd4CCQC1n9FarkDiuTANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQGEwJD
3 | TjELMAkGA1UECAwCU0gxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
4 | ZDAeFw0xNzAxMjcwMjI0MjNaFw0xODAxMjcwMjI0MjNaMD0xCzAJBgNVBAYTAkNO
5 | MQswCQYDVQQIDAJTSDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
6 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6P2tbxw2JBEr/jI6dyU/
7 | ppopZ3XbUybS4A3V2G/k5wXocjPOx2ttb4J3cClKYzyyG96AFRftmkjps1ixxknO
8 | ZlXWVJ+Cdgw3RXn0kt9s8zjYtGKFnDBTyi27KMVjpdGiOl9iJA71FgWeRuUOzFUf
9 | poAVkeohGwVQQKjiBANZo4Civwae71LJ0HwN+JtX/l8Rcqrlt5DYAAix13ZzF9EY
10 | OQQTb18UVEdKyVM8RQbpnE3DZ0tdB7WAiUBOd3H9DICPSCytSSVfOFYyWXzM66CO
11 | q2rI3CN4mNYBybNrfjby5FK8Qgnu8boVLPlpq70j8SnAik1drfGVdf0W2UOvxWcD
12 | +wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBWSUEWA161OYo9XM6PVqP++RQyIq7d
13 | YFgdwgxjZTUINuOlBQnQEPbCskeCEaxEK/APkyGfV8M+b8NzLgXSJJAXjI6tfBmR
14 | CMLXk/cTGImAd1c6KQ3wCPQqbn8bxCaC1ca9MrJPqB4EOjIiVw+zBNXS6n3kDqkD
15 | a6N3DfBXZycY6tfOSnbej6q2/hPzgqP2VquNvsiloRpay5i+yfcx3rbh/bJVHsb7
16 | 1YoqYZU/Kn88Lt2U8Qhwkag2qAvSd7uGAldxNkzgbAXZ3QneoOKFcWCvGLbS3XjN
17 | mMWkYg8EHsks6PzLRtQesHKiq8t92JdZsxvasMXMNAs9GHDQ88En8iUL
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/example/ssl/_ssl_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEA6P2tbxw2JBEr/jI6dyU/ppopZ3XbUybS4A3V2G/k5wXocjPO
3 | x2ttb4J3cClKYzyyG96AFRftmkjps1ixxknOZlXWVJ+Cdgw3RXn0kt9s8zjYtGKF
4 | nDBTyi27KMVjpdGiOl9iJA71FgWeRuUOzFUfpoAVkeohGwVQQKjiBANZo4Civwae
5 | 71LJ0HwN+JtX/l8Rcqrlt5DYAAix13ZzF9EYOQQTb18UVEdKyVM8RQbpnE3DZ0td
6 | B7WAiUBOd3H9DICPSCytSSVfOFYyWXzM66COq2rI3CN4mNYBybNrfjby5FK8Qgnu
7 | 8boVLPlpq70j8SnAik1drfGVdf0W2UOvxWcD+wIDAQABAoIBAGr6BwbsMiOvSr7F
8 | Ozj3flk1kWsynwh32/0J/TUKc1RrzwWYqgnAVpgLlGGIY+JavQC00aRdvzfG4J77
9 | S2za+IzePrHOJ22iQEKDM3u4YGLwp/dzARvsyJwdEUEwGIfLUCxzHTGnOHrrC3hL
10 | tda2AvlE+CJuGNM6RlXZ9SSYkTDTgBORkuAJf4pTZrG5wVedTGR0us1lj4BKzuDF
11 | MsApnBum8Dpj73nZCcfT9MMgbuXGp1jsDuE7eZsiDzklarroZxOcYX9YMTpsG2iY
12 | f2drGWSVssZKwmxaTiAHi4iMIhawxQwE13PxK5FqP1DQjQYmPjoDY5CQ1/OD6oGJ
13 | klRpEykCgYEA+XDlDHRP3UK1UpZJY6heudRgdbuEdd4vFbEVG6Kk1vh3ub6Y33UJ
14 | 1nvHIvjzCOEnjO5wZbBpoJy1O2fCJt/rdP1nWI2SLXtBIv4floia1SK5S1fmrr4C
15 | 1uDAB3+/PAJe5gSBX6l9dt1JesB6ERrrVYU05GCnW64T+kDg19IZaUUCgYEA7x4M
16 | sOGXjdunmk5DmgaAZcKKUXm6G5+DGHCiHzylUr3LsJ4dqBMFYDfA0s2YNlT+pKpK
17 | BEDBxfzlx1ewaorr2Hge3QAHeGWa4oQok42u7rv1ernl/RYOfI3JcZaX89cu482b
18 | /tFUjeMDfxqiD74hl2JtK3vkBTfldApPS10ZbD8CgYEA4S1S+C0/s4P23/kfg0GJ
19 | UcQw19dRun26j6BuZ6YpnI5stlci9gZXvTG8EQwfiZKH+6d7+7CHiiAtUtVV5XYa
20 | vl/LnZsfrMMpiAdUSLZE38ca8rMFYhXRxlzZvWtLHUcemBLVH6CHzHfzT0bWav6c
21 | F3XKy6edLRw2mKFc2DeoDOkCgYEAndDhY2Sg2Bme9rKs311nJMJO9BM5B74xkdHU
22 | znUCUCe/5eOVgzZ6l9R0SS00Rre2EQvrKf9rZLbTGMwBPXBO1GIDTK0WQRoeLV44
23 | QZqDWEFpdQR4jJ8gFIin6XYQ9/iPk/5B9N5HxhWMWzlCuM0t8nIdx1NPXTpVEE81
24 | onHhs5UCgYA6y5LpvJ5vOpZyHdTISgQK+4SmhUSu3e29qE8AjixaTBFwGa/PKuVO
25 | ggpceNZjl+pg1F74IMEpg1c6N1MKEIhK+NEYhxS9QkShPNYwasfkhg87QfkOxUi+
26 | jed5G+yO01Re0bkyE6YiS+JrJQZJEdna/x9eb5IxF/LiTWkZhkdywg==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 | test/TModule
21 |
22 |
23 |
24 | test/TUnit
25 |
26 |
27 |
28 |
29 |
30 | src
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/Socket/Socket.php:
--------------------------------------------------------------------------------
1 | configure($config);
111 |
112 | if (!is_resource($endpointOrResource))
113 | {
114 | $endpointOrResource = $this->createClient($endpointOrResource, $config);
115 | }
116 |
117 | parent::__construct($endpointOrResource, $loop);
118 |
119 | $this->read();
120 | }
121 | catch (Error $ex)
122 | {
123 | throw new InstantiationException('SocketClient could not be created.', 0, $ex);
124 | }
125 | catch (Exception $ex)
126 | {
127 | throw new InstantiationException('SocketClient could not be created.', 0, $ex);
128 | }
129 | }
130 |
131 | /**
132 | * @override
133 | * @inheritDoc
134 | */
135 | public function stop()
136 | {
137 | $this->close();
138 | }
139 |
140 | /**
141 | * @override
142 | * @inheritDoc
143 | */
144 | public function getLocalEndpoint()
145 | {
146 | return $this->parseEndpoint(false);
147 | }
148 |
149 | /**
150 | * @override
151 | * @inheritDoc
152 | */
153 | public function getRemoteEndpoint()
154 | {
155 | return $this->parseEndpoint(true);
156 | }
157 |
158 | /**
159 | * @override
160 | * @inheritDoc
161 | */
162 | public function getLocalAddress()
163 | {
164 | $endpoint = explode('://', $this->getLocalEndpoint(), 2);
165 |
166 | return isset($endpoint[1]) ? $endpoint[1] : $endpoint[0];
167 | }
168 |
169 | /**
170 | * @override
171 | * @inheritDoc
172 | */
173 | public function getLocalHost()
174 | {
175 | $address = explode(':', $this->getLocalAddress(), 2);
176 |
177 | return $address[0];
178 | }
179 |
180 | /**
181 | * @override
182 | * @inheritDoc
183 | */
184 | public function getLocalPort()
185 | {
186 | $address = explode(':', $this->getLocalAddress(), 2);
187 |
188 | return isset($address[1]) ? $address[1] : '';
189 | }
190 |
191 | /**
192 | * @override
193 | * @inheritDoc
194 | */
195 | public function getLocalProtocol()
196 | {
197 | $endpoint = explode('://', $this->getLocalEndpoint(), 2);
198 |
199 | return isset($endpoint[0]) ? $endpoint[0] : '';
200 | }
201 |
202 | /**
203 | * @override
204 | * @inheritDoc
205 | */
206 | public function getRemoteAddress()
207 | {
208 | $endpoint = explode('://', $this->getRemoteEndpoint(), 2);
209 |
210 | return isset($endpoint[1]) ? $endpoint[1] : $endpoint[0];
211 | }
212 |
213 | /**
214 | * @override
215 | * @inheritDoc
216 | */
217 | public function getRemoteHost()
218 | {
219 | $address = explode(':', $this->getRemoteAddress(), 2);
220 |
221 | return $address[0];
222 | }
223 |
224 | /**
225 | * @override
226 | * @inheritDoc
227 | */
228 | public function getRemotePort()
229 | {
230 | $address = explode(':', $this->getRemoteAddress(), 2);
231 |
232 | return isset($address[1]) ? $address[1] : '';
233 | }
234 |
235 | /**
236 | * @override
237 | * @inheritDoc
238 | */
239 | public function getRemoteProtocol()
240 | {
241 | $endpoint = explode('://', $this->getRemoteEndpoint(), 2);
242 |
243 | return isset($endpoint[0]) ? $endpoint[0] : '';
244 | }
245 |
246 | /**
247 | * @override
248 | * @inheritDoc
249 | */
250 | public function isEncrypted()
251 | {
252 | return $this->crypto !== 0;
253 | }
254 |
255 | /**
256 | * Create the client resource.
257 | *
258 | * This method creates client resource for socket connections.
259 | *
260 | * @param string $endpoint
261 | * @param mixed[] $config
262 | * @return resource
263 | * @throws LogicException
264 | */
265 | protected function createClient($endpoint, $config = [])
266 | {
267 | $ssl = $this->config['ssl'];
268 | $name = $this->config['ssl_name'];
269 | $verifySign = $this->config['ssl_verify_sign'];
270 | $verifyPeer = $this->config['ssl_verify_peer'];
271 | $verifyDepth = $this->config['ssl_verify_depth'];
272 |
273 | $context = [];
274 | $context['socket'] = [
275 | 'connect' => $endpoint
276 | ];
277 | $context['ssl'] = [
278 | 'capture_peer_cert' => true,
279 | 'capture_peer_chain' => true,
280 | 'capture_peer_cert_chain' => true,
281 | 'allow_self_signed' => !$verifySign,
282 | 'verify_peer' => $verifyPeer,
283 | 'verify_peer_name' => $verifyPeer,
284 | 'verify_depth' => $verifyDepth,
285 | 'SNI_enabled' => $name !== '',
286 | 'SNI_server_name' => $name,
287 | 'peer_name' => $name,
288 | 'disable_compression' => true,
289 | 'honor_cipher_order' => true,
290 | ];
291 |
292 | if ($ssl && isset($config['ssl_cafile']))
293 | {
294 | $context['ssl']['cafile'] = $config['ssl_cafile'];
295 | }
296 |
297 | if ($ssl && isset($config['ssl_capath']))
298 | {
299 | $context['ssl']['capath'] = $config['ssl_capath'];
300 | }
301 |
302 | $context = stream_context_create($context);
303 | // Error reporting suppressed since stream_socket_client() emits an E_WARNING on failure.
304 | $socket = @stream_socket_client(
305 | $endpoint,
306 | $errno,
307 | $errstr,
308 | 0, // Timeout does not apply for async connect.
309 | STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT,
310 | $context
311 | );
312 |
313 | if (!$socket || $errno > 0)
314 | {
315 | throw new LogicException(
316 | sprintf('Could not connect socket [%s] because of error [%d; %s]', $endpoint, $errno, $errstr)
317 | );
318 | }
319 |
320 | stream_set_blocking($socket, false);
321 |
322 | return $socket;
323 | }
324 |
325 | /**
326 | * Handle socket encryption.
327 | *
328 | * @internal
329 | */
330 | public function handleEncrypt()
331 | {
332 | $ex = null;
333 |
334 | try
335 | {
336 | if ($this->isEncrypted())
337 | {
338 | return;
339 | }
340 |
341 | $this->encrypt($this->config['ssl_method']);
342 |
343 | if ($this->isEncrypted())
344 | {
345 | $this->pause();
346 | $this->resume();
347 | }
348 | }
349 | catch (Error $ex)
350 | {}
351 | catch (Exception $ex)
352 | {}
353 |
354 | if ($ex !== null)
355 | {
356 | $this->close();
357 | $this->emit('error', [ $this, $ex ]);
358 | }
359 | }
360 |
361 | /**
362 | * @internal
363 | * @override
364 | * @inheritDoc
365 | */
366 | public function handleRead()
367 | {
368 | $ex = null;
369 |
370 | try
371 | {
372 | $data = fread($this->resource, $this->bufferSize);
373 |
374 | if ($data !== '' && $data !== false)
375 | {
376 | $this->emit('data', [ $this, $data ]);
377 | $this->emit('end', [ $this ]);
378 | }
379 |
380 | if ($data === '' || $data === false || !is_resource($this->resource) || feof($this->resource))
381 | {
382 | $this->close();
383 | }
384 | }
385 | catch (Error $ex)
386 | {}
387 | catch (Exception $ex)
388 | {}
389 |
390 | if ($ex !== null)
391 | {
392 | $this->emit('error', [ $this, $ex ]);
393 | }
394 | }
395 |
396 | /**
397 | * @internal
398 | * @override
399 | * @inheritDoc
400 | */
401 | public function handleWrite()
402 | {
403 | $ex = null;
404 |
405 | try
406 | {
407 | parent::handleWrite();
408 | }
409 | catch (Error $ex)
410 | {}
411 | catch (Exception $ex)
412 | {}
413 |
414 | if ($ex !== null)
415 | {
416 | $this->emit('error', [ $this, $ex ]);
417 | }
418 | }
419 |
420 | /**
421 | * @internal
422 | * @override
423 | * @inheritDoc
424 | */
425 | public function handleClose()
426 | {
427 | $this->pause();
428 |
429 | if (is_resource($this->resource))
430 | {
431 | // http://chat.stackoverflow.com/transcript/message/7727858#7727858
432 | stream_socket_shutdown($this->resource, STREAM_SHUT_RDWR);
433 | stream_set_blocking($this->resource, 0);
434 | fclose($this->resource);
435 | }
436 | }
437 |
438 | /**
439 | * Get function that should be invoked on read event.
440 | *
441 | * @return callable
442 | */
443 | protected function getHandleReadFunction()
444 | {
445 | return $this->config['ssl'] === true && !$this->isEncrypted()
446 | ? [ $this, 'handleEncrypt' ]
447 | : [ $this, 'handleRead' ];
448 | }
449 |
450 | /**
451 | * Get function that should be invoked on write event.
452 | *
453 | * @return callable
454 | */
455 | protected function getHandleWriteFunction()
456 | {
457 | return $this->config['ssl'] === true && !$this->isEncrypted()
458 | ? [ $this, 'handleEncrypt' ]
459 | : [ $this, 'handleWrite' ];
460 | }
461 |
462 | /**
463 | * Configure socket.
464 | *
465 | * @param string[] $config
466 | */
467 | private function configure($config = [])
468 | {
469 | $this->config = $config;
470 |
471 | $this->configureVariable('ssl');
472 | $this->configureVariable('ssl_method');
473 | $this->configureVariable('ssl_name');
474 | $this->configureVariable('ssl_verify_sign');
475 | $this->configureVariable('ssl_verify_peer');
476 | $this->configureVariable('ssl_verify_depth');
477 | }
478 |
479 | /**
480 | * Configure config variable.
481 | *
482 | * @param $configKey
483 | */
484 | private function configureVariable($configKey)
485 | {
486 | $configStaticKey = 'CONFIG_DEFAULT_' . strtoupper($configKey);
487 | $this->config[$configKey] = isset($this->config[$configKey]) ? $this->config[$configKey] : constant("static::$configStaticKey");
488 | }
489 |
490 | /**
491 | * @param bool $wantPeer
492 | * @return string
493 | */
494 | private function parseEndpoint($wantPeer = false)
495 | {
496 | $wantIndex = (int)$wantPeer;
497 |
498 | if (isset($this->cachedEndpoint[$wantIndex]))
499 | {
500 | return $this->cachedEndpoint[$wantIndex];
501 | }
502 |
503 | if (get_resource_type($this->resource) === Socket::TYPE_UNKNOWN)
504 | {
505 | return '';
506 | }
507 |
508 | $name = stream_socket_get_name($this->resource, $wantPeer);
509 | $type = $this->getStreamType();
510 |
511 | switch ($type)
512 | {
513 | case Socket::TYPE_UNIX:
514 | $transport = 'unix://';
515 | $endpoint = $transport . $name;
516 | break;
517 |
518 | case Socket::TYPE_TCP:
519 | $transport = 'tcp://';
520 | if (substr_count($name, ':') > 1)
521 | {
522 | $parts = explode(':', $name);
523 | $count = count($parts);
524 | $port = $parts[$count - 1];
525 | unset($parts[$count - 1]);
526 | $endpoint = $transport.'[' . implode(':', $parts) . ']:' . $port;
527 | }
528 | else
529 | {
530 | $endpoint = $transport . $name;
531 | }
532 | break;
533 |
534 | case Socket::TYPE_UDP:
535 | $transport = 'udp://';
536 | $endpoint = $transport . $name;
537 | break;
538 |
539 | default:
540 | $endpoint = '';
541 | }
542 |
543 | $this->cachedEndpoint[$wantIndex] = $endpoint;
544 |
545 | return $endpoint;
546 | }
547 |
548 | /**
549 | * @override
550 | * @inheritDoc
551 | */
552 | private function encrypt($method)
553 | {
554 | $type = $this->selectCryptoType($method);
555 |
556 | if ($type === self::CRYPTO_TYPE_UNKNOWN)
557 | {
558 | throw new ExecutionException('Socket encryption method is invalid!');
559 | }
560 |
561 | $resource = $this->getResource();
562 |
563 | if ($type === self::CRYPTO_TYPE_SERVER && $this->cryptoMethod === 0)
564 | {
565 | $raw = @stream_socket_recvfrom($resource, 11, STREAM_PEEK);
566 |
567 | if ($raw === '')
568 | {
569 | return;
570 | }
571 |
572 | if (11 > strlen($raw))
573 | {
574 | throw new ExecutionException('Failed to read crypto handshake.');
575 | }
576 |
577 | $data = unpack('ctype/nversion/nlength/Nembed/nmax-version', $raw);
578 | if (0x16 !== $data['type'])
579 | {
580 | throw new ExecutionException('Invalid crypto handshake.');
581 | }
582 |
583 | // Check if version was available in $method.
584 | $version = $this->selectCryptoVersion($data['max-version']);
585 | if ($method & $version)
586 | {
587 | $method = $version;
588 | }
589 | }
590 |
591 | $this->cryptoMethod = $method;
592 | $result = @stream_socket_enable_crypto($resource, true, $this->cryptoMethod);
593 |
594 | if ($result === 0)
595 | {
596 | return;
597 | }
598 | else if (!$result)
599 | {
600 | $message = 'Socket encryption failed.';
601 | if ($error = error_get_last())
602 | {
603 | $message .= sprintf(' Errno: %d; %s', $error['type'], $error['message']);
604 | }
605 | throw new ExecutionException($message);
606 | }
607 | else
608 | {
609 | $this->crypto = $this->cryptoMethod;
610 | }
611 | }
612 |
613 | /**
614 | * Checks type of crypto.
615 | *
616 | * @param int $version
617 | * @return int
618 | */
619 | private function selectCryptoType($version)
620 | {
621 | switch ($version)
622 | {
623 | case STREAM_CRYPTO_METHOD_SSLv3_SERVER:
624 | case STREAM_CRYPTO_METHOD_TLSv1_0_SERVER:
625 | case STREAM_CRYPTO_METHOD_TLSv1_1_SERVER:
626 | case STREAM_CRYPTO_METHOD_TLSv1_2_SERVER:
627 | return self::CRYPTO_TYPE_SERVER;
628 |
629 | case STREAM_CRYPTO_METHOD_SSLv3_CLIENT:
630 | case STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT:
631 | case STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT:
632 | case STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT:
633 | return self::CRYPTO_TYPE_CLIENT;
634 |
635 | default:
636 | return self::CRYPTO_TYPE_UNKNOWN;
637 | }
638 | }
639 |
640 | /**
641 | * Returns highest supported crypto method constant based on protocol version identifier.
642 | *
643 | * @param int $version
644 | * @return int
645 | */
646 | private function selectCryptoVersion($version)
647 | {
648 | switch ($version)
649 | {
650 | case 0x300: return STREAM_CRYPTO_METHOD_SSLv3_SERVER;
651 | case 0x301: return STREAM_CRYPTO_METHOD_TLSv1_0_SERVER;
652 | case 0x302: return STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
653 | default: return STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
654 | }
655 | }
656 | }
657 |
--------------------------------------------------------------------------------
/src/Socket/SocketInterface.php:
--------------------------------------------------------------------------------
1 | configure($config);
87 | $this->endpoint = $endpointOrResource;
88 | $this->socket = null;
89 | $this->loop = $loop;
90 | $this->started = false;
91 | $this->paused = true;
92 | }
93 |
94 | /**
95 | *
96 | */
97 | public function __destruct()
98 | {
99 | $this->handleClose();
100 |
101 | parent::__destruct();
102 |
103 | unset($this->socket);
104 | unset($this->loop);
105 | unset($this->started);
106 | unset($this->paused);
107 | unset($this->config);
108 | unset($this->endpoint);
109 | }
110 |
111 | /**
112 | * @override
113 | * @inheritDoc
114 | */
115 | public function start()
116 | {
117 | if ($this->isOpen())
118 | {
119 | return;
120 | }
121 |
122 | try
123 | {
124 | if (!is_resource($this->endpoint))
125 | {
126 | $this->socket = $this->createServer($this->endpoint, $this->config);
127 | }
128 | else
129 | {
130 | $this->socket = &$this->endpoint;
131 | }
132 |
133 | $this->resume();
134 | $this->started = true;
135 | }
136 | catch (Error $ex)
137 | {
138 | throw new InstantiationException('SocketListener could not be created.', 0, $ex);
139 | }
140 | catch (Exception $ex)
141 | {
142 | throw new InstantiationException('SocketListener could not be created.', 0, $ex);
143 | }
144 | }
145 |
146 | /**
147 | * @override
148 | * @inheritDoc
149 | */
150 | public function stop()
151 | {
152 | $this->close();
153 | }
154 |
155 | /**
156 | * @override
157 | * @inheritDoc
158 | */
159 | public function getLocalEndpoint()
160 | {
161 | return $this->parseEndpoint();
162 | }
163 |
164 | /**
165 | * @override
166 | * @inheritDoc
167 | */
168 | public function getLocalAddress()
169 | {
170 | $endpoint = explode('://', $this->getLocalEndpoint(), 2);
171 |
172 | return isset($endpoint[1]) ? $endpoint[1] : $endpoint[0];
173 | }
174 |
175 | /**
176 | * @override
177 | * @inheritDoc
178 | */
179 | public function getLocalHost()
180 | {
181 | $address = explode(':', $this->getLocalAddress(), 2);
182 |
183 | return $address[0];
184 | }
185 |
186 | /**
187 | * @override
188 | * @inheritDoc
189 | */
190 | public function getLocalPort()
191 | {
192 | $address = explode(':', $this->getLocalAddress(), 2);
193 |
194 | return isset($address[1]) ? $address[1] : '';
195 | }
196 |
197 | /**
198 | * @override
199 | * @inheritDoc
200 | */
201 | public function getLocalProtocol()
202 | {
203 | $endpoint = explode('://', $this->getLocalEndpoint(), 2);
204 |
205 | return isset($endpoint[0]) ? $endpoint[0]:'';
206 | }
207 |
208 | /**
209 | * @override
210 | * @inheritDoc
211 | */
212 | public function getResource()
213 | {
214 | return $this->socket;
215 | }
216 |
217 | /**
218 | * @override
219 | * @inheritDoc
220 | */
221 | public function getResourceId()
222 | {
223 | return (int) $this->socket;
224 | }
225 |
226 | /**
227 | * @override
228 | * @inheritDoc
229 | */
230 | public function getMetadata()
231 | {
232 | if ($this->isOpen())
233 | {
234 | return stream_get_meta_data($this->socket);
235 | }
236 | return [];
237 | }
238 |
239 | /**
240 | * @override
241 | * @inheritDoc
242 | */
243 | public function getStreamType()
244 | {
245 | $data = $this->getMetadata();
246 |
247 | return isset($data['stream_type']) ? $data['stream_type'] : 'undefined';
248 | }
249 |
250 | /**
251 | * @override
252 | * @inheritDoc
253 | */
254 | public function getWrapperType()
255 | {
256 | $data = $this->getMetadata();
257 |
258 | return isset($data['wrapper_type']) ? $data['wrapper_type'] : 'undefined';
259 | }
260 |
261 | /**
262 | * @override
263 | * @inheritDoc
264 | */
265 | public function isOpen()
266 | {
267 | return $this->started;
268 | }
269 |
270 | /**
271 | * @override
272 | * @inheritDoc
273 | */
274 | public function isPaused()
275 | {
276 | return $this->paused;
277 | }
278 |
279 | /**
280 | * @override
281 | * @inheritDoc
282 | */
283 | public function isEncrypted()
284 | {
285 | return isset($this->config['ssl']) && $this->config['ssl'] === true;
286 | }
287 |
288 | /**
289 | * @override
290 | * @inheritDoc
291 | */
292 | public function close()
293 | {
294 | if (!$this->isOpen())
295 | {
296 | return;
297 | }
298 |
299 | $this->started = false;
300 |
301 | $this->emit('close', [ $this ]);
302 | $this->handleClose();
303 | $this->emit('done', [ $this ]);
304 | }
305 |
306 | /**
307 | * @override
308 | * @inheritDoc
309 | */
310 | public function pause()
311 | {
312 | if (!$this->paused)
313 | {
314 | $this->paused = true;
315 |
316 | if (isset($this->loop))
317 | {
318 | $this->loop->removeReadStream($this->socket);
319 | }
320 | }
321 | }
322 |
323 | /**
324 | * @override
325 | * @inheritDoc
326 | */
327 | public function resume()
328 | {
329 | if ($this->paused)
330 | {
331 | $this->paused = false;
332 |
333 | if (isset($this->loop))
334 | {
335 | $this->loop->addReadStream($this->socket, [ $this, 'handleConnect' ]);
336 | }
337 | }
338 | }
339 |
340 | /**
341 | * Create the server resource.
342 | *
343 | * This method creates server resource for socket connections.
344 | *
345 | * @param string $endpoint
346 | * @param mixed[] $config
347 | * @return resource
348 | * @throws LogicException
349 | */
350 | protected function createServer($endpoint, $config = [])
351 | {
352 | if (stripos($endpoint, 'unix://') !== false)
353 | {
354 | if ($endpoint[7] === DIRECTORY_SEPARATOR)
355 | {
356 | $path = substr($endpoint, 7);
357 | }
358 | else
359 | {
360 | $path = getcwd() . DIRECTORY_SEPARATOR . substr($endpoint, 7);
361 | $endpoint = 'unix://' . $path;
362 | }
363 |
364 | if (file_exists($path))
365 | {
366 | unlink($path);
367 | }
368 | }
369 |
370 | $ssl = $this->config['ssl'];
371 | $name = $this->config['ssl_name'];
372 | $verifySign = $this->config['ssl_verify_sign'];
373 | $verifyPeer = $this->config['ssl_verify_peer'];
374 | $verifyDepth = $this->config['ssl_verify_depth'];
375 |
376 | $backlog = (int) (isset($config['backlog']) ? $config['backlog'] : self::DEFAULT_BACKLOG);
377 | $reuseaddr = (bool) (isset($config['reuseaddr']) ? $config['reuseaddr'] : false);
378 | $reuseport = (bool) (isset($config['reuseport']) ? $config['reuseport'] : false);
379 |
380 | $context = [];
381 | $context['socket'] = [
382 | 'bindto' => $endpoint,
383 | 'backlog' => $backlog,
384 | 'ipv6_v6only' => true,
385 | 'so_reuseaddr' => $reuseaddr,
386 | 'so_reuseport' => $reuseport,
387 | ];
388 | $context['ssl'] = [
389 | 'allow_self_signed' => !$verifySign,
390 | 'verify_peer' => $verifyPeer,
391 | 'verify_peer_name' => $verifyPeer,
392 | 'verify_depth' => $verifyDepth,
393 | 'disable_compression' => true,
394 | 'SNI_enabled' => $name !== '',
395 | 'SNI_server_name' => $name,
396 | 'peer_name' => $name,
397 | ];
398 |
399 | if ($ssl && isset($config['ssl_cert']))
400 | {
401 | $context['ssl']['local_cert'] = $config['ssl_cert'];
402 | }
403 |
404 | if ($ssl && isset($config['ssl_key']))
405 | {
406 | $context['ssl']['local_pk'] = $config['ssl_key'];
407 | }
408 |
409 | if ($ssl && isset($config['ssl_secret']))
410 | {
411 | $context['ssl']['passphrase'] = $config['ssl_secret'];
412 | }
413 |
414 | $bitmask = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
415 |
416 | $context = stream_context_create($context);
417 |
418 | // Error reporting suppressed since stream_socket_server() emits an E_WARNING on failure.
419 | $socket = @stream_socket_server(
420 | $endpoint,
421 | $errno,
422 | $errstr,
423 | $bitmask,
424 | $context
425 | );
426 |
427 | if (!$socket || $errno > 0)
428 | {
429 | throw new LogicException(
430 | sprintf('Could not bind socket [%s] because of error [%d; %s]', $endpoint, $errno, $errstr)
431 | );
432 | }
433 |
434 | return $socket;
435 | }
436 |
437 | /**
438 | * Create the client resource.
439 | *
440 | * This method creates client resource for socket connections.
441 | *
442 | * @param resource $resource
443 | * @param string[] $config
444 | * @return SocketInterface
445 | */
446 | protected function createClient($resource, $config = [])
447 | {
448 | return new Socket($resource, $this->loop, $config);
449 | }
450 |
451 | /**
452 | * Handle the new connection.
453 | *
454 | * @internal
455 | */
456 | public function handleConnect()
457 | {
458 | $socket = @stream_socket_accept($this->socket);
459 |
460 | if ($socket === false)
461 | {
462 | $this->emit('error', [ $this, new ReadException('Socket could not accept new connection.') ]);
463 | return;
464 | }
465 |
466 | $client = null;
467 | $ex = null;
468 |
469 | try
470 | {
471 | $client = $this->createClient($socket, [
472 | 'ssl' => $this->config['ssl'],
473 | 'ssl_method' => $this->config['ssl_method'],
474 | ]);
475 |
476 | $this->emit('connect', [ $this, $client ]);
477 | }
478 | catch (Error $ex)
479 | {}
480 | catch (Exception $ex)
481 | {}
482 |
483 | if ($ex !== null)
484 | {
485 | $this->handleDisconnect($socket);
486 | $this->emit('error', [ $this, new ReadException('Socket could not wrap new connection!') ]);
487 | }
488 | }
489 |
490 | private function handleDisconnect($resource)
491 | {
492 | if (is_resource($resource))
493 | {
494 | // http://chat.stackoverflow.com/transcript/message/7727858#7727858
495 | stream_socket_shutdown($resource, STREAM_SHUT_RDWR);
496 | stream_set_blocking($resource, 0);
497 | fclose($resource);
498 | }
499 | }
500 |
501 | /**
502 | * Handle closing event.
503 | *
504 | * @internal
505 | */
506 | public function handleClose()
507 | {
508 | $this->pause();
509 |
510 | if (is_resource($this->socket))
511 | {
512 | if ($this->getStreamType() === Socket::TYPE_UNIX)
513 | {
514 | $path = substr($this->parseEndpoint(), 7);
515 | unlink($path);
516 | }
517 | fclose($this->socket);
518 | }
519 | }
520 |
521 | /**
522 | * Configure socket.
523 | *
524 | * @param string[] $config
525 | */
526 | private function configure($config = [])
527 | {
528 | $this->config = $config;
529 |
530 | $this->configureVariable('ssl');
531 | $this->configureVariable('ssl_method');
532 | $this->configureVariable('ssl_name');
533 | $this->configureVariable('ssl_verify_sign');
534 | $this->configureVariable('ssl_verify_peer');
535 | $this->configureVariable('ssl_verify_depth');
536 | }
537 |
538 | /**
539 | * Configure static key
540 | *
541 | * @param $configKey
542 | */
543 | private function configureVariable($configKey)
544 | {
545 | $configStaticKey = 'CONFIG_DEFAULT_' . strtoupper($configKey);
546 | $this->config[$configKey] = isset($this->config[$configKey]) ? $this->config[$configKey] : constant("static::$configStaticKey");
547 | }
548 |
549 | /**
550 | * @return string
551 | */
552 | private function parseEndpoint()
553 | {
554 | if ($this->isOpen())
555 | {
556 | $name = stream_socket_get_name($this->socket, false);
557 | $type = $this->getStreamType();
558 |
559 | switch ($type)
560 | {
561 | case Socket::TYPE_UNIX:
562 | $transport = 'unix://';
563 | $endpoint = $transport . $name;
564 | break;
565 |
566 | case Socket::TYPE_TCP:
567 | $transport = 'tcp://';
568 | if (substr_count($name, ':') > 1)
569 | {
570 | $parts = explode(':', $name);
571 | $count = count($parts);
572 | $port = $parts[$count - 1];
573 | unset($parts[$count - 1]);
574 | $endpoint = $transport.'[' . implode(':', $parts) . ']:' . $port;
575 | }
576 | else
577 | {
578 | $endpoint = $transport . $name;
579 | }
580 | break;
581 |
582 | case Socket::TYPE_UDP:
583 | $transport = 'udp://';
584 | $endpoint = $transport . $name;
585 | break;
586 |
587 | default:
588 | $endpoint = '';
589 | }
590 |
591 | return $endpoint;
592 | }
593 | else
594 | {
595 | return is_string($this->endpoint) ? $this->endpoint : '';
596 | }
597 | }
598 | }
599 |
--------------------------------------------------------------------------------
/src/Socket/SocketListenerInterface.php:
--------------------------------------------------------------------------------
1 | loop = null;
46 | $this->sim = null;
47 | }
48 |
49 | /**
50 | *
51 | */
52 | public function tearDown()
53 | {
54 | unset($this->sim);
55 | unset($this->loop);
56 | }
57 |
58 | /**
59 | * @return LoopInterface|null
60 | */
61 | public function getLoop()
62 | {
63 | return $this->loop;
64 | }
65 |
66 | /**
67 | * Run test scenario as simulation.
68 | *
69 | * @param callable(Simulation) $scenario
70 | * @return TModule
71 | */
72 | public function simulate(callable $scenario)
73 | {
74 | try
75 | {
76 | $this->loop = new Loop(new SelectLoop);
77 | $this->loop->erase(true);
78 |
79 | $this->sim = new Simulation($this->loop);
80 | $this->sim->setScenario($scenario);
81 | $this->sim->begin();
82 | }
83 | catch (Exception $ex)
84 | {
85 | $this->fail($ex->getMessage());
86 | }
87 |
88 | return $this;
89 | }
90 |
91 | /**
92 | * @param $events
93 | * @param int $flags
94 | * @return TModule
95 | */
96 | public function expect($events, $flags = Simulation::EVENTS_COMPARE_IN_ORDER)
97 | {
98 | $expectedEvents = [];
99 |
100 | foreach ($events as $event)
101 | {
102 | $data = isset($event[1]) ? $event[1] : [];
103 | $expectedEvents[] = new Event($event[0], $data);
104 | }
105 |
106 | $this->assertEvents(
107 | $this->sim->getExpectations(),
108 | $expectedEvents,
109 | $flags
110 | );
111 |
112 | return $this;
113 | }
114 |
115 | /**
116 | * @param Event[] $actualEvents
117 | * @param Event[] $expectedEvents
118 | * @param int $flags
119 | */
120 | public function assertEvents($actualEvents = [], $expectedEvents = [], $flags = Simulation::EVENTS_COMPARE_IN_ORDER)
121 | {
122 | $count = max(count($actualEvents), count($expectedEvents));
123 |
124 | if ($flags === Simulation::EVENTS_COMPARE_RANDOMLY)
125 | {
126 | sort($actualEvents);
127 | sort($expectedEvents);
128 | }
129 |
130 | for ($i=0; $i<$count; $i++)
131 | {
132 | if (!isset($actualEvents[$i]))
133 | {
134 | $this->fail(
135 | sprintf(self::MSG_EVENT_GET_ASSERTION_FAILED, $i, $expectedEvents[$i]->name(), 'null')
136 | );
137 | }
138 | else if (!isset($expectedEvents[$i]))
139 | {
140 | $this->fail(
141 | sprintf(self::MSG_EVENT_GET_ASSERTION_FAILED, $i, 'null', $actualEvents[$i]->name())
142 | );
143 | }
144 |
145 | $actualEvent = $actualEvents[$i];
146 | $expectedEvent = $expectedEvents[$i];
147 |
148 | $this->assertSame(
149 | $expectedEvent->name(),
150 | $actualEvent->name(),
151 | sprintf(self::MSG_EVENT_NAME_ASSERTION_FAILED, $i)
152 | );
153 | $this->assertSame(
154 | $expectedEvent->data(),
155 | $actualEvent->data(),
156 | sprintf(self::MSG_EVENT_DATA_ASSERTION_FAILED, $i)
157 | );
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/test/TModule/SocketTest.php:
--------------------------------------------------------------------------------
1 | simulate(function(SimulationInterface $sim) use($endpoint) {
24 | $loop = $sim->getLoop();
25 |
26 | $server = new SocketListener($endpoint, $loop);
27 | $server->start();
28 | $server->on('connect', function(SocketListenerInterface $server, SocketInterface $conn) use($sim) {
29 | $conn->on('data', function(SocketInterface $conn, $data) use($server, $sim) {
30 | $sim->expect('data', $data);
31 | $conn->write('secret answer!');
32 | $server->close();
33 | });
34 | });
35 | $server->on('error', $this->expectCallableNever());
36 | $server->on('close', function() use($sim) {
37 | $sim->expect('close');
38 | });
39 | $server->start();
40 |
41 | $client = new Socket($endpoint, $loop);
42 | $client->on('data', function(SocketInterface $conn, $data) use($loop, $sim) {
43 | $sim->expect('data', $data);
44 | $conn->close();
45 | $sim->done();
46 | });
47 | $client->on('error', $this->expectCallableNever());
48 | $client->on('close', function() use($sim) {
49 | $sim->expect('close');
50 | });
51 |
52 | $client->write('secret question!');
53 | });
54 | $sim = $sim->expect([
55 | [ 'data', 'secret question!' ],
56 | [ 'close' ],
57 | [ 'data', 'secret answer!' ],
58 | [ 'close' ]
59 | ]);
60 | }
61 |
62 | /**
63 | * @dataProvider endpointSSLProvider
64 | */
65 | public function testSocketWritesAndReadsDataCorrectly_WithSSLConfiguration($endpoint)
66 | {
67 | $sim = $this->simulate(function(SimulationInterface $sim) use($endpoint) {
68 | $loop = $sim->getLoop();
69 |
70 | $serverConfig = [
71 | 'ssl' => true,
72 | 'ssl_cert' => __DIR__ . '/_Data/_ssl_cert.pem',
73 | 'ssl_key' => __DIR__ . '/_Data/_ssl_key.pem',
74 | 'ssl_secret' => 'secret',
75 | ];
76 |
77 | $server = new SocketListener($endpoint, $loop, $serverConfig);
78 | $server->on('connect', function(SocketListenerInterface $server, SocketInterface $conn) use($sim) {
79 | $conn->on('data', function(SocketInterface $conn, $data) use($server, $sim) {
80 | $sim->expect('data', $data);
81 | $conn->write('secret answer!');
82 | $server->close();
83 | });
84 | });
85 | $server->on('error', $this->expectCallableNever());
86 | $server->on('close', function() use($sim) {
87 | $sim->expect('close');
88 | });
89 | $server->start();
90 |
91 | $clientConfig = [
92 | 'ssl' => true,
93 | ];
94 | $client = new Socket($endpoint, $loop, $clientConfig);
95 | $client->on('data', function(SocketInterface $conn, $data) use($loop, $sim) {
96 | $sim->expect('data', $data);
97 | $conn->close();
98 | $sim->done();
99 | });
100 | $client->on('error', $this->expectCallableNever());
101 | $client->on('close', function() use($sim) {
102 | $sim->expect('close');
103 | });
104 |
105 | $client->write('secret question!');
106 | });
107 | $sim = $sim->expect([
108 | [ 'data', 'secret question!' ],
109 | [ 'close' ],
110 | [ 'data', 'secret answer!' ],
111 | [ 'close' ]
112 | ]);
113 | }
114 |
115 | /**
116 | * @return string[][]
117 | */
118 | public function endpointProvider()
119 | {
120 | return [
121 | [ 'tcp://127.0.0.1:2080' ],
122 | [ 'unix://mysocket.sock' ]
123 | ];
124 | }
125 |
126 | /**
127 | * @return string[][]
128 | */
129 | public function endpointSSLProvider()
130 | {
131 | return [
132 | [ 'tcp://127.0.0.1:2080' ],
133 | ];
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/test/TModule/_Data/_ssl_cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC9jCCAd4CCQC1n9FarkDiuTANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQGEwJD
3 | TjELMAkGA1UECAwCU0gxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
4 | ZDAeFw0xNzAxMjcwMjI0MjNaFw0xODAxMjcwMjI0MjNaMD0xCzAJBgNVBAYTAkNO
5 | MQswCQYDVQQIDAJTSDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
6 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6P2tbxw2JBEr/jI6dyU/
7 | ppopZ3XbUybS4A3V2G/k5wXocjPOx2ttb4J3cClKYzyyG96AFRftmkjps1ixxknO
8 | ZlXWVJ+Cdgw3RXn0kt9s8zjYtGKFnDBTyi27KMVjpdGiOl9iJA71FgWeRuUOzFUf
9 | poAVkeohGwVQQKjiBANZo4Civwae71LJ0HwN+JtX/l8Rcqrlt5DYAAix13ZzF9EY
10 | OQQTb18UVEdKyVM8RQbpnE3DZ0tdB7WAiUBOd3H9DICPSCytSSVfOFYyWXzM66CO
11 | q2rI3CN4mNYBybNrfjby5FK8Qgnu8boVLPlpq70j8SnAik1drfGVdf0W2UOvxWcD
12 | +wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBWSUEWA161OYo9XM6PVqP++RQyIq7d
13 | YFgdwgxjZTUINuOlBQnQEPbCskeCEaxEK/APkyGfV8M+b8NzLgXSJJAXjI6tfBmR
14 | CMLXk/cTGImAd1c6KQ3wCPQqbn8bxCaC1ca9MrJPqB4EOjIiVw+zBNXS6n3kDqkD
15 | a6N3DfBXZycY6tfOSnbej6q2/hPzgqP2VquNvsiloRpay5i+yfcx3rbh/bJVHsb7
16 | 1YoqYZU/Kn88Lt2U8Qhwkag2qAvSd7uGAldxNkzgbAXZ3QneoOKFcWCvGLbS3XjN
17 | mMWkYg8EHsks6PzLRtQesHKiq8t92JdZsxvasMXMNAs9GHDQ88En8iUL
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/test/TModule/_Data/_ssl_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEA6P2tbxw2JBEr/jI6dyU/ppopZ3XbUybS4A3V2G/k5wXocjPO
3 | x2ttb4J3cClKYzyyG96AFRftmkjps1ixxknOZlXWVJ+Cdgw3RXn0kt9s8zjYtGKF
4 | nDBTyi27KMVjpdGiOl9iJA71FgWeRuUOzFUfpoAVkeohGwVQQKjiBANZo4Civwae
5 | 71LJ0HwN+JtX/l8Rcqrlt5DYAAix13ZzF9EYOQQTb18UVEdKyVM8RQbpnE3DZ0td
6 | B7WAiUBOd3H9DICPSCytSSVfOFYyWXzM66COq2rI3CN4mNYBybNrfjby5FK8Qgnu
7 | 8boVLPlpq70j8SnAik1drfGVdf0W2UOvxWcD+wIDAQABAoIBAGr6BwbsMiOvSr7F
8 | Ozj3flk1kWsynwh32/0J/TUKc1RrzwWYqgnAVpgLlGGIY+JavQC00aRdvzfG4J77
9 | S2za+IzePrHOJ22iQEKDM3u4YGLwp/dzARvsyJwdEUEwGIfLUCxzHTGnOHrrC3hL
10 | tda2AvlE+CJuGNM6RlXZ9SSYkTDTgBORkuAJf4pTZrG5wVedTGR0us1lj4BKzuDF
11 | MsApnBum8Dpj73nZCcfT9MMgbuXGp1jsDuE7eZsiDzklarroZxOcYX9YMTpsG2iY
12 | f2drGWSVssZKwmxaTiAHi4iMIhawxQwE13PxK5FqP1DQjQYmPjoDY5CQ1/OD6oGJ
13 | klRpEykCgYEA+XDlDHRP3UK1UpZJY6heudRgdbuEdd4vFbEVG6Kk1vh3ub6Y33UJ
14 | 1nvHIvjzCOEnjO5wZbBpoJy1O2fCJt/rdP1nWI2SLXtBIv4floia1SK5S1fmrr4C
15 | 1uDAB3+/PAJe5gSBX6l9dt1JesB6ERrrVYU05GCnW64T+kDg19IZaUUCgYEA7x4M
16 | sOGXjdunmk5DmgaAZcKKUXm6G5+DGHCiHzylUr3LsJ4dqBMFYDfA0s2YNlT+pKpK
17 | BEDBxfzlx1ewaorr2Hge3QAHeGWa4oQok42u7rv1ernl/RYOfI3JcZaX89cu482b
18 | /tFUjeMDfxqiD74hl2JtK3vkBTfldApPS10ZbD8CgYEA4S1S+C0/s4P23/kfg0GJ
19 | UcQw19dRun26j6BuZ6YpnI5stlci9gZXvTG8EQwfiZKH+6d7+7CHiiAtUtVV5XYa
20 | vl/LnZsfrMMpiAdUSLZE38ca8rMFYhXRxlzZvWtLHUcemBLVH6CHzHfzT0bWav6c
21 | F3XKy6edLRw2mKFc2DeoDOkCgYEAndDhY2Sg2Bme9rKs311nJMJO9BM5B74xkdHU
22 | znUCUCe/5eOVgzZ6l9R0SS00Rre2EQvrKf9rZLbTGMwBPXBO1GIDTK0WQRoeLV44
23 | QZqDWEFpdQR4jJ8gFIin6XYQ9/iPk/5B9N5HxhWMWzlCuM0t8nIdx1NPXTpVEE81
24 | onHhs5UCgYA6y5LpvJ5vOpZyHdTISgQK+4SmhUSu3e29qE8AjixaTBFwGa/PKuVO
25 | ggpceNZjl+pg1F74IMEpg1c6N1MKEIhK+NEYhxS9QkShPNYwasfkhg87QfkOxUi+
26 | jed5G+yO01Re0bkyE6YiS+JrJQZJEdna/x9eb5IxF/LiTWkZhkdywg==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/TUnit.php:
--------------------------------------------------------------------------------
1 | exactly(2);
34 | }
35 |
36 | /**
37 | * Creates a callback that must be called $amount times or the test will fail.
38 | *
39 | * @param $amount
40 | * @return callable|\PHPUnit_Framework_MockObject_MockObject
41 | */
42 | public function expectCallableExactly($amount)
43 | {
44 | $mock = $this->createCallableMock();
45 | $mock
46 | ->expects($this->exactly($amount))
47 | ->method('__invoke');
48 |
49 | return $mock;
50 | }
51 |
52 | /**
53 | * Creates a callback that must be called once.
54 | *
55 | * @return callable|\PHPUnit_Framework_MockObject_MockObject
56 | */
57 | public function expectCallableOnce()
58 | {
59 | $mock = $this->createCallableMock();
60 | $mock
61 | ->expects($this->once())
62 | ->method('__invoke');
63 |
64 | return $mock;
65 | }
66 |
67 | /**
68 | * Creates a callback that must be called twice.
69 | *
70 | * @return callable|\PHPUnit_Framework_MockObject_MockObject
71 | */
72 | public function expectCallableTwice()
73 | {
74 | $mock = $this->createCallableMock();
75 | $mock
76 | ->expects($this->exactly(2))
77 | ->method('__invoke');
78 |
79 | return $mock;
80 | }
81 |
82 | /**
83 | * Creates a callable that must not be called once.
84 | *
85 | * @return callable|\PHPUnit_Framework_MockObject_MockObject
86 | */
87 | public function expectCallableNever()
88 | {
89 | $mock = $this->createCallableMock();
90 | $mock
91 | ->expects($this->never())
92 | ->method('__invoke');
93 |
94 | return $mock;
95 | }
96 |
97 | /**
98 | * Creates a callable mock.
99 | *
100 | * @return callable|\PHPUnit_Framework_MockObject_MockObject
101 | */
102 | public function createCallableMock()
103 | {
104 | return $this->getMock(Callback::class);
105 | }
106 |
107 | /**
108 | * @return LoopInterface|\PHPUnit_Framework_MockObject_MockObject
109 | */
110 | public function createLoopMock()
111 | {
112 | return $this->getMock('Dazzle\Loop\LoopInterface');
113 | }
114 |
115 | /**
116 | * @return LoopInterface|\PHPUnit_Framework_MockObject_MockObject
117 | */
118 | public function createWritableLoopMock()
119 | {
120 | $loop = $this->createLoopMock();
121 | $loop
122 | ->expects($this->once())
123 | ->method('addWriteStream')
124 | ->will($this->returnCallback(function($stream, $listener) {
125 | call_user_func($listener, $stream);
126 | }));
127 |
128 | return $loop;
129 | }
130 |
131 | /**
132 | * @return LoopInterface|\PHPUnit_Framework_MockObject_MockObject
133 | */
134 | public function createReadableLoopMock()
135 | {
136 | $loop = $this->createLoopMock();
137 | $loop
138 | ->expects($this->once())
139 | ->method('addReadStream')
140 | ->will($this->returnCallback(function($stream, $listener) {
141 | call_user_func($listener, $stream);
142 | }));
143 |
144 | return $loop;
145 | }
146 |
147 | /**
148 | * Check if protected property exists.
149 | *
150 | * @param object $object
151 | * @param string $property
152 | * @return bool
153 | */
154 | public function existsProtectedProperty($object, $property)
155 | {
156 | $reflection = new ReflectionClass($object);
157 | return $reflection->hasProperty($property);
158 | }
159 |
160 | /**
161 | * Get protected property from given object via reflection.
162 | *
163 | * @param object $object
164 | * @param string $property
165 | * @return mixed
166 | */
167 | public function getProtectedProperty($object, $property)
168 | {
169 | $reflection = new ReflectionClass($object);
170 | $reflection_property = $reflection->getProperty($property);
171 | $reflection_property->setAccessible(true);
172 |
173 | return $reflection_property->getValue($object);
174 | }
175 |
176 | /**
177 | * Set protected property on a given object via reflection.
178 | *
179 | * @param object $object
180 | * @param string $property
181 | * @param mixed $value
182 | * @return object
183 | */
184 | public function setProtectedProperty($object, $property, $value)
185 | {
186 | $reflection = new ReflectionClass($object);
187 | $reflection_property = $reflection->getProperty($property);
188 | $reflection_property->setAccessible(true);
189 | $reflection_property->setValue($object, $value);
190 |
191 | return $object;
192 | }
193 |
194 | /**
195 | * Call protected method on a given object via reflection.
196 | *
197 | * @param object|string $objectOrClass
198 | * @param string $method
199 | * @param mixed[] $args
200 | * @return mixed
201 | */
202 | public function callProtectedMethod($objectOrClass, $method, $args = [])
203 | {
204 | $reflection = new ReflectionClass($objectOrClass);
205 | $reflectionMethod = $reflection->getMethod($method);
206 | $reflectionMethod->setAccessible(true);
207 | $reflectionTarget = is_object($objectOrClass) ? $objectOrClass : null;
208 |
209 | return $reflectionMethod->invokeArgs($reflectionTarget, $args);
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/test/TUnit/SocketListenerTest.php:
--------------------------------------------------------------------------------
1 | createSocketListenerMock();
19 |
20 | $this->assertInstanceOf(SocketListener::class, $socket);
21 | $this->assertInstanceOf(SocketListenerInterface::class, $socket);
22 | }
23 |
24 | /**
25 | *
26 | */
27 | public function testApiDestructor_DoesNotThrowException()
28 | {
29 | $socket = $this->createSocketListenerMock();
30 | unset($socket);
31 | }
32 |
33 | /**
34 | *
35 | */
36 | public function testStart_ThrowsException_OnInvalidResource()
37 | {
38 | $this->setExpectedException(InstantiationException::class);
39 | $socket = $this->createSocketListenerMock('invalid');
40 | $socket->start();
41 | }
42 |
43 | /**
44 | *
45 | */
46 | public function testStart_ThrowsException_OnOccupiedEndpoint()
47 | {
48 | $server = stream_socket_server($this->tempSocketRemoteAddress());
49 | $this->setExpectedException(InstantiationException::class);
50 | $socket = $this->createSocketListenerMock();
51 | $socket->start();
52 | }
53 |
54 | /**
55 | *
56 | */
57 | public function testApiGetLocalEndpoint_ReturnsEndpoint()
58 | {
59 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
60 | $this->assertRegExp('#^tcp://(([0-9]*?)\.){3}([0-9]*?):([0-9]*?)$#si', $socket->getLocalEndpoint());
61 | }
62 |
63 | /**
64 | *
65 | */
66 | public function testApiGetLocalAddress_ReturnsAddress()
67 | {
68 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
69 |
70 | $pattern = '#^(([0-9]*?)\.){3}([0-9]*?):([0-9]*?)$#si';
71 |
72 | $this->assertRegExp($pattern, $socket->getLocalAddress());
73 | }
74 |
75 | /**
76 | *
77 | */
78 | public function testApiGetLocalHost_ReturnsHost()
79 | {
80 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
81 |
82 | $pattern = '#^(([0-9]*?)\.){3}([0-9]*?)$#si';
83 |
84 | $this->assertRegExp($pattern, $socket->getLocalHost());
85 | }
86 |
87 | /**
88 | *
89 | */
90 | public function testApiGetLocalPort_ReturnsPort()
91 | {
92 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
93 |
94 | $pattern = '#^([0-9]*?)$#si';
95 |
96 | $this->assertRegExp($pattern, $socket->getLocalPort());
97 | }
98 |
99 | /**
100 | *
101 | */
102 | public function testApiGetResource_ReturnsResource()
103 | {
104 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
105 | $socket->start();
106 | $this->assertTrue(is_resource($socket->getResource()));
107 | }
108 |
109 | /**
110 | *
111 | */
112 | public function testApiGetResourceId_ReturnsResourceId()
113 | {
114 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
115 | $socket->start();
116 | $this->assertTrue(is_numeric($socket->getResourceId()));
117 | }
118 |
119 | /**
120 | *
121 | */
122 | public function testApiGetMetadata_ReturnsMetadata()
123 | {
124 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
125 | $this->assertTrue(is_array($socket->getMetadata()));
126 | }
127 |
128 | /**
129 | *
130 | */
131 | public function testApiGetStreamType_ReturnsStreamType()
132 | {
133 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
134 | $socket->start();
135 | $this->assertSame('tcp_socket/ssl', $socket->getStreamType());
136 | }
137 |
138 | /**
139 | *
140 | */
141 | public function testApiGetWrapperType_ReturnsWrapperType()
142 | {
143 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
144 | $this->assertSame('undefined', $socket->getWrapperType());
145 | }
146 |
147 | /**
148 | *
149 | */
150 | public function testApiIsOpen_ReturnsIfSocketIsOpened()
151 | {
152 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
153 | $this->assertFalse($socket->isOpen());
154 | $socket->start();
155 | $this->assertTrue($socket->isOpen());
156 | }
157 |
158 | /**
159 | *
160 | */
161 | public function testApiIsPaused_ReturnsIfSocketIsPaused()
162 | {
163 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
164 | $this->assertTrue($socket->isPaused());
165 | $socket->start();
166 | $this->assertFalse($socket->isPaused());
167 | }
168 |
169 | //todo
170 | public function testApiStart_CannotBeInvokedMoreThanOnce()
171 | {
172 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
173 | }
174 |
175 | //todo
176 | public function testApiStop_CannotBeInvokedMoreThanOnce()
177 | {
178 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
179 | }
180 |
181 | //todo
182 | public function testApiStart_CanBeInvokedAfterStop()
183 | {
184 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
185 |
186 | }
187 |
188 | //todo
189 | public function testApiStop_CanBeInvokedAfterStart()
190 | {
191 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
192 | }
193 |
194 | /**
195 | *
196 | */
197 | public function testApiStart_StartSocket()
198 | {
199 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
200 | $this->assertFalse($socket->isOpen());
201 | $socket->start();
202 | $this->assertTrue($socket->isOpen());
203 | }
204 |
205 | /**
206 | *
207 | */
208 | public function testApiClose_ClosesSocket()
209 | {
210 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
211 | $socket->start();
212 | $this->assertTrue($socket->isOpen());
213 | $socket->close();
214 | $this->assertFalse($socket->isOpen());
215 | }
216 |
217 | /**
218 | *
219 | */
220 | public function testApiPause_PausesSocket()
221 | {
222 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
223 | $socket->start();
224 | $this->assertFalse($socket->isPaused());
225 | $socket->pause();
226 | $this->assertTrue($socket->isPaused());
227 | }
228 |
229 | /**
230 | *
231 | */
232 | public function testApiResume_ResumesSocket()
233 | {
234 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress());
235 |
236 | $socket->pause();
237 | $this->assertTrue($socket->isPaused());
238 |
239 | $socket->resume();
240 | $this->assertFalse($socket->isPaused());
241 | }
242 |
243 | /**
244 | * @throws InstantiationException
245 | */
246 | public function testApiStart_SslSocket()
247 | {
248 | $config = [
249 | 'ssl' => true,
250 | 'ssl_cert' => __DIR__ . '/_Data/_ssl_cert.pem',
251 | 'ssl_secret' => __DIR__ . '/_Data/_ssl_key.pem',
252 | 'ssl_key'=> 'secret',
253 | ];
254 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress(), null, $config);
255 |
256 | $this->assertFalse($socket->isOpen());
257 | $socket->start();
258 |
259 | $this->assertTrue($socket->isOpen());
260 | }
261 |
262 | /**
263 | * @throws InstantiationException
264 | */
265 | public function testApiClose_SslSocket()
266 | {
267 | $config = [
268 | 'ssl' => true,
269 | 'ssl_cert' => __DIR__ . '/_Data/_ssl_cert.pem',
270 | 'ssl_secret' => __DIR__ . '/_Data/_ssl_key.pem',
271 | 'ssl_key'=> 'secret',
272 | ];
273 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress(), null, $config);
274 | $socket->start();
275 |
276 | $this->assertTrue($socket->isOpen());
277 | $socket->close();
278 |
279 | $this->assertFalse($socket->isOpen());
280 | }
281 |
282 | /**
283 | * @throws InstantiationException
284 | */
285 | public function testApiPause_SslSocket()
286 | {
287 | $config = [
288 | 'ssl' => true,
289 | 'ssl_cert' => __DIR__ . '/_Data/_ssl_cert.pem',
290 | 'ssl_secret' => __DIR__ . '/_Data/_ssl_key.pem',
291 | 'ssl_key'=> 'secret',
292 | ];
293 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress(), null, $config);
294 | $socket->start();
295 |
296 | $this->assertFalse($socket->isPaused());
297 | $socket->pause();
298 |
299 | $this->assertTrue($socket->isPaused());
300 | }
301 |
302 | /**
303 | * @throws InstantiationException
304 | */
305 | public function testApiResume_SslSocket()
306 | {
307 | $config = [
308 | 'ssl' => true,
309 | 'ssl_cert' => __DIR__ . '/_Data/_ssl_cert.pem',
310 | 'ssl_secret' => __DIR__ . '/_Data/_ssl_key.pem',
311 | 'ssl_key'=> 'secret',
312 | ];
313 | $socket = $this->createSocketListenerMock($this->tempSocketRemoteAddress(), null, $config);
314 | $socket->pause();
315 | $this->assertTrue($socket->isPaused());
316 |
317 | $socket->resume();
318 | $this->assertFalse($socket->isPaused());
319 | }
320 |
321 | /**
322 | * @param resource|null $resource
323 | * @param LoopInterface $loop
324 | * @param array $config
325 | * @return SocketListener
326 | */
327 | protected function createSocketListenerMock($resource = null, LoopInterface $loop = null , $config = [])
328 | {
329 | return $this->createSocketListenerInjection(
330 | is_null($resource) ? $this->tempSocketRemoteAddress() : $resource,
331 | is_null($loop) ? $this->createLoopMock() : $loop , $config
332 | );
333 | }
334 |
335 | /**
336 | * @param string|resource $endpointOrResource
337 | * @param LoopInterface $loop
338 | * @param array $config
339 | * @return SocketListener
340 | */
341 | protected function createSocketListenerInjection($endpointOrResource, LoopInterface $loop , $config = [])
342 | {
343 | return new SocketListener($endpointOrResource, $loop, $config);
344 | }
345 |
346 | /**
347 | * @return string
348 | */
349 | private function tempSocketRemoteAddress()
350 | {
351 | return 'tcp://127.0.0.1:10080';
352 | }
353 | }
354 |
--------------------------------------------------------------------------------
/test/TUnit/SocketTest.php:
--------------------------------------------------------------------------------
1 | tempSocketAddress());
20 |
21 | if (file_exists($path))
22 | {
23 | unlink($path);
24 | }
25 |
26 | parent::tearDown();
27 | }
28 |
29 | public function testApiConstructor_CreatesInstance()
30 | {
31 | $server = stream_socket_server($this->tempSocketAddress());
32 | $socket = $this->createSocketMock();
33 |
34 | $this->assertInstanceOf(Socket::class, $socket);
35 | $this->assertInstanceOf(SocketInterface::class, $socket);
36 | }
37 |
38 | /**
39 | *
40 | */
41 | public function testApiConstructor_ThrowsException_WhenNoServerExists()
42 | {
43 | $this->setExpectedException(InstantiationException::class);
44 | $this->createSocketMock();
45 | }
46 |
47 | /**
48 | *
49 | */
50 | public function testApiConstructor_ThrowsException_OnInvalidResource()
51 | {
52 | $this->setExpectedException(InstantiationException::class);
53 | $this->createSocketMock('invalid');
54 | }
55 |
56 | /**
57 | *
58 | */
59 | public function testDestructor_DoesNotThrowException()
60 | {
61 | $server = stream_socket_server($this->tempSocketAddress());
62 | $socket = $this->createSocketMock();
63 |
64 | unset($socket);
65 | }
66 |
67 | /**
68 | *
69 | */
70 | public function testApiStop_StopsSocket()
71 | {
72 | $server = stream_socket_server($this->tempSocketAddress());
73 | $socket = $this->createSocketMock();
74 |
75 | $this->assertTrue($socket->isOpen());
76 | $socket->stop();
77 | $this->assertFalse($socket->isOpen());
78 | }
79 |
80 | /**
81 | *
82 | */
83 | public function testApiGetLocalEndpoint_ReturnsEndpoint()
84 | {
85 | $server = stream_socket_server($this->tempSocketRemoteAddress(),$errno,$errstr,STREAM_SERVER_BIND);
86 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
87 |
88 | $this->assertRegExp('#^tcp://(([0-9]*?)\.){3}([0-9]*?):([0-9]*?)$#si', $socket->getLocalEndpoint());
89 | }
90 |
91 | /**
92 | *
93 | */
94 | public function testApiGetRemoteEndpoint_ReturnsEndpoint()
95 | {
96 | $remote = $this->tempSocketRemoteAddress();
97 | $server = stream_socket_server($remote,$errno,$errstr);
98 | $socket = $this->createSocketMock($remote);
99 |
100 | $this->assertEquals($remote, $socket->getRemoteEndpoint());
101 | }
102 |
103 | /**
104 | *
105 | */
106 | public function testApiGetLocalAddress_ReturnsAddress()
107 | {
108 | $server = stream_socket_server($this->tempSocketRemoteAddress());
109 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
110 | $pattern = '#^(([0-9]*?)\.){3}([0-9]*?):([0-9]*?)$#si';
111 |
112 | $this->assertRegExp($pattern, $socket->getLocalAddress());
113 | }
114 |
115 | /**
116 | *
117 | */
118 | public function testApiGetRemoteAddress_ReturnsAddress()
119 | {
120 | $remote = $this->tempSocketRemoteAddress();
121 | $server = stream_socket_server($remote);
122 | $socket = $this->createSocketMock($remote);
123 |
124 | $address = str_replace('tcp://', '', $remote);
125 |
126 | $this->assertEquals($address, $socket->getRemoteAddress());
127 | }
128 |
129 | /**
130 | *
131 | */
132 | public function testApiGetLocalHost_ReturnsHost()
133 | {
134 | $server = stream_socket_server($this->tempSocketRemoteAddress());
135 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
136 |
137 | $pattern = '#^(([0-9]*?)\.){3}([0-9]*?)$#si';
138 |
139 | $this->assertRegExp($pattern, $socket->getLocalHost());
140 | }
141 |
142 | /**
143 | *
144 | */
145 | public function testApiGetRemoteHost_ReturnsHost()
146 | {
147 | $server = stream_socket_server($this->tempSocketRemoteAddress());
148 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
149 |
150 | $pattern = '#^(([0-9]*?)\.){3}([0-9]*?)$#si';
151 |
152 | $this->assertRegExp($pattern, $socket->getRemoteHost());
153 | }
154 |
155 | /**
156 | *
157 | */
158 | public function testApiGetLocalPort_ReturnsPort()
159 | {
160 | $server = stream_socket_server($this->tempSocketRemoteAddress());
161 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
162 |
163 | $pattern = '#^([0-9]*?)$#si';
164 |
165 | $this->assertRegExp($pattern, $socket->getLocalPort());
166 | }
167 |
168 | /**
169 | *
170 | */
171 | public function testApiGetRemotePort_ReturnsPort()
172 | {
173 | $server = stream_socket_server($this->tempSocketRemoteAddress());
174 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
175 |
176 | $pattern = '#^([0-9]*?)$#si';
177 |
178 | $this->assertRegExp($pattern, $socket->getRemotePort());
179 | }
180 |
181 | public function testApiGetLocalProtocol_ReturnsProtocol()
182 | {
183 | $server = stream_socket_server($this->tempSocketRemoteAddress());
184 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
185 |
186 | $transports = stream_get_transports();
187 | $this->assertTrue(in_array($socket->getLocalProtocol(), $transports));
188 | }
189 |
190 | public function testApiGetRemoteProtocol_ReturnsProtocol()
191 | {
192 | $server = stream_socket_server($this->tempSocketRemoteAddress());
193 | $socket = $this->createSocketMock($this->tempSocketRemoteAddress());
194 |
195 | $transports = stream_get_transports();
196 |
197 | $this->assertTrue(in_array($socket->getRemoteProtocol(), $transports));
198 | }
199 |
200 | /**
201 | * @param resource|null $resource
202 | * @param LoopInterface $loop
203 | * @param array $config
204 | * @return Socket
205 | */
206 | protected function createSocketMock($resource = null, LoopInterface $loop = null, $config = [])
207 | {
208 | return $this->createSocketInjection(
209 | is_null($resource) ? $this->tempSocketAddress() : $resource,
210 | is_null($loop) ? $this->createLoopMock() : $loop, $config
211 | );
212 | }
213 |
214 | /**
215 | * @param string|resource $endpointOrResource
216 | * @param LoopInterface $loop
217 | * @param array $config
218 | * @return Socket
219 | */
220 | protected function createSocketInjection($endpointOrResource, LoopInterface $loop, $config = [])
221 | {
222 | return new Socket($endpointOrResource, $loop ,$config);
223 | }
224 |
225 | /**
226 | * @return string
227 | */
228 | private function tempSocketAddress()
229 | {
230 | return 'unix://' . $this->basePath() . '/temp.sock';
231 | }
232 |
233 | /**
234 | * @return string
235 | */
236 | private function tempSocketRemoteAddress()
237 | {
238 | return 'tcp://127.0.0.1:10080';
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/test/TUnit/_Data/_ssl_cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC9jCCAd4CCQC1n9FarkDiuTANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQGEwJD
3 | TjELMAkGA1UECAwCU0gxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
4 | ZDAeFw0xNzAxMjcwMjI0MjNaFw0xODAxMjcwMjI0MjNaMD0xCzAJBgNVBAYTAkNO
5 | MQswCQYDVQQIDAJTSDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
6 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6P2tbxw2JBEr/jI6dyU/
7 | ppopZ3XbUybS4A3V2G/k5wXocjPOx2ttb4J3cClKYzyyG96AFRftmkjps1ixxknO
8 | ZlXWVJ+Cdgw3RXn0kt9s8zjYtGKFnDBTyi27KMVjpdGiOl9iJA71FgWeRuUOzFUf
9 | poAVkeohGwVQQKjiBANZo4Civwae71LJ0HwN+JtX/l8Rcqrlt5DYAAix13ZzF9EY
10 | OQQTb18UVEdKyVM8RQbpnE3DZ0tdB7WAiUBOd3H9DICPSCytSSVfOFYyWXzM66CO
11 | q2rI3CN4mNYBybNrfjby5FK8Qgnu8boVLPlpq70j8SnAik1drfGVdf0W2UOvxWcD
12 | +wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBWSUEWA161OYo9XM6PVqP++RQyIq7d
13 | YFgdwgxjZTUINuOlBQnQEPbCskeCEaxEK/APkyGfV8M+b8NzLgXSJJAXjI6tfBmR
14 | CMLXk/cTGImAd1c6KQ3wCPQqbn8bxCaC1ca9MrJPqB4EOjIiVw+zBNXS6n3kDqkD
15 | a6N3DfBXZycY6tfOSnbej6q2/hPzgqP2VquNvsiloRpay5i+yfcx3rbh/bJVHsb7
16 | 1YoqYZU/Kn88Lt2U8Qhwkag2qAvSd7uGAldxNkzgbAXZ3QneoOKFcWCvGLbS3XjN
17 | mMWkYg8EHsks6PzLRtQesHKiq8t92JdZsxvasMXMNAs9GHDQ88En8iUL
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/test/TUnit/_Data/_ssl_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEA6P2tbxw2JBEr/jI6dyU/ppopZ3XbUybS4A3V2G/k5wXocjPO
3 | x2ttb4J3cClKYzyyG96AFRftmkjps1ixxknOZlXWVJ+Cdgw3RXn0kt9s8zjYtGKF
4 | nDBTyi27KMVjpdGiOl9iJA71FgWeRuUOzFUfpoAVkeohGwVQQKjiBANZo4Civwae
5 | 71LJ0HwN+JtX/l8Rcqrlt5DYAAix13ZzF9EYOQQTb18UVEdKyVM8RQbpnE3DZ0td
6 | B7WAiUBOd3H9DICPSCytSSVfOFYyWXzM66COq2rI3CN4mNYBybNrfjby5FK8Qgnu
7 | 8boVLPlpq70j8SnAik1drfGVdf0W2UOvxWcD+wIDAQABAoIBAGr6BwbsMiOvSr7F
8 | Ozj3flk1kWsynwh32/0J/TUKc1RrzwWYqgnAVpgLlGGIY+JavQC00aRdvzfG4J77
9 | S2za+IzePrHOJ22iQEKDM3u4YGLwp/dzARvsyJwdEUEwGIfLUCxzHTGnOHrrC3hL
10 | tda2AvlE+CJuGNM6RlXZ9SSYkTDTgBORkuAJf4pTZrG5wVedTGR0us1lj4BKzuDF
11 | MsApnBum8Dpj73nZCcfT9MMgbuXGp1jsDuE7eZsiDzklarroZxOcYX9YMTpsG2iY
12 | f2drGWSVssZKwmxaTiAHi4iMIhawxQwE13PxK5FqP1DQjQYmPjoDY5CQ1/OD6oGJ
13 | klRpEykCgYEA+XDlDHRP3UK1UpZJY6heudRgdbuEdd4vFbEVG6Kk1vh3ub6Y33UJ
14 | 1nvHIvjzCOEnjO5wZbBpoJy1O2fCJt/rdP1nWI2SLXtBIv4floia1SK5S1fmrr4C
15 | 1uDAB3+/PAJe5gSBX6l9dt1JesB6ERrrVYU05GCnW64T+kDg19IZaUUCgYEA7x4M
16 | sOGXjdunmk5DmgaAZcKKUXm6G5+DGHCiHzylUr3LsJ4dqBMFYDfA0s2YNlT+pKpK
17 | BEDBxfzlx1ewaorr2Hge3QAHeGWa4oQok42u7rv1ernl/RYOfI3JcZaX89cu482b
18 | /tFUjeMDfxqiD74hl2JtK3vkBTfldApPS10ZbD8CgYEA4S1S+C0/s4P23/kfg0GJ
19 | UcQw19dRun26j6BuZ6YpnI5stlci9gZXvTG8EQwfiZKH+6d7+7CHiiAtUtVV5XYa
20 | vl/LnZsfrMMpiAdUSLZE38ca8rMFYhXRxlzZvWtLHUcemBLVH6CHzHfzT0bWav6c
21 | F3XKy6edLRw2mKFc2DeoDOkCgYEAndDhY2Sg2Bme9rKs311nJMJO9BM5B74xkdHU
22 | znUCUCe/5eOVgzZ6l9R0SS00Rre2EQvrKf9rZLbTGMwBPXBO1GIDTK0WQRoeLV44
23 | QZqDWEFpdQR4jJ8gFIin6XYQ9/iPk/5B9N5HxhWMWzlCuM0t8nIdx1NPXTpVEE81
24 | onHhs5UCgYA6y5LpvJ5vOpZyHdTISgQK+4SmhUSu3e29qE8AjixaTBFwGa/PKuVO
25 | ggpceNZjl+pg1F74IMEpg1c6N1MKEIhK+NEYhxS9QkShPNYwasfkhg87QfkOxUi+
26 | jed5G+yO01Re0bkyE6YiS+JrJQZJEdna/x9eb5IxF/LiTWkZhkdywg==
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/_Simulation/Event.php:
--------------------------------------------------------------------------------
1 | name = $name;
24 | $this->data = $data;
25 | }
26 |
27 | /**
28 | * @return mixed
29 | */
30 | public function name()
31 | {
32 | return $this->name;
33 | }
34 |
35 | /**
36 | * @return mixed[]
37 | */
38 | public function data()
39 | {
40 | return $this->data;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/_Simulation/EventCollection.php:
--------------------------------------------------------------------------------
1 | loop = $loop;
65 | $this->scenario = function() {};
66 | $this->events = [];
67 | $this->failureMessage = null;
68 | $this->startCallback = function() {};
69 | $this->stopCallback = function() {};
70 | $this->stopFlags = false;
71 | }
72 |
73 | /**
74 | *
75 | */
76 | public function __destruct()
77 | {
78 | $this->stop();
79 |
80 | unset($loop);
81 | unset($this->scenario);
82 | unset($this->events);
83 | unset($this->failureMessage);
84 | unset($this->startCallback);
85 | unset($this->stopCallback);
86 | unset($this->stopFlags);
87 | }
88 |
89 | /**
90 | * @param callable(SimulationInterface) $scenario
91 | */
92 | public function setScenario(callable $scenario)
93 | {
94 | $this->scenario = $scenario;
95 | }
96 |
97 | /**
98 | * @return callable(SimulationInterface)|null $scenario
99 | */
100 | public function getScenario()
101 | {
102 | return $this->scenario;
103 | }
104 |
105 | /**
106 | * @return LoopInterface
107 | */
108 | public function getLoop()
109 | {
110 | return $this->loop;
111 | }
112 |
113 | /**
114 | *
115 | */
116 | public function begin()
117 | {
118 | $this->start();
119 | }
120 |
121 | /**
122 | *
123 | */
124 | public function done()
125 | {
126 | $this->stop();
127 | }
128 |
129 | /**
130 | *
131 | */
132 | public function fail($message)
133 | {
134 | $this->failureMessage = $message;
135 | $this->stop();
136 | }
137 |
138 | /**
139 | * @param mixed $expected
140 | * @param mixed $actual
141 | * @param string $message
142 | */
143 | public function assertSame($expected, $actual, $message = "Assertion failed, expected \"%s\" got \"%s\"")
144 | {
145 | if ($expected !== $actual)
146 | {
147 | $stringExpected = (is_object($expected) || is_array($expected)) ? json_encode($expected) : (string) $expected;
148 | $stringActual = (is_object($actual) || is_array($actual)) ? json_encode($actual) : (string) $actual;
149 |
150 | $this->fail(sprintf($message, $stringExpected, $stringActual));
151 | }
152 | }
153 |
154 | /**
155 | * @param string $name
156 | * @param mixed $data
157 | */
158 | public function expect($name, $data = [])
159 | {
160 | $this->events[] = new Event($name, $data);
161 | }
162 |
163 | /**
164 | * @return Event[]
165 | */
166 | public function getExpectations()
167 | {
168 | return $this->events;
169 | }
170 |
171 | /**
172 | * @param callable $callable
173 | */
174 | public function onStart(callable $callable)
175 | {
176 | $this->startCallback = $callable;
177 | }
178 |
179 | /**
180 | * @param callable $callable
181 | */
182 | public function onStop(callable $callable)
183 | {
184 | $this->stopCallback = $callable;
185 | }
186 |
187 | /**
188 | * @param string $model
189 | * @param mixed[] $config
190 | * @return object
191 | */
192 | public function reflect($model, $config = [])
193 | {
194 | foreach ($config as $key=>$value)
195 | {
196 | if ($value === 'Dazzle\Loop\Loop' || $value === 'Dazzle\Loop\LoopInterface')
197 | {
198 | $config[$key] = $this->getLoop();
199 | }
200 | }
201 |
202 | return (new ReflectionClass($model))->newInstanceArgs($config);
203 | }
204 |
205 | /**
206 | * @throws Exception
207 | */
208 | private function start()
209 | {
210 | $sim = $this;
211 |
212 | $scenario = $this->scenario;
213 | $scenario($sim);
214 |
215 | if ($this->stopFlags === true)
216 | {
217 | return;
218 | }
219 |
220 | $onStart = $this->startCallback;
221 | $loop = $this->loop;
222 |
223 | $loop->onStart(function() use($sim, $onStart) {
224 | $onStart($sim);
225 | });
226 | $loop->addTimer(5, function() use($sim) {
227 | $sim->fail('Timeout for test has been reached.');
228 | });
229 |
230 | $loop->start();
231 |
232 | if ($sim->failureMessage !== null)
233 | {
234 | throw new Exception($sim->failureMessage);
235 | }
236 | }
237 |
238 | /**
239 | *
240 | */
241 | private function stop()
242 | {
243 | if ($this->loop !== null && $this->loop->isRunning())
244 | {
245 | $this->loop->stop();
246 | }
247 |
248 | if ($this->stopFlags === false)
249 | {
250 | $callable = $this->stopCallback;
251 | $callable($this);
252 | }
253 |
254 | $this->stopFlags = true;
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/test/_Simulation/SimulationInterface.php:
--------------------------------------------------------------------------------
1 |