├── LICENSE
├── README.md
├── WebSocketServer.php
├── composer.json
└── events
├── ExceptionEvent.php
├── WSClientCommandEvent.php
├── WSClientErrorEvent.php
├── WSClientEvent.php
└── WSClientMessageEvent.php
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Sergey Poltaranin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Yii2 [WebSocketServer](/WebSocketServer.php)
2 |
3 | [](https://packagist.org/packages/consik/yii2-websocket)
4 | [](https://packagist.org/packages/consik/yii2-websocket)
5 | [](https://packagist.org/packages/consik/yii2-websocket)
6 |
7 | Used [Ratchet](http://socketo.me/)
8 |
9 | ## Installation
10 |
11 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
12 |
13 | Either run
14 |
15 | ```
16 | composer require consik/yii2-websocket
17 | ```
18 |
19 | or add
20 |
21 | ```json
22 | "consik/yii2-websocket": "^1.0"
23 | ```
24 |
25 | ## WebSocketServer class description
26 |
27 | ### Properties
28 |
29 | 1. ``` int $port = 8080``` - Port number for websocket server
30 | 2. ``` bool $closeConnectionOnError = true``` - Close connection or not when error occurs with it
31 | 3. ``` bool $runClientCommands = true``` - Check client's messages for commands or not
32 | 4. ``` null|IoServer $server = null``` - IOServer object
33 | 5. ``` null|\SplObjectStorage $clients = null``` - Storage of connected clients
34 |
35 | ### Methods
36 |
37 | ### Events
38 |
39 | * EVENT_WEBSOCKET_OPEN
40 |
41 | > **Class** yii\base\Event -
42 | Triggered when binding is successfully completed
43 |
44 | * EVENT_WEBSOCKET_CLOSE
45 |
46 | > **Class** yii\base\Event -
47 | Triggered when socket listening is closed
48 |
49 | * EVENT_WEBSOCKET_OPEN_ERROR
50 |
51 | > **Class** [events\ExceptionEvent](/events/ExceptionEvent.php) -
52 | Triggered when throwed Exception on binding socket
53 |
54 | * EVENT_CLIENT_CONNECTED
55 |
56 | > **Class** [events\WSClientEvent](/events/WSClientEvent.php) -
57 | Triggered when client connected to the server
58 |
59 | * EVENT_CLIENT_DISCONNECTED
60 |
61 | > **Class** [events\WSClientEvent](/events/WSClientEvent.php) -
62 | Triggered when client close connection with server
63 |
64 | * EVENT_CLIENT_ERROR
65 |
66 | > **Class** [events\WSClientErrorEvent](/events/WSClientErrorEvent.php) -
67 | Triggered when an error occurs on a Connection
68 |
69 | * EVENT_CLIENT_MESSAGE
70 |
71 | > **Class** [events\WSClientMessageEvent](/events/WSClientMessageEvent.php) -
72 | Triggered when message recieved from client
73 |
74 | * EVENT_CLIENT_RUN_COMMAND
75 |
76 | > **Class** [events\WSClientCommandEvent](/events/WSClientCommandEvent.php) -
77 | Triggered when controller starts user's command
78 |
79 | * EVENT_CLIENT_END_COMMAND
80 |
81 | > **Class** [events\WSClientCommandEvent](/events/WSClientCommandEvent.php) -
82 | Triggered when controller finished user's command
83 |
84 | ## Examples
85 |
86 | ### Simple echo server
87 |
88 | Create your server class based on WebSocketServer. For example ```daemons\EchoServer.php```:
89 |
90 | ```php
91 | on(self::EVENT_CLIENT_MESSAGE, function (WSClientMessageEvent $e) {
105 | $e->client->send( $e->message );
106 | });
107 | }
108 |
109 | }
110 | ```
111 |
112 | Create yii2 console controller for starting server:
113 |
114 | ```php
115 | port = $port;
128 | }
129 | $server->start();
130 | }
131 | }
132 | ```
133 |
134 | Start your server using console:
135 |
136 | > php yii server/start
137 |
138 | Now let's check our server via js connection:
139 |
140 | ```javascript
141 | var conn = new WebSocket('ws://localhost:8080');
142 | conn.onmessage = function(e) {
143 | console.log('Response:' + e.data);
144 | };
145 | conn.onopen = function(e) {
146 | console.log("Connection established!");
147 | console.log('Hey!');
148 | conn.send('Hey!');
149 | };
150 | ```
151 |
152 | Console result must be:
153 |
154 | > Connection established!
155 |
156 | > Hey!
157 |
158 | > Response:Hey!
159 |
160 | ### Handle server starting success and error events
161 |
162 | Now we try handle socket binding error and open it on other port, when error occurs;
163 |
164 | Create yii2 console controller for starting server:
165 |
166 | ```php
167 | port = 80; //This port must be busy by WebServer and we handle an error
179 |
180 | $server->on(WebSocketServer::EVENT_WEBSOCKET_OPEN_ERROR, function($e) use($server) {
181 | echo "Error opening port " . $server->port . "\n";
182 | $server->port += 1; //Try next port to open
183 | $server->start();
184 | });
185 |
186 | $server->on(WebSocketServer::EVENT_WEBSOCKET_OPEN, function($e) use($server) {
187 | echo "Server started at port " . $server->port;
188 | });
189 |
190 | $server->start();
191 | }
192 | }
193 | ```
194 |
195 | Start your server using console command:
196 |
197 | > php yii server/start
198 |
199 | Server console result must be:
200 |
201 | > Error opening port 80
202 |
203 | > Server started at port 81
204 |
205 | ### Recieving client commands
206 |
207 | You can implement methods that will be runned after some of user messages automatically;
208 |
209 | Server class ```daemons\CommandsServer.php```:
210 |
211 | ```php
212 | send('Pong');
239 | }
240 |
241 | }
242 | ```
243 |
244 | Run the server like in examples above
245 |
246 | Check connection and command working by js script:
247 |
248 | ```javascript
249 | var conn = new WebSocket('ws://localhost:8080');
250 | conn.onmessage = function(e) {
251 | console.log('Response:' + e.data);
252 | };
253 | conn.onopen = function(e) {
254 | console.log('ping');
255 | conn.send('ping');
256 | };
257 | ```
258 |
259 | Console result must be:
260 |
261 | > ping
262 |
263 | > Response:Pong
264 |
265 | ### Chat example
266 |
267 | In the end let's make simple chat with sending messages and function to change username;
268 |
269 | Code without comments, try to understand it by youself ;)
270 |
271 | * Server class ```daemons\ChatServer.php```:
272 |
273 | ```php
274 | on(self::EVENT_CLIENT_CONNECTED, function(WSClientEvent $e) {
289 | $e->client->name = null;
290 | });
291 | }
292 |
293 |
294 | protected function getCommand(ConnectionInterface $from, $msg)
295 | {
296 | $request = json_decode($msg, true);
297 | return !empty($request['action']) ? $request['action'] : parent::getCommand($from, $msg);
298 | }
299 |
300 | public function commandChat(ConnectionInterface $client, $msg)
301 | {
302 | $request = json_decode($msg, true);
303 | $result = ['message' => ''];
304 |
305 | if (!$client->name) {
306 | $result['message'] = 'Set your name';
307 | } elseif (!empty($request['message']) && $message = trim($request['message']) ) {
308 | foreach ($this->clients as $chatClient) {
309 | $chatClient->send( json_encode([
310 | 'type' => 'chat',
311 | 'from' => $client->name,
312 | 'message' => $message
313 | ]) );
314 | }
315 | } else {
316 | $result['message'] = 'Enter message';
317 | }
318 |
319 | $client->send( json_encode($result) );
320 | }
321 |
322 | public function commandSetName(ConnectionInterface $client, $msg)
323 | {
324 | $request = json_decode($msg, true);
325 | $result = ['message' => 'Username updated'];
326 |
327 | if (!empty($request['name']) && $name = trim($request['name'])) {
328 | $usernameFree = true;
329 | foreach ($this->clients as $chatClient) {
330 | if ($chatClient != $client && $chatClient->name == $name) {
331 | $result['message'] = 'This name is used by other user';
332 | $usernameFree = false;
333 | break;
334 | }
335 | }
336 |
337 | if ($usernameFree) {
338 | $client->name = $name;
339 | }
340 | } else {
341 | $result['message'] = 'Invalid username';
342 | }
343 |
344 | $client->send( json_encode($result) );
345 | }
346 |
347 | }
348 | ```
349 |
350 | * Simple html form ```chat.html```:
351 |
352 | ```html
353 | Username:
354 |
355 |
356 |