Skip to content

Commit

Permalink
Merge pull request #14 from exodus4d/develop
Browse files Browse the repository at this point in the history
v1.2.1
  • Loading branch information
exodus4d authored Jul 20, 2019
2 parents cd2ca80 + b96e95c commit 71f77bb
Show file tree
Hide file tree
Showing 13 changed files with 1,529 additions and 347 deletions.
45 changes: 38 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## WebSocket server for [Pathfinder](https://github.com/exodus4d/pathfinder)

### Requirements
- _PHP_ **(≥ v7.1)**
- A working instance of *[Pathfinder](https://github.com/exodus4d/pathfinder)* **(≥ v1.2.0)**
- [_Composer_](https://getcomposer.org/download/) to install packages for the WebSocket server

Expand All @@ -19,20 +20,49 @@
**Clients (WebBrowser) listen for connections**
- Host: `0.0.0.0.` (=> any client can connect)
- Port: `8020`
- URI: `127.0.0.1:8020` (Your WebServer (e.g. Nginx) should pass all WebSocket connections to this source)
- URI: `127.0.0.1:8020`

**TCP TcpSocket connection (Internal use for WebServer ⇄ WebSocket communication)**
- Host: `127.0.0.1` (=> Assumed WebServer and WebSocket Server running on the same machine)
(=> Your WebServer (e.g. Nginx) should proxy all WebSocket connections to this source)

**TCP TcpSocket connection (Internal use for WebServer ⇄ WebSocket server communication)**
- Host: `127.0.0.1` (=> Assumed WebServer and WebSocket server running on the same machine)
- Port: `5555`
- URI: `tcp://127.0.0.1:5555`
- ↪ URI: `tcp://127.0.0.1:5555`

(=> Where _Pathfinder_ reaches the WebSocket server. This must match `SOCKET_HOST`, `SOCKET_PORT` options in `environment.ini`)

#### Custom [Optional]
#### Start parameters [Optional]

The default configuration should be fine for most installations.
You can change/overwrite the default **Host** and **Port** configuration by adding additional CLI parameters when starting the WebSocket server:

`$ php cmd.php --pf_listen_host [CLIENTS_HOST] --pf_listen_port [CLIENTS_PORT] --pf_host [TCP_HOST] --pf_port [TCP_PORT]`
`$ php cmd.php --wsHost [CLIENTS_HOST] --wsPort [CLIENTS_PORT] --tcpHost [TCP_HOST] --tcpPort [TCP_PORT] --debug 0`

For example: If you want to change the the WebSocket port and increase debug output:

`$ php cmd.php --wsPort 8030 --debug 3`

##### --debug (default `--debug 2`)

Allows you to set log output level from `0` (silent) - errors are not logged, to `3` (debug) for detailed logging.

![alt text](https://i.imgur.com/KfNF4lk.png)

### WebSocket UI

There is a WebSocket section on _Pathinders_ `/setup` page. After the WebSocket server is started, you should check it if everything works.
You see the most recent WebSocket log entries, the current connection state, the current number of active connections and all maps that have subscriptions

![alt text](https://i.imgur.com/dDUrnx2.png)

Log entry view. Depending on the `--debug` parameter, the most recent (max 50) entries will be shown:

![alt text](https://i.imgur.com/LIn9aNm.png)

Subscriptions for each map:

![alt text](https://i.imgur.com/fANYwho.gif)

### Unix Service (systemd)

#### New Service
Expand Down Expand Up @@ -97,4 +127,5 @@ ExecStart = /usr/bin/systemctl try-restart websocket.pathfinder.service
```

### Info
- [*Ratchet*](http://socketo.me/) - "WebSockets for PHP"
- [*Ratchet*](http://socketo.me) - "WebSockets for PHP"
- [*ReactPHP*](https://reactphp.org) - "Event-driven, non-blocking I/O with PHP"
269 changes: 269 additions & 0 deletions app/Component/AbstractMessageComponent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
<?php


namespace Exodus4D\Socket\Component;


use Exodus4D\Socket\Data\Payload;
use Exodus4D\Socket\Log\Store;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
use React\EventLoop\TimerInterface;

abstract class AbstractMessageComponent implements MessageComponentInterface {

/**
* unique name for this component
* -> should be overwritten in child instances
* -> is used as "log store" name
*/
const COMPONENT_NAME = 'default';

/**
* log message server start
*/
const LOG_TEXT_SERVER_START = 'start WebSocket server…';

/**
* store for logs
* @var Store
*/
protected $logStore;

/**
* stores all active connections
* -> regardless of its subscription state
* [
* '$conn1->resourceId' => [
* 'connection' => $conn1,
* 'data' => null
* ],
* '$conn2->resourceId' => [
* 'connection' => $conn2,
* 'data' => null
* ]
* ]
* @var array
*/
private $connections;

/**
* max count of concurrent open connections
* @var int
*/
private $maxConnections = 0;

/**
* AbstractMessageComponent constructor.
* @param Store $store
*/
public function __construct(Store $store){
$this->connections = [];
$this->logStore = $store;

$this->log(['debug', 'info'], null, 'START', static::LOG_TEXT_SERVER_START);
}

// Connection callbacks from MessageComponentInterface ============================================================

/**
* new client connection onOpen
* @param ConnectionInterface $conn
*/
public function onOpen(ConnectionInterface $conn){
$this->log(['debug'], $conn, __FUNCTION__, 'open connection');

$this->addConnection($conn);
}

/**
* client connection onClose
* @param ConnectionInterface $conn
*/
public function onClose(ConnectionInterface $conn){
$this->log(['debug'], $conn, __FUNCTION__, 'close connection');

$this->removeConnection($conn);
}

/**
* client connection onError
* @param ConnectionInterface $conn
* @param \Exception $e
*/
public function onError(ConnectionInterface $conn, \Exception $e){
$this->log(['debug', 'error'], $conn, __FUNCTION__, $e->getMessage());
}

/**
* new message received from client connection
* @param ConnectionInterface $conn
* @param string $msg
*/
public function onMessage(ConnectionInterface $conn, $msg){
// parse message into payload object
$payload = $this->getPayloadFromMessage($msg);

if($payload){
$this->dispatchWebSocketPayload($conn, $payload);
}
}

// Connection handling ============================================================================================

/**
* add connection
* @param ConnectionInterface $conn
*/
private function addConnection(ConnectionInterface $conn) : void {
$this->connections[$conn->resourceId] = [
'connection' => $conn,
];

$this->maxConnections = max(count($this->connections), $this->maxConnections);
}

/**
* remove connection
* @param ConnectionInterface $conn
*/
private function removeConnection(ConnectionInterface $conn) : void {
if($this->hasConnection($conn)){
unset($this->connections[$conn->resourceId]);
}
}

/**
* @param ConnectionInterface $conn
* @return bool
*/
protected function hasConnection(ConnectionInterface $conn) : bool {
return isset($this->connections[$conn->resourceId]);
}

/**
* @param int $resourceId
* @return bool
*/
protected function hasConnectionId(int $resourceId) : bool {
return isset($this->connections[$resourceId]);
}

/**
* @param int $resourceId
* @return ConnectionInterface|null
*/
protected function getConnection(int $resourceId) : ?ConnectionInterface {
return $this->hasConnectionId($resourceId) ? $this->connections[$resourceId]['connection'] : null;
}

/**
* update meta data for $conn
* @param ConnectionInterface $conn
*/
protected function updateConnection(ConnectionInterface $conn){
if($this->hasConnection($conn)){
$meta = [
'mTimeSend' => microtime(true)
];
$this->connections[$conn->resourceId]['data'] = array_merge($this->getConnectionData($conn), $meta);
}
}

/**
* get meta data from $conn
* @param ConnectionInterface $conn
* @return array
*/
protected function getConnectionData(ConnectionInterface $conn) : array {
$meta = [];
if($this->hasConnection($conn)){
$meta = (array)$this->connections[$conn->resourceId]['data'];
}
return $meta;
}

/**
* wrapper for ConnectionInterface->send()
* -> this stores some meta data to the $conn
* @param ConnectionInterface $conn
* @param $data
*/
protected function send(ConnectionInterface $conn, $data){
$conn->send($data);
$this->updateConnection($conn);
}

/**
* @param ConnectionInterface $conn
* @param Payload $payload
*/
abstract protected function dispatchWebSocketPayload(ConnectionInterface $conn, Payload $payload) : void;

/**
* get Payload class from client message
* @param mixed $msg
* @return Payload|null
*/
protected function getPayloadFromMessage($msg) : ?Payload {
$payload = null;
$msg = (array)json_decode($msg, true);

if(isset($msg['task'], $msg['load'])){
$payload = $this->newPayload((string)$msg['task'], $msg['load']);
}

return $payload;
}

/**
* @param string $task
* @param null $load
* @param array|null $characterIds
* @return Payload|null
*/
protected function newPayload(string $task, $load = null, ?array $characterIds = null) : ?Payload {
$payload = null;
try{
$payload = new Payload($task, $load, $characterIds);
}catch(\Exception $e){
$this->log(['debug', 'error'], null, __FUNCTION__, $e->getMessage());
}

return $payload;
}

/**
* get WebSocket stats data
* @return array
*/
public function getSocketStats() : array {
return [
'connections' => count($this->connections),
'maxConnections' => $this->maxConnections,
'logs' => array_reverse($this->logStore->getStore())
];
}

/**
* @param $logTypes
* @param ConnectionInterface|null $connection
* @param string $action
* @param string $message
*/
protected function log($logTypes, ?ConnectionInterface $connection, string $action, string $message = '') : void {
if($this->logStore){
$remoteAddress = $connection ? $connection->remoteAddress : null;
$resourceId = $connection ? $connection->resourceId : null;
$this->logStore->log($logTypes, $remoteAddress, $resourceId, $action, $message);
}
}

/**
*
* @param TimerInterface $timer
*/
public function housekeeping(TimerInterface $timer) : void {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Time: 13:09
*/

namespace Exodus4D\Socket\Main\Formatter;
namespace Exodus4D\Socket\Component\Formatter;


class SubscriptionFormatter{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Time: 17:02
*/

namespace Exodus4D\Socket\Main\Handler;
namespace Exodus4D\Socket\Component\Handler;


class LogFileHandler {
Expand Down
Loading

0 comments on commit 71f77bb

Please sign in to comment.