Skip to content

Commit 5b55be7

Browse files
committed
Cleaning up Server
1 parent e1507f8 commit 5b55be7

File tree

4 files changed

+162
-63
lines changed

4 files changed

+162
-63
lines changed

README.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ It does not include convenience operations such as listeners and implicit error
1010

1111
## Documentation
1212

13-
- [Client](docs/Client.md)
14-
- [Server](docs/Server.md)
15-
- [Message](docs/Message.md)
13+
- [Client overwiew](docs/Client.md)
14+
- [Server overview](docs/Server.md)
15+
- [Classes](docs/Classes/Classes.md)
1616
- [Examples](docs/Examples.md)
1717
- [Changelog](docs/Changelog.md)
1818
- [Contributing](docs/Contributing.md)
@@ -25,8 +25,8 @@ composer require textalk/websocket
2525
```
2626

2727
* Current version support PHP versions `^7.2|8.0`.
28-
* For PHP `7.1` support use version `1.4`.
29-
* For PHP `^5.4` and `7.0` support use version `1.3`.
28+
* For PHP `7.1` support use version [`1.4`](https://github.com/Textalk/websocket-php/tree/1.4.0).
29+
* For PHP `^5.4` and `7.0` support use version [`1.3`](https://github.com/Textalk/websocket-php/tree/1.3.0).
3030

3131
## Client
3232

@@ -42,18 +42,17 @@ $client->close();
4242

4343
## Server
4444

45-
The library contains a rudimentary single stream/single thread [server](docs/Server.md).
45+
The library contains a websocket [server](docs/Server.md).
4646
It internally supports Upgrade handshake and implicit close and ping/pong operations.
47-
48-
Note that it does **not** support threading or automatic association ot continuous client requests.
49-
If you require this kind of server behavior, you need to build it on top of provided server implementation.
47+
Preferred operation is using the listener function, but optional operations exist.
5048

5149
```php
5250
$server = new WebSocket\Server();
53-
$server->accept();
54-
$message = $server->receive();
55-
$server->text($message);
56-
$server->close();
51+
$server->listen(function ($message, $connection = null) {
52+
echo "Got {$message->getContent()}\n";
53+
if (!$connection) return; // Current connection is closed
54+
$connection->text('Sending message to client');
55+
});
5756
```
5857

5958
### License and Contributors

docs/Changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
# Websocket: Changelog
44

5+
## `v1.6`
6+
7+
> PHP version `^7.2`
8+
9+
### `1.6.0`
10+
11+
* Listener functions (@sirn-se)
12+
* Multi connection server (@sirn-se)
13+
* Major refactoring, using Connections (@sirn-se)
14+
515
## `v1.5`
616

717
> PHP version `^7.2`

examples/echoserver.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
echo "> Using logger\n";
3333
}
3434

35-
// Setting timeout to 200 seconds to make time for all tests and manual runs.
35+
// Initiate server
3636
try {
3737
$server = new Server($options);
3838
} catch (ConnectionException $e) {
@@ -42,21 +42,21 @@
4242

4343
echo "> Listening to port {$server->getPort()}\n";
4444

45-
$server->listen(function ($message, $connection) use ($server) {
45+
$server->listen(function ($message, $connection = null) use ($server) {
4646
$content = $message->getContent();
4747
$opcode = $message->getOpcode();
4848
$peer = $connection ? $connection->getPeer() : '(closed)';
4949
echo "> Got '{$content}' [opcode: {$opcode}, peer: {$peer}]\n";
5050

5151
// Connection closed, can't respond
5252
if (!$connection) {
53-
return null; // Continue listening
53+
return; // Continue listening
5454
}
5555

5656
if (in_array($opcode, ['ping', 'pong'])) {
5757
$connection->text($content);
5858
echo "< Sent '{$content}' [opcode: text, peer: {$peer}]\n";
59-
return null; // Continue listening
59+
return; // Continue listening
6060
}
6161

6262
// Allow certain string to trigger server action
@@ -71,7 +71,7 @@
7171
echo "< Sent '{$content}' [opcode: close, peer: {$peer}]\n";
7272
break;
7373
case 'exit':
74-
echo "> Client told me to quit. Bye bye.\n";
74+
echo "> Client told me to quit.\n";
7575
$server->close();
7676
return true; // Stop listener
7777
case 'headers':
@@ -87,6 +87,10 @@
8787
$connection->pong($content);
8888
echo "< Sent '{$content}' [opcode: pong, peer: {$peer}]\n";
8989
break;
90+
case 'stop':
91+
$server->stop();
92+
echo "> Client told me to stop listening.\n";
93+
break;
9094
default:
9195
$connection->text($content);
9296
echo "< Sent '{$content}' [opcode: text, peer: {$peer}]\n";

lib/Server.php

Lines changed: 131 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace WebSocket;
1111

1212
use Closure;
13+
use Psr\Log\NullLogger;
1314
use Throwable;
1415

1516
class Server extends Base
@@ -24,23 +25,31 @@ class Server extends Base
2425
'timeout' => null,
2526
];
2627

27-
protected $addr;
2828
protected $port;
2929
protected $listening;
3030
protected $request;
3131
protected $request_path;
32-
private $connectors = [];
32+
private $connections = [];
33+
private $listen = false;
34+
35+
36+
/* ---------- Construct & Destruct ----------------------------------------------- */
3337

3438
/**
3539
* @param array $options
3640
* Associative array containing:
37-
* - timeout: Set the socket timeout in seconds.
41+
* - filter: Array of opcodes to handle. Default: ['text', 'binary'].
3842
* - fragment_size: Set framgemnt size. Default: 4096
39-
* - port: Chose port for listening. Default 8000.
43+
* - logger: PSR-3 compatible logger. Default NullLogger.
44+
* - port: Chose port for listening. Default 8000.
45+
* - return_obj: If receive() function return Message instance. Default false.
46+
* - timeout: Set the socket timeout in seconds.
4047
*/
4148
public function __construct(array $options = [])
4249
{
43-
$this->options = array_merge(self::$default_options, $options);
50+
$this->options = array_merge(self::$default_options, [
51+
'logger' => new NullLogger(),
52+
], $options);
4453
$this->port = $this->options['port'];
4554
$this->setLogger($this->options['logger']);
4655

@@ -65,57 +74,40 @@ public function __construct(array $options = [])
6574
$this->logger->info("Server listening to port {$this->port}");
6675
}
6776

77+
/**
78+
* Disconnect streams on shutdown.
79+
*/
6880
public function __destruct()
6981
{
82+
/*
7083
if ($this->connection && $this->connection->isConnected()) {
7184
$this->connection->disconnect();
7285
}
7386
$this->connection = null;
74-
}
75-
76-
public function getPort(): int
77-
{
78-
return $this->port;
79-
}
80-
81-
public function getPath(): string
82-
{
83-
return $this->request_path;
84-
}
85-
86-
public function getRequest(): array
87-
{
88-
return $this->request;
89-
}
90-
91-
public function getHeader($header): ?string
92-
{
93-
foreach ($this->request as $row) {
94-
if (stripos($row, $header) !== false) {
95-
list($headername, $headervalue) = explode(":", $row);
96-
return trim($headervalue);
87+
*/
88+
foreach ($this->connections as $connection) {
89+
if ($connection->isConnected()) {
90+
$connection->disconnect();
9791
}
9892
}
99-
return null;
93+
$this->connections = [];
10094
}
10195

102-
public function accept(): bool
103-
{
104-
$this->connection = null;
105-
return (bool)$this->listening;
106-
}
96+
97+
/* ---------- Server operations -------------------------------------------------- */
10798

10899
/**
109100
* Set server to listen to incoming requests.
110101
* @param Closure A callback function that will be called when server receives message.
111102
* function (Message $message, Connection $connection = null)
112-
* If callback function returns not null value, the listener will halt and return that value.
103+
* If callback function returns non-null value, the listener will halt and return that value.
113104
* Otherwise it will continue listening and propagating messages.
114-
* @return Returns any not null value returned by callback function.
105+
* @return mixed Returns any non-null value returned by callback function.
115106
*/
116107
public function listen(Closure $callback)
117108
{
118-
while (true) {
109+
$this->listen = true;
110+
while ($this->listen) {
119111
// Server accept
120112
if ($stream = @stream_socket_accept($this->listening, 0)) {
121113
$peer = stream_socket_get_name($stream, true);
@@ -126,18 +118,18 @@ public function listen(Closure $callback)
126118
$connection->setTimeout($this->options['timeout']);
127119
}
128120
$this->performHandshake($connection);
129-
$this->connectors[$peer] = $connection;
121+
$this->connections[$peer] = $connection;
130122
}
131123

132124
// Collect streams to listen to
133125
$streams = array_filter(array_map(function ($connection, $peer) {
134126
$stream = $connection->getStream();
135127
if (is_null($stream)) {
136128
$this->logger->debug("[server] Remove {$peer} from listener stack");
137-
unset($this->connectors[$peer]);
129+
unset($this->connections[$peer]);
138130
}
139131
return $stream;
140-
}, $this->connectors, array_keys($this->connectors)));
132+
}, $this->connections, array_keys($this->connections)));
141133

142134
// Handle incoming
143135
if (!empty($streams)) {
@@ -149,11 +141,15 @@ public function listen(Closure $callback)
149141
try {
150142
$result = null;
151143
$peer = stream_socket_get_name($stream, true);
152-
$connection = $this->connectors[$peer];
144+
if (empty($peer)) {
145+
$this->logger->warning("[server] Got detached stream '{$peer}'");
146+
continue;
147+
}
148+
$connection = $this->connections[$peer];
153149
$this->logger->debug("[server] Handling {$peer}");
154150
$message = $connection->pullMessage();
155151
if (!$connection->isConnected()) {
156-
unset($this->connectors[$peer]);
152+
unset($this->connections[$peer]);
157153
$connection = null;
158154
}
159155
// Trigger callback according to filter
@@ -175,14 +171,102 @@ public function listen(Closure $callback)
175171
}
176172
}
177173

174+
/**
175+
* Tell server to stop listening to incoming requests.
176+
* Active connections are still available when restarting listening.
177+
*/
178+
public function stop(): void
179+
{
180+
$this->listen = false;
181+
}
182+
183+
/**
184+
* Accept an incoming request.
185+
* Note that this operation will block accepting additional requests.
186+
* @return bool True if listening
187+
* @deprecated Will be removed in future version
188+
*/
189+
public function accept(): bool
190+
{
191+
$this->connection = null;
192+
return (bool)$this->listening;
193+
}
194+
195+
196+
/* ---------- Server option functions -------------------------------------------- */
197+
198+
/**
199+
* Get current port.
200+
* @return int port
201+
*/
202+
public function getPort(): int
203+
{
204+
return $this->port;
205+
}
206+
207+
// Inherited from Base:
208+
// - setLogger
209+
// - setTimeout
210+
// - setFragmentSize
211+
// - getFragmentSize
212+
213+
214+
/* ---------- Connection broadcast operations ------------------------------------ */
215+
216+
/**
217+
* Close all connections.
218+
* @param int Close status, default: 1000
219+
* @param string Close message, default: 'ttfn'
220+
*/
178221
public function close(int $status = 1000, string $message = 'ttfn'): void
179222
{
180-
parent::close($status, $message);
181-
foreach ($this->connectors as $connection) {
182-
$connection->close($status, $message);
223+
foreach ($this->connections as $connection) {
224+
if ($connection->isConnected()) {
225+
$connection->close($status, $message);
226+
}
227+
}
228+
}
229+
230+
// Inherited from Base:
231+
// - receive
232+
// - send
233+
// - text, binary, ping, pong
234+
235+
236+
/* ---------- Connection functions (all deprecated) ------------------------------ */
237+
238+
public function getPath(): string
239+
{
240+
return $this->request_path;
241+
}
242+
243+
public function getRequest(): array
244+
{
245+
return $this->request;
246+
}
247+
248+
public function getHeader($header): ?string
249+
{
250+
foreach ($this->request as $row) {
251+
if (stripos($row, $header) !== false) {
252+
list($headername, $headervalue) = explode(":", $row);
253+
return trim($headervalue);
254+
}
183255
}
256+
return null;
184257
}
185258

259+
// Inherited from Base:
260+
// - getLastOpcode
261+
// - getCloseStatus
262+
// - isConnected
263+
// - disconnect
264+
// - getName, getPeer, getPier
265+
266+
267+
/* ---------- Helper functions --------------------------------------------------- */
268+
269+
// Connect when read/write operation is performed.
186270
protected function connect(): void
187271
{
188272
$error = null;
@@ -215,8 +299,10 @@ protected function connect(): void
215299
'pier' => $this->connection->getPeer(),
216300
]);
217301
$this->performHandshake($this->connection);
302+
$this->connections = ['*' => $this->connection];
218303
}
219304

305+
// Perform upgrade handshake on new connections.
220306
protected function performHandshake(Connection $connection): void
221307
{
222308
$request = '';

0 commit comments

Comments
 (0)