Yii2 WebSocketServer
Used Ratchet
The preferred way to install this extension is through composer.
Either run
composer require consik/yii2-websocket
or add
"consik/yii2-websocket": "^1.0"
int $port = 8080
- Port number for websocket serverbool $closeConnectionOnError = true
- Close connection or not when error occurs with itbool $runClientCommands = true
- Check client's messages for commands or notnull|IoServer $server = null
- IOServer objectnull|\SplObjectStorage $clients = null
- Storage of connected clients
- EVENT_WEBSOCKET_OPEN
Class yii\base\Event - Triggered when binding is successfully completed
- EVENT_WEBSOCKET_CLOSE
Class yii\base\Event - Triggered when socket listening is closed
- EVENT_WEBSOCKET_OPEN_ERROR
Class events\ExceptionEvent - Triggered when throwed Exception on binding socket
- EVENT_CLIENT_CONNECTED
Class events\WSClientEvent - Triggered when client connected to the server
- EVENT_CLIENT_DISCONNECTED
Class events\WSClientEvent - Triggered when client close connection with server
- EVENT_CLIENT_ERROR
Class events\WSClientErrorEvent - Triggered when an error occurs on a Connection
- EVENT_CLIENT_MESSAGE
Class events\WSClientMessageEvent - Triggered when message recieved from client
- EVENT_CLIENT_RUN_COMMAND
Class events\WSClientCommandEvent - Triggered when controller starts user's command
- EVENT_CLIENT_END_COMMAND
Class events\WSClientCommandEvent - Triggered when controller finished user's command
Create your server class based on WebSocketServer. For example daemons\EchoServer.php
:
<?php
namespace app\daemons;
use consik\yii2websocket\events\WSClientMessageEvent;
use consik\yii2websocket\WebSocketServer;
class EchoServer extends WebSocketServer
{
public function init()
{
parent::init();
$this->on(self::EVENT_CLIENT_MESSAGE, function (WSClientMessageEvent $e) {
$e->client->send( $e->message );
});
}
}
Create yii2 console controller for starting server:
<?php
namespace app\commands;
use app\daemons\EchoServer;
use yii\console\Controller;
class ServerController extends Controller
{
public function actionStart($port = null)
{
$server = new EchoServer();
if ($port) {
$server->port = $port;
}
$server->start();
}
}
Start your server using console:
php yii server/start
Now let's check our server via js connection:
var conn = new WebSocket('ws://localhost:8080');
conn.onmessage = function(e) {
console.log('Response:' + e.data);
};
conn.onopen = function(e) {
console.log("Connection established!");
console.log('Hey!');
conn.send('Hey!');
};
Console result must be:
Connection established!
Hey!
Response:Hey!
Now we try handle socket binding error and open it on other port, when error occurs;
Create yii2 console controller for starting server:
<?php
namespace app\commands;
use consik\yii2websocket\WebSocketServer;
use yii\console\Controller;
class ServerController extends Controller
{
public function actionStart()
{
$server = new WebSocketServer();
$server->port = 80; //This port must be busy by WebServer and we handle an error
$server->on(WebSocketServer::EVENT_WEBSOCKET_OPEN_ERROR, function($e) use($server) {
echo "Error opening port " . $server->port . "\n";
$server->port += 1; //Try next port to open
$server->start();
});
$server->on(WebSocketServer::EVENT_WEBSOCKET_OPEN, function($e) use($server) {
echo "Server started at port " . $server->port;
});
$server->start();
}
}
Start your server using console command:
php yii server/start
Server console result must be:
Error opening port 80
Server started at port 81
You can implement methods that will be runned after some of user messages automatically;
Server class daemons\CommandsServer.php
:
<?php
namespace app\daemons;
use consik\yii2websocket\WebSocketServer;
use Ratchet\ConnectionInterface;
class CommandsServer extends WebSocketServer
{
/**
* override method getCommand( ... )
*
* For example, we think that all user's message is a command
*/
protected function getCommand(ConnectionInterface $from, $msg)
{
return $msg;
}
/**
* Implement command's method using "command" as prefix for method name
*
* method for user's command "ping"
*/
function commandPing(ConnectionInterface $client, $msg)
{
$client->send('Pong');
}
}
Run the server like in examples above
Check connection and command working by js script:
var conn = new WebSocket('ws://localhost:8080');
conn.onmessage = function(e) {
console.log('Response:' + e.data);
};
conn.onopen = function(e) {
console.log('ping');
conn.send('ping');
};
Console result must be:
ping
Response:Pong
In the end let's make simple chat with sending messages and function to change username;
Code without comments, try to understand it by youself ;)
- Server class
daemons\ChatServer.php
:
<?php
namespace app\daemons;
use consik\yii2websocket\events\WSClientEvent;
use consik\yii2websocket\WebSocketServer;
use Ratchet\ConnectionInterface;
class ChatServer extends WebSocketServer
{
public function init()
{
parent::init();
$this->on(self::EVENT_CLIENT_CONNECTED, function(WSClientEvent $e) {
$e->client->name = null;
});
}
protected function getCommand(ConnectionInterface $from, $msg)
{
$request = json_decode($msg, true);
return !empty($request['action']) ? $request['action'] : parent::getCommand($from, $msg);
}
public function commandChat(ConnectionInterface $client, $msg)
{
$request = json_decode($msg, true);
$result = ['message' => ''];
if (!$client->name) {
$result['message'] = 'Set your name';
} elseif (!empty($request['message']) && $message = trim($request['message']) ) {
foreach ($this->clients as $chatClient) {
$chatClient->send( json_encode([
'type' => 'chat',
'from' => $client->name,
'message' => $message
]) );
}
} else {
$result['message'] = 'Enter message';
}
$client->send( json_encode($result) );
}
public function commandSetName(ConnectionInterface $client, $msg)
{
$request = json_decode($msg, true);
$result = ['message' => 'Username updated'];
if (!empty($request['name']) && $name = trim($request['name'])) {
$usernameFree = true;
foreach ($this->clients as $chatClient) {
if ($chatClient != $client && $chatClient->name == $name) {
$result['message'] = 'This name is used by other user';
$usernameFree = false;
break;
}
}
if ($usernameFree) {
$client->name = $name;
}
} else {
$result['message'] = 'Invalid username';
}
$client->send( json_encode($result) );
}
}
- Simple html form
chat.html
:
Username:<br />
<input id="username" type="text"><button id="btnSetUsername">Set username</button>
<div id="chat" style="width:400px; height: 250px; overflow: scroll;"></div>
Message:<br />
<input id="message" type="text"><button id="btnSend">Send</button>
<div id="response" style="color:#D00"></div>
- JS code for chat with jQuery:
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script>
$(function() {
var chat = new WebSocket('ws://localhost:8080');
chat.onmessage = function(e) {
$('#response').text('');
var response = JSON.parse(e.data);
if (response.type && response.type == 'chat') {
$('#chat').append('<div><b>' + response.from + '</b>: ' + response.message + '</div>');
$('#chat').scrollTop = $('#chat').height;
} else if (response.message) {
$('#response').text(response.message);
}
};
chat.onopen = function(e) {
$('#response').text("Connection established! Please, set your username.");
};
$('#btnSend').click(function() {
if ($('#message').val()) {
chat.send( JSON.stringify({'action' : 'chat', 'message' : $('#message').val()}) );
} else {
alert('Enter the message')
}
})
$('#btnSetUsername').click(function() {
if ($('#username').val()) {
chat.send( JSON.stringify({'action' : 'setName', 'name' : $('#username').val()}) );
} else {
alert('Enter username')
}
})
})
</script>
Enjoy ;)
Starting yii2 console application as daemon using nohup
nohup php yii _ControllerName_/_ActionName_ &