Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b1f1d6b

Browse files
committedApr 29, 2021
First look at per-session inter-thread channels
this is a working implementation of #52. I have my doubts that this gigantic refactor was worth the effort, because there's still lock contention on Snooze to consider; however, this does substantially reduce the problem described in pmmp/ext-pmmpthread#42, as well as reducing the overhead of inter-thread communication by removing the need to transmit session IDs. This currently relies on each end of the IPC channels to roundtrip session open/close notifications to setup/cleanup stuff; I'll try to improve this before landing this in main.
1 parent bd8b753 commit b1f1d6b

21 files changed

+726
-264
lines changed
 

‎src/server/Server.php

+9-21
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
use raklib\generic\SocketException;
2323
use raklib\protocol\ACK;
2424
use raklib\protocol\Datagram;
25-
use raklib\protocol\EncapsulatedPacket;
2625
use raklib\protocol\NACK;
2726
use raklib\protocol\Packet;
2827
use raklib\protocol\PacketSerializer;
@@ -152,10 +151,12 @@ public function tickProcessor() : void{
152151
* The below code is designed to allow co-op between sending and receiving to avoid slowing down either one
153152
* when high traffic is coming either way. Yielding will occur after 100 messages.
154153
*/
154+
$eventGenerator = $this->eventSource->process($this);
155155
do{
156156
$stream = !$this->shutdown;
157157
for($i = 0; $i < 100 && $stream && !$this->shutdown; ++$i){ //if we received a shutdown event, we don't care about any more messages from the event source
158-
$stream = $this->eventSource->process($this);
158+
$eventGenerator->next();
159+
$stream = $eventGenerator->valid();
159160
}
160161

161162
$socket = true;
@@ -327,13 +328,6 @@ public function getEventListener() : ServerEventListener{
327328
return $this->eventListener;
328329
}
329330

330-
public function sendEncapsulated(int $sessionId, EncapsulatedPacket $packet, bool $immediate = false) : void{
331-
$session = $this->sessions[$sessionId] ?? null;
332-
if($session !== null and $session->isConnected()){
333-
$session->addEncapsulatedToQueue($packet, $immediate);
334-
}
335-
}
336-
337331
public function sendRaw(string $address, int $port, string $payload) : void{
338332
try{
339333
$this->socket->writePacket($payload, $address, $port);
@@ -342,12 +336,6 @@ public function sendRaw(string $address, int $port, string $payload) : void{
342336
}
343337
}
344338

345-
public function closeSession(int $sessionId) : void{
346-
if(isset($this->sessions[$sessionId])){
347-
$this->sessions[$sessionId]->initiateDisconnect("server disconnect");
348-
}
349-
}
350-
351339
public function setName(string $name) : void{
352340
$this->name = $name;
353341
}
@@ -383,6 +371,10 @@ public function addRawPacketFilter(string $regex) : void{
383371
$this->rawPacketFilters[] = $regex;
384372
}
385373

374+
public function getSession(int $id) : ?SessionInterface{
375+
return $this->sessions[$id] ?? null;
376+
}
377+
386378
public function getSessionByAddress(InternetAddress $address) : ?Session{
387379
return $this->sessionsByAddress[$address->toString()] ?? null;
388380
}
@@ -411,9 +403,9 @@ private function removeSessionInternal(Session $session) : void{
411403
unset($this->sessionsByAddress[$session->getAddress()->toString()], $this->sessions[$session->getInternalId()]);
412404
}
413405

414-
public function openSession(Session $session) : void{
406+
public function openSession(Session $session) : SessionEventListener{
415407
$address = $session->getAddress();
416-
$this->eventListener->onClientConnect($session->getInternalId(), $address->ip, $address->port, $session->getID());
408+
return $this->eventListener->onClientConnect($session->getInternalId(), $address->ip, $address->port, $session->getID());
417409
}
418410

419411
private function checkSessions() : void{
@@ -429,10 +421,6 @@ private function checkSessions() : void{
429421
}
430422
}
431423

432-
public function notifyACK(Session $session, int $identifierACK) : void{
433-
$this->eventListener->onPacketAck($session->getInternalId(), $identifierACK);
434-
}
435-
436424
public function getName() : string{
437425
return $this->name;
438426
}

‎src/server/ServerEventListener.php

+1-9
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,9 @@
1919

2020
interface ServerEventListener{
2121

22-
public function onClientConnect(int $sessionId, string $address, int $port, int $clientID) : void;
23-
24-
public function onClientDisconnect(int $sessionId, string $reason) : void;
25-
26-
public function onPacketReceive(int $sessionId, string $packet) : void;
22+
public function onClientConnect(int $sessionId, string $address, int $port, int $clientID) : SessionEventListener;
2723

2824
public function onRawPacketReceive(string $address, int $port, string $payload) : void;
2925

30-
public function onPacketAck(int $sessionId, int $identifierACK) : void;
31-
3226
public function onBandwidthStatsUpdate(int $bytesSentDiff, int $bytesReceivedDiff) : void;
33-
34-
public function onPingMeasure(int $sessionId, int $pingMS) : void;
3527
}

‎src/server/ServerEventSource.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@
1919

2020
interface ServerEventSource{
2121

22-
public function process(ServerInterface $server) : bool;
22+
/**
23+
* @phpstan-return \Generator<int, null, void, void>
24+
*/
25+
public function process(ServerInterface $server) : \Generator;
2326
}

‎src/server/ServerInterface.php

+1-5
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,12 @@
1717

1818
namespace raklib\server;
1919

20-
use raklib\protocol\EncapsulatedPacket;
21-
2220
interface ServerInterface{
2321

24-
public function sendEncapsulated(int $sessionId, EncapsulatedPacket $packet, bool $immediate = false) : void;
22+
public function getSession(int $id) : ?SessionInterface;
2523

2624
public function sendRaw(string $address, int $port, string $payload) : void;
2725

28-
public function closeSession(int $sessionId) : void;
29-
3026
public function setName(string $name) : void;
3127

3228
public function setPortCheck(bool $value) : void;

‎src/server/Session.php

+21-9
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
use function microtime;
4040
use function ord;
4141

42-
class Session{
42+
class Session implements SessionInterface{
4343
public const MAX_SPLIT_PART_COUNT = 128;
4444
public const MAX_CONCURRENT_SPLIT_COUNT = 4;
4545

@@ -90,6 +90,8 @@ class Session{
9090
/** @var SendReliabilityLayer */
9191
private $sendLayer;
9292

93+
private ?SessionEventListener $eventListener = null;
94+
9395
public function __construct(Server $server, \Logger $logger, InternetAddress $address, int $clientId, int $mtuSize, int $internalId){
9496
if($mtuSize < self::MIN_MTU_SIZE){
9597
throw new \InvalidArgumentException("MTU size must be at least " . self::MIN_MTU_SIZE . ", got $mtuSize");
@@ -120,7 +122,9 @@ function(Datagram $datagram) : void{
120122
$this->sendPacket($datagram);
121123
},
122124
function(int $identifierACK) : void{
123-
$this->server->getEventListener()->onPacketAck($this->internalId, $identifierACK);
125+
if($this->eventListener !== null){
126+
$this->eventListener->onPacketAck($identifierACK);
127+
}
124128
}
125129
);
126130
}
@@ -195,7 +199,7 @@ private function queueConnectedPacket(ConnectedPacket $packet, int $reliability,
195199
$this->sendLayer->addEncapsulatedToQueue($encapsulated, $immediate);
196200
}
197201

198-
public function addEncapsulatedToQueue(EncapsulatedPacket $packet, bool $immediate) : void{
202+
public function sendEncapsulated(EncapsulatedPacket $packet, bool $immediate = false) : void{
199203
$this->sendLayer->addEncapsulatedToQueue($packet, $immediate);
200204
}
201205

@@ -231,7 +235,7 @@ private function handleEncapsulatedPacketRoute(EncapsulatedPacket $packet) : voi
231235
if($dataPacket->address->port === $this->server->getPort() or !$this->server->portChecking){
232236
$this->state = self::STATE_CONNECTED; //FINALLY!
233237
$this->isTemporal = false;
234-
$this->server->openSession($this);
238+
$this->eventListener = $this->server->openSession($this);
235239

236240
//$this->handlePong($dataPacket->sendPingTime, $dataPacket->sendPongTime); //can't use this due to system-address count issues in MCPE >.<
237241
$this->sendPing();
@@ -252,8 +256,8 @@ private function handleEncapsulatedPacketRoute(EncapsulatedPacket $packet) : voi
252256

253257
$this->handlePong($dataPacket->sendPingTime, $dataPacket->sendPongTime);
254258
}
255-
}elseif($this->state === self::STATE_CONNECTED){
256-
$this->server->getEventListener()->onPacketReceive($this->internalId, $packet->buffer);
259+
}elseif($this->eventListener !== null){
260+
$this->eventListener->onPacketReceive($packet->buffer);
257261
}else{
258262
//$this->logger->notice("Received packet before connection: " . bin2hex($packet->buffer));
259263
}
@@ -264,7 +268,9 @@ private function handleEncapsulatedPacketRoute(EncapsulatedPacket $packet) : voi
264268
*/
265269
private function handlePong(int $sendPingTime, int $sendPongTime) : void{
266270
$this->lastPingMeasure = $this->server->getRakNetTimeMS() - $sendPingTime;
267-
$this->server->getEventListener()->onPingMeasure($this->internalId, $this->lastPingMeasure);
271+
if($this->eventListener !== null){
272+
$this->eventListener->onPingMeasure($this->lastPingMeasure);
273+
}
268274
}
269275

270276
public function handlePacket(Packet $packet) : void{
@@ -288,7 +294,10 @@ public function initiateDisconnect(string $reason) : void{
288294
$this->state = self::STATE_DISCONNECTING;
289295
$this->disconnectionTime = microtime(true);
290296
$this->queueConnectedPacket(new DisconnectionNotification(), PacketReliability::RELIABLE_ORDERED, 0, true);
291-
$this->server->getEventListener()->onClientDisconnect($this->internalId, $reason);
297+
if($this->eventListener !== null){
298+
$this->eventListener->onDisconnect($reason);
299+
$this->eventListener = null;
300+
}
292301
$this->logger->debug("Requesting graceful disconnect because \"$reason\"");
293302
}
294303
}
@@ -298,7 +307,10 @@ public function initiateDisconnect(string $reason) : void{
298307
*/
299308
public function forciblyDisconnect(string $reason) : void{
300309
$this->state = self::STATE_DISCONNECTED;
301-
$this->server->getEventListener()->onClientDisconnect($this->internalId, $reason);
310+
if($this->eventListener !== null){
311+
$this->eventListener->onDisconnect($reason);
312+
$this->eventListener = null;
313+
}
302314
$this->logger->debug("Forcibly disconnecting session due to \"$reason\"");
303315
}
304316

‎src/server/SessionEventListener.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server;
25+
26+
interface SessionEventListener{
27+
28+
/**
29+
* Called when the client disconnects, or when RakLib terminates the connection (e.g. due to a timeout).
30+
*/
31+
public function onDisconnect(string $reason) : void;
32+
33+
/**
34+
* Called when a non-RakNet packet is received (user packet).
35+
*/
36+
public function onPacketReceive(string $payload) : void;
37+
38+
/**
39+
* Called when a packet that was sent with a requested ACK receipt is ACKed by the recipient.
40+
*/
41+
public function onPacketAck(int $identifierACK) : void;
42+
43+
/**
44+
* Called when RakLib records a new ping measurement for the session.
45+
*/
46+
public function onPingMeasure(int $pingMS) : void;
47+
}

‎src/server/SessionInterface.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server;
25+
26+
use raklib\protocol\EncapsulatedPacket;
27+
28+
interface SessionInterface{
29+
30+
public function sendEncapsulated(EncapsulatedPacket $packet, bool $immediate = false) : void;
31+
32+
public function initiateDisconnect(string $reason) : void;
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
interface InterThreadChannelFactory{
27+
/**
28+
* Returns an array of two parts: the first is a serialized representation of a channel reader that will be passed
29+
* to an InterThreadChannelReaderDeserializer, and the second is an InterThreadChannelWriter that will be used by
30+
* this thread.
31+
*
32+
* @see InterThreadChannelReaderDeserializer
33+
* @phpstan-return array{string, InterThreadChannelWriter}
34+
*/
35+
public function createChannel() : array;
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
interface InterThreadChannelReaderDeserializer{
27+
28+
public function deserialize(string $channelInfo) : ?InterThreadChannelReader;
29+
}

‎src/server/ipc/RakLibToUserThreadMessageProtocol.php

-29
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@ private function __construct(){
3939
* byte[] (payload)
4040
*/
4141

42-
/*
43-
* ENCAPSULATED payload:
44-
* int32 (internal session ID)
45-
* byte[] (user packet payload)
46-
*/
47-
public const PACKET_ENCAPSULATED = 0x01;
48-
4942
/*
5043
* OPEN_SESSION payload:
5144
* int32 (internal session ID)
@@ -56,20 +49,6 @@ private function __construct(){
5649
*/
5750
public const PACKET_OPEN_SESSION = 0x02;
5851

59-
/*
60-
* CLOSE_SESSION payload:
61-
* int32 (internal session ID)
62-
* string (reason)
63-
*/
64-
public const PACKET_CLOSE_SESSION = 0x03;
65-
66-
/*
67-
* ACK_NOTIFICATION payload:
68-
* int32 (internal session ID)
69-
* int32 (identifierACK)
70-
*/
71-
public const PACKET_ACK_NOTIFICATION = 0x04;
72-
7352
/*
7453
* REPORT_BANDWIDTH_STATS payload:
7554
* int64 (sent bytes diff)
@@ -85,12 +64,4 @@ private function __construct(){
8564
* byte[] (payload)
8665
*/
8766
public const PACKET_RAW = 0x06;
88-
89-
/*
90-
* REPORT_PING payload:
91-
* int32 (internal session ID)
92-
* int32 (measured latency in MS)
93-
*/
94-
public const PACKET_REPORT_PING = 0x07;
95-
9667
}

‎src/server/ipc/RakLibToUserThreadMessageReceiver.php

+71-56
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use pocketmine\utils\Binary;
2121
use raklib\server\ipc\RakLibToUserThreadMessageProtocol as ITCProtocol;
2222
use raklib\server\ServerEventListener;
23+
use raklib\server\SessionEventListener;
2324
use function inet_ntop;
2425
use function ord;
2526
use function substr;
@@ -28,67 +29,81 @@ final class RakLibToUserThreadMessageReceiver{
2829
/** @var InterThreadChannelReader */
2930
private $channel;
3031

31-
public function __construct(InterThreadChannelReader $channel){
32+
private InterThreadChannelReaderDeserializer $channelFactory;
33+
34+
/**
35+
* @var SessionEventListener[][]|RakLibToUserThreadSessionMessageReceiver[][]
36+
* @phpstan-var array<int, array{RakLibToUserThreadSessionMessageReceiver, SessionEventListener}>
37+
*/
38+
private array $sessionMap = [];
39+
40+
public function __construct(InterThreadChannelReader $channel, InterThreadChannelReaderDeserializer $channelFactory){
3241
$this->channel = $channel;
42+
$this->channelFactory = $channelFactory;
3343
}
3444

35-
public function handle(ServerEventListener $listener) : bool{
36-
if(($packet = $this->channel->read()) !== null){
37-
$id = ord($packet[0]);
38-
$offset = 1;
39-
if($id === ITCProtocol::PACKET_ENCAPSULATED){
40-
$sessionId = Binary::readInt(substr($packet, $offset, 4));
41-
$offset += 4;
42-
$buffer = substr($packet, $offset);
43-
$listener->onPacketReceive($sessionId, $buffer);
44-
}elseif($id === ITCProtocol::PACKET_RAW){
45-
$len = ord($packet[$offset++]);
46-
$address = substr($packet, $offset, $len);
47-
$offset += $len;
48-
$port = Binary::readShort(substr($packet, $offset, 2));
49-
$offset += 2;
50-
$payload = substr($packet, $offset);
51-
$listener->onRawPacketReceive($address, $port, $payload);
52-
}elseif($id === ITCProtocol::PACKET_REPORT_BANDWIDTH_STATS){
53-
$sentBytes = Binary::readLong(substr($packet, $offset, 8));
54-
$offset += 8;
55-
$receivedBytes = Binary::readLong(substr($packet, $offset, 8));
56-
$listener->onBandwidthStatsUpdate($sentBytes, $receivedBytes);
57-
}elseif($id === ITCProtocol::PACKET_OPEN_SESSION){
58-
$sessionId = Binary::readInt(substr($packet, $offset, 4));
59-
$offset += 4;
60-
$len = ord($packet[$offset++]);
61-
$rawAddr = substr($packet, $offset, $len);
62-
$offset += $len;
63-
$address = inet_ntop($rawAddr);
64-
if($address === false){
65-
throw new \RuntimeException("Unexpected invalid IP address in inter-thread message");
45+
/**
46+
* @phpstan-return \Generator<int, null, void, void>
47+
*/
48+
public function handle(ServerEventListener $listener) : \Generator{
49+
do{
50+
$processed = false;
51+
if(($packet = $this->channel->read()) !== null){
52+
$id = ord($packet[0]);
53+
$offset = 1;
54+
if($id === ITCProtocol::PACKET_RAW){
55+
$len = ord($packet[$offset++]);
56+
$address = substr($packet, $offset, $len);
57+
$offset += $len;
58+
$port = Binary::readShort(substr($packet, $offset, 2));
59+
$offset += 2;
60+
$payload = substr($packet, $offset);
61+
$listener->onRawPacketReceive($address, $port, $payload);
62+
}elseif($id === ITCProtocol::PACKET_REPORT_BANDWIDTH_STATS){
63+
$sentBytes = Binary::readLong(substr($packet, $offset, 8));
64+
$offset += 8;
65+
$receivedBytes = Binary::readLong(substr($packet, $offset, 8));
66+
$listener->onBandwidthStatsUpdate($sentBytes, $receivedBytes);
67+
}elseif($id === ITCProtocol::PACKET_OPEN_SESSION){
68+
$sessionId = Binary::readInt(substr($packet, $offset, 4));
69+
$offset += 4;
70+
$len = ord($packet[$offset++]);
71+
$rawAddr = substr($packet, $offset, $len);
72+
$offset += $len;
73+
$address = inet_ntop($rawAddr);
74+
if($address === false){
75+
throw new \RuntimeException("Unexpected invalid IP address in inter-thread message");
76+
}
77+
$port = Binary::readShort(substr($packet, $offset, 2));
78+
$offset += 2;
79+
$clientID = Binary::readLong(substr($packet, $offset, 8));
80+
$offset += 8;
81+
82+
$channelReaderInfo = substr($packet, $offset);
83+
$channelReader = $this->channelFactory->deserialize($channelReaderInfo);
84+
if($channelReader !== null){ //the channel may have been destroyed before we could deserialize it
85+
$receiver = new RakLibToUserThreadSessionMessageReceiver($channelReader);
86+
$sessionListener = $listener->onClientConnect($sessionId, $address, $port, $clientID);
87+
$this->sessionMap[$sessionId] = [$receiver, $sessionListener];
88+
}
6689
}
67-
$port = Binary::readShort(substr($packet, $offset, 2));
68-
$offset += 2;
69-
$clientID = Binary::readLong(substr($packet, $offset, 8));
70-
$listener->onClientConnect($sessionId, $address, $port, $clientID);
71-
}elseif($id === ITCProtocol::PACKET_CLOSE_SESSION){
72-
$sessionId = Binary::readInt(substr($packet, $offset, 4));
73-
$offset += 4;
74-
$len = ord($packet[$offset++]);
75-
$reason = substr($packet, $offset, $len);
76-
$listener->onClientDisconnect($sessionId, $reason);
77-
}elseif($id === ITCProtocol::PACKET_ACK_NOTIFICATION){
78-
$sessionId = Binary::readInt(substr($packet, $offset, 4));
79-
$offset += 4;
80-
$identifierACK = Binary::readInt(substr($packet, $offset, 4));
81-
$listener->onPacketAck($sessionId, $identifierACK);
82-
}elseif($id === ITCProtocol::PACKET_REPORT_PING){
83-
$sessionId = Binary::readInt(substr($packet, $offset, 4));
84-
$offset += 4;
85-
$pingMS = Binary::readInt(substr($packet, $offset, 4));
86-
$listener->onPingMeasure($sessionId, $pingMS);
87-
}
8890

89-
return true;
90-
}
91+
$processed = true;
92+
yield;
93+
}
9194

92-
return false;
95+
foreach($this->sessionMap as $sessionId => [$receiver, $sessionListener]){
96+
try{
97+
if($receiver->process($sessionListener)){
98+
$processed = true;
99+
yield;
100+
}
101+
}finally{
102+
if($receiver->isClosed()){
103+
unset($this->sessionMap[$sessionId]);
104+
}
105+
}
106+
}
107+
}while($processed);
93108
}
94109
}

‎src/server/ipc/RakLibToUserThreadMessageSender.php

+11-35
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use pocketmine\utils\Binary;
2121
use raklib\server\ipc\RakLibToUserThreadMessageProtocol as ITCProtocol;
2222
use raklib\server\ServerEventListener;
23+
use raklib\server\SessionEventListener;
2324
use function chr;
2425
use function inet_pton;
2526
use function strlen;
@@ -29,38 +30,29 @@ final class RakLibToUserThreadMessageSender implements ServerEventListener{
2930
/** @var InterThreadChannelWriter */
3031
private $channel;
3132

32-
public function __construct(InterThreadChannelWriter $channel){
33+
private InterThreadChannelFactory $channelFactory;
34+
35+
public function __construct(InterThreadChannelWriter $channel, InterThreadChannelFactory $channelFactory){
3336
$this->channel = $channel;
37+
$this->channelFactory = $channelFactory;
3438
}
3539

36-
public function onClientConnect(int $sessionId, string $address, int $port, int $clientId) : void{
40+
public function onClientConnect(int $sessionId, string $address, int $port, int $clientID) : SessionEventListener{
3741
$rawAddr = inet_pton($address);
3842
if($rawAddr === false){
3943
throw new \InvalidArgumentException("Invalid IP address");
4044
}
45+
46+
[$channelReaderInfo, $channelWriter] = $this->channelFactory->createChannel();
4147
$this->channel->write(
4248
chr(ITCProtocol::PACKET_OPEN_SESSION) .
4349
Binary::writeInt($sessionId) .
4450
chr(strlen($rawAddr)) . $rawAddr .
4551
Binary::writeShort($port) .
46-
Binary::writeLong($clientId)
47-
);
48-
}
49-
50-
public function onClientDisconnect(int $sessionId, string $reason) : void{
51-
$this->channel->write(
52-
chr(ITCProtocol::PACKET_CLOSE_SESSION) .
53-
Binary::writeInt($sessionId) .
54-
chr(strlen($reason)) . $reason
55-
);
56-
}
57-
58-
public function onPacketReceive(int $sessionId, string $packet) : void{
59-
$this->channel->write(
60-
chr(ITCProtocol::PACKET_ENCAPSULATED) .
61-
Binary::writeInt($sessionId) .
62-
$packet
52+
Binary::writeLong($clientID) .
53+
$channelReaderInfo
6354
);
55+
return new RakLibToUserThreadSessionMessageSender($channelWriter);
6456
}
6557

6658
public function onRawPacketReceive(string $address, int $port, string $payload) : void{
@@ -72,27 +64,11 @@ public function onRawPacketReceive(string $address, int $port, string $payload)
7264
);
7365
}
7466

75-
public function onPacketAck(int $sessionId, int $identifierACK) : void{
76-
$this->channel->write(
77-
chr(ITCProtocol::PACKET_ACK_NOTIFICATION) .
78-
Binary::writeInt($sessionId) .
79-
Binary::writeInt($identifierACK)
80-
);
81-
}
82-
8367
public function onBandwidthStatsUpdate(int $bytesSentDiff, int $bytesReceivedDiff) : void{
8468
$this->channel->write(
8569
chr(ITCProtocol::PACKET_REPORT_BANDWIDTH_STATS) .
8670
Binary::writeLong($bytesSentDiff) .
8771
Binary::writeLong($bytesReceivedDiff)
8872
);
8973
}
90-
91-
public function onPingMeasure(int $sessionId, int $pingMS) : void{
92-
$this->channel->write(
93-
chr(ITCProtocol::PACKET_REPORT_PING) .
94-
Binary::writeInt($sessionId) .
95-
Binary::writeInt($pingMS)
96-
);
97-
}
9874
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
final class RakLibToUserThreadSessionMessageProtocol{
27+
28+
/*
29+
* ENCAPSULATED payload:
30+
* byte[] (user packet payload)
31+
*/
32+
public const PACKET_ENCAPSULATED = 0x01;
33+
34+
/*
35+
* CLOSE_SESSION payload:
36+
* string (reason)
37+
*/
38+
public const PACKET_CLOSE_SESSION = 0x02;
39+
40+
/*
41+
* ACK_NOTIFICATION payload:
42+
* int32 (identifierACK)
43+
*/
44+
public const PACKET_ACK_NOTIFICATION = 0x03;
45+
46+
/*
47+
* REPORT_PING payload:
48+
* int32 (measured latency in MS)
49+
*/
50+
public const PACKET_REPORT_PING = 0x04;
51+
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
use pocketmine\utils\Binary;
27+
use raklib\server\ipc\RakLibToUserThreadSessionMessageProtocol as ITCSessionProtocol;
28+
use raklib\server\SessionEventListener;
29+
use function ord;
30+
use function substr;
31+
32+
final class RakLibToUserThreadSessionMessageReceiver{
33+
34+
private InterThreadChannelReader $channel;
35+
private bool $closed = false;
36+
37+
public function __construct(InterThreadChannelReader $channel){
38+
$this->channel = $channel;
39+
}
40+
41+
public function process(SessionEventListener $listener) : bool{
42+
if(($packet = $this->channel->read()) !== null){
43+
$id = ord($packet[0]);
44+
$offset = 1;
45+
if($id === ITCSessionProtocol::PACKET_ENCAPSULATED){
46+
$buffer = substr($packet, $offset);
47+
$listener->onPacketReceive($buffer);
48+
}elseif($id === ITCSessionProtocol::PACKET_CLOSE_SESSION){
49+
$len = ord($packet[$offset++]);
50+
$reason = substr($packet, $offset, $len);
51+
$listener->onDisconnect($reason);
52+
$this->closed = true;
53+
}elseif($id === ITCSessionProtocol::PACKET_ACK_NOTIFICATION){
54+
$identifierACK = Binary::readInt(substr($packet, $offset, 4));
55+
$listener->onPacketAck($identifierACK);
56+
}elseif($id === ITCSessionProtocol::PACKET_REPORT_PING){
57+
$pingMS = Binary::readInt(substr($packet, $offset, 4));
58+
$listener->onPingMeasure($pingMS);
59+
}
60+
61+
return true;
62+
}
63+
64+
return false;
65+
}
66+
67+
public function isClosed() : bool{ return $this->closed; }
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
use pocketmine\utils\Binary;
27+
use raklib\server\ipc\RakLibToUserThreadSessionMessageProtocol as ITCSessionProtocol;
28+
use raklib\server\SessionEventListener;
29+
use function chr;
30+
use function strlen;
31+
32+
final class RakLibToUserThreadSessionMessageSender implements SessionEventListener{
33+
34+
private InterThreadChannelWriter $channel;
35+
36+
public function __construct(InterThreadChannelWriter $channel){
37+
$this->channel = $channel;
38+
}
39+
40+
public function onDisconnect(string $reason) : void{
41+
$this->channel->write(
42+
chr(ITCSessionProtocol::PACKET_CLOSE_SESSION) .
43+
chr(strlen($reason)) . $reason
44+
);
45+
}
46+
47+
public function onPacketReceive(string $payload) : void{
48+
$this->channel->write(
49+
chr(ITCSessionProtocol::PACKET_ENCAPSULATED) .
50+
$payload
51+
);
52+
}
53+
54+
public function onPacketAck(int $identifierACK) : void{
55+
$this->channel->write(
56+
chr(ITCSessionProtocol::PACKET_ACK_NOTIFICATION) .
57+
Binary::writeInt($identifierACK)
58+
);
59+
}
60+
61+
public function onPingMeasure(int $pingMS) : void{
62+
$this->channel->write(
63+
chr(ITCSessionProtocol::PACKET_REPORT_PING) .
64+
Binary::writeInt($pingMS)
65+
);
66+
}
67+
}

‎src/server/ipc/UserToRakLibThreadMessageProtocol.php

+4-17
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,11 @@ private function __construct(){
4040
*/
4141

4242
/*
43-
* ENCAPSULATED payload:
44-
* int32 (internal session ID)
45-
* byte (flags, last 3 bits, priority)
46-
* byte (reliability)
47-
* int32 (ack identifier)
48-
* byte? (order channel, only when sequenced or ordered reliability)
49-
* byte[] (user packet payload)
43+
* PACKET_OPEN_SESSION_RESPONSE payload:
44+
* int32 (session ID)
45+
* byte[] (serialized channel information)
5046
*/
51-
public const PACKET_ENCAPSULATED = 0x01;
52-
53-
public const ENCAPSULATED_FLAG_NEED_ACK = 1 << 0;
54-
public const ENCAPSULATED_FLAG_IMMEDIATE = 1 << 1;
55-
56-
/*
57-
* CLOSE_SESSION payload:
58-
* int32 (internal session ID)
59-
*/
60-
public const PACKET_CLOSE_SESSION = 0x02;
47+
public const PACKET_OPEN_SESSION_RESPONSE = 0x01;
6148

6249
/*
6350
* RAW payload:

‎src/server/ipc/UserToRakLibThreadMessageReceiver.php

+75-62
Original file line numberDiff line numberDiff line change
@@ -18,85 +18,98 @@
1818
namespace raklib\server\ipc;
1919

2020
use pocketmine\utils\Binary;
21-
use raklib\protocol\EncapsulatedPacket;
22-
use raklib\protocol\PacketReliability;
2321
use raklib\server\ipc\UserToRakLibThreadMessageProtocol as ITCProtocol;
2422
use raklib\server\ServerEventSource;
2523
use raklib\server\ServerInterface;
24+
use raklib\server\SessionInterface;
2625
use function ord;
2726
use function substr;
2827

2928
final class UserToRakLibThreadMessageReceiver implements ServerEventSource{
3029
/** @var InterThreadChannelReader */
3130
private $channel;
3231

33-
public function __construct(InterThreadChannelReader $channel){
34-
$this->channel = $channel;
35-
}
32+
private InterThreadChannelReaderDeserializer $channelReaderDeserializer;
3633

37-
public function process(ServerInterface $server) : bool{
38-
if(($packet = $this->channel->read()) !== null){
39-
$id = ord($packet[0]);
40-
$offset = 1;
41-
if($id === ITCProtocol::PACKET_ENCAPSULATED){
42-
$sessionId = Binary::readInt(substr($packet, $offset, 4));
43-
$offset += 4;
44-
$flags = ord($packet[$offset++]);
45-
$immediate = ($flags & ITCProtocol::ENCAPSULATED_FLAG_IMMEDIATE) !== 0;
46-
$needACK = ($flags & ITCProtocol::ENCAPSULATED_FLAG_NEED_ACK) !== 0;
34+
/**
35+
* @var SessionInterface[][]|UserToRakLibThreadSessionMessageReceiver[][]
36+
* @phpstan-var array<int, array{UserToRakLibThreadSessionMessageReceiver, SessionInterface}>
37+
*/
38+
private array $sessionMap = [];
4739

48-
$encapsulated = new EncapsulatedPacket();
49-
$encapsulated->reliability = ord($packet[$offset++]);
40+
public function __construct(InterThreadChannelReader $channel, InterThreadChannelReaderDeserializer $channelReaderDeserializer){
41+
$this->channel = $channel;
42+
$this->channelReaderDeserializer = $channelReaderDeserializer;
43+
}
5044

51-
if($needACK){
52-
$encapsulated->identifierACK = Binary::readInt(substr($packet, $offset, 4));
45+
/**
46+
* @phpstan-return \Generator<int, null, void, void>
47+
*/
48+
public function process(ServerInterface $server) : \Generator{
49+
do{
50+
$processed = false;
51+
if(($packet = $this->channel->read()) !== null){
52+
$id = ord($packet[0]);
53+
$offset = 1;
54+
if($id === ITCProtocol::PACKET_RAW){
55+
$len = ord($packet[$offset++]);
56+
$address = substr($packet, $offset, $len);
57+
$offset += $len;
58+
$port = Binary::readShort(substr($packet, $offset, 2));
59+
$offset += 2;
60+
$payload = substr($packet, $offset);
61+
$server->sendRaw($address, $port, $payload);
62+
}elseif($id === ITCProtocol::PACKET_SET_NAME){
63+
$server->setName(substr($packet, $offset));
64+
}elseif($id === ITCProtocol::PACKET_ENABLE_PORT_CHECK){
65+
$server->setPortCheck(true);
66+
}elseif($id === ITCProtocol::PACKET_DISABLE_PORT_CHECK){
67+
$server->setPortCheck(false);
68+
}elseif($id === ITCProtocol::PACKET_SET_PACKETS_PER_TICK_LIMIT){
69+
$limit = Binary::readLong(substr($packet, $offset, 8));
70+
$server->setPacketsPerTickLimit($limit);
71+
}elseif($id === ITCProtocol::PACKET_BLOCK_ADDRESS){
72+
$len = ord($packet[$offset++]);
73+
$address = substr($packet, $offset, $len);
74+
$offset += $len;
75+
$timeout = Binary::readInt(substr($packet, $offset, 4));
76+
$server->blockAddress($address, $timeout);
77+
}elseif($id === ITCProtocol::PACKET_UNBLOCK_ADDRESS){
78+
$len = ord($packet[$offset++]);
79+
$address = substr($packet, $offset, $len);
80+
$server->unblockAddress($address);
81+
}elseif($id === ITCProtocol::PACKET_RAW_FILTER){
82+
$pattern = substr($packet, $offset);
83+
$server->addRawPacketFilter($pattern);
84+
}elseif($id === ITCProtocol::PACKET_OPEN_SESSION_RESPONSE){
85+
$sessionId = Binary::readInt(substr($packet, $offset, 4));
5386
$offset += 4;
87+
$session = $server->getSession($sessionId);
88+
if($session !== null){
89+
$channelInfo = substr($packet, $offset);
90+
$channel = $this->channelReaderDeserializer->deserialize($channelInfo);
91+
if($channel !== null){
92+
$this->sessionMap[$sessionId] = [new UserToRakLibThreadSessionMessageReceiver($channel), $session];
93+
}
94+
}
5495
}
5596

56-
if(PacketReliability::isSequencedOrOrdered($encapsulated->reliability)){
57-
$encapsulated->orderChannel = ord($packet[$offset++]);
58-
}
59-
60-
$encapsulated->buffer = substr($packet, $offset);
61-
$server->sendEncapsulated($sessionId, $encapsulated, $immediate);
62-
}elseif($id === ITCProtocol::PACKET_RAW){
63-
$len = ord($packet[$offset++]);
64-
$address = substr($packet, $offset, $len);
65-
$offset += $len;
66-
$port = Binary::readShort(substr($packet, $offset, 2));
67-
$offset += 2;
68-
$payload = substr($packet, $offset);
69-
$server->sendRaw($address, $port, $payload);
70-
}elseif($id === ITCProtocol::PACKET_CLOSE_SESSION){
71-
$sessionId = Binary::readInt(substr($packet, $offset, 4));
72-
$server->closeSession($sessionId);
73-
}elseif($id === ITCProtocol::PACKET_SET_NAME){
74-
$server->setName(substr($packet, $offset));
75-
}elseif($id === ITCProtocol::PACKET_ENABLE_PORT_CHECK){
76-
$server->setPortCheck(true);
77-
}elseif($id === ITCProtocol::PACKET_DISABLE_PORT_CHECK){
78-
$server->setPortCheck(false);
79-
}elseif($id === ITCProtocol::PACKET_SET_PACKETS_PER_TICK_LIMIT){
80-
$limit = Binary::readLong(substr($packet, $offset, 8));
81-
$server->setPacketsPerTickLimit($limit);
82-
}elseif($id === ITCProtocol::PACKET_BLOCK_ADDRESS){
83-
$len = ord($packet[$offset++]);
84-
$address = substr($packet, $offset, $len);
85-
$offset += $len;
86-
$timeout = Binary::readInt(substr($packet, $offset, 4));
87-
$server->blockAddress($address, $timeout);
88-
}elseif($id === ITCProtocol::PACKET_UNBLOCK_ADDRESS){
89-
$len = ord($packet[$offset++]);
90-
$address = substr($packet, $offset, $len);
91-
$server->unblockAddress($address);
92-
}elseif($id === ITCProtocol::PACKET_RAW_FILTER){
93-
$pattern = substr($packet, $offset);
94-
$server->addRawPacketFilter($pattern);
97+
$processed = true;
98+
yield;
9599
}
96100

97-
return true;
98-
}
99-
100-
return false;
101+
foreach($this->sessionMap as $sessionId => [$receiver, $session]){
102+
try{
103+
if($receiver->process($session)){
104+
$processed = true;
105+
yield;
106+
}
107+
}finally{
108+
if($receiver->isClosed()){
109+
unset($this->sessionMap[$sessionId]);
110+
}
111+
}
112+
}
113+
}while($processed);
101114
}
102115
}

‎src/server/ipc/UserToRakLibThreadMessageSender.php

+19-20
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,42 @@
1818
namespace raklib\server\ipc;
1919

2020
use pocketmine\utils\Binary;
21-
use raklib\protocol\EncapsulatedPacket;
22-
use raklib\protocol\PacketReliability;
2321
use raklib\server\ipc\UserToRakLibThreadMessageProtocol as ITCProtocol;
2422
use raklib\server\ServerInterface;
23+
use raklib\server\SessionInterface;
2524
use function chr;
2625
use function strlen;
2726

2827
class UserToRakLibThreadMessageSender implements ServerInterface{
2928
/** @var InterThreadChannelWriter */
3029
private $channel;
3130

32-
public function __construct(InterThreadChannelWriter $channel){
31+
private InterThreadChannelFactory $channelFactory;
32+
33+
public function __construct(InterThreadChannelWriter $channel, InterThreadChannelFactory $channelFactory){
3334
$this->channel = $channel;
35+
$this->channelFactory = $channelFactory;
3436
}
3537

36-
public function sendEncapsulated(int $sessionId, EncapsulatedPacket $packet, bool $immediate = false) : void{
37-
$flags =
38-
($immediate ? ITCProtocol::ENCAPSULATED_FLAG_IMMEDIATE : 0) |
39-
($packet->identifierACK !== null ? ITCProtocol::ENCAPSULATED_FLAG_NEED_ACK : 0);
40-
41-
$buffer = chr(ITCProtocol::PACKET_ENCAPSULATED) .
38+
/**
39+
* Opens an inter-thread channel to the RakLib thread for the given session.
40+
*/
41+
public function openSessionChannel(int $sessionId) : SessionInterface{
42+
[$channelReaderInfo, $channelWriter] = $this->channelFactory->createChannel();
43+
$this->channel->write(
44+
chr(ITCProtocol::PACKET_OPEN_SESSION_RESPONSE) .
4245
Binary::writeInt($sessionId) .
43-
chr($flags) .
44-
chr($packet->reliability) .
45-
($packet->identifierACK !== null ? Binary::writeInt($packet->identifierACK) : "") .
46-
(PacketReliability::isSequencedOrOrdered($packet->reliability) ? chr($packet->orderChannel) : "") .
47-
$packet->buffer;
48-
$this->channel->write($buffer);
46+
$channelReaderInfo
47+
);
48+
return new UserToRakLibThreadSessionMessageSender($channelWriter);
4949
}
5050

51-
public function sendRaw(string $address, int $port, string $payload) : void{
52-
$buffer = chr(ITCProtocol::PACKET_RAW) . chr(strlen($address)) . $address . Binary::writeShort($port) . $payload;
53-
$this->channel->write($buffer);
51+
public function getSession(int $id) : ?SessionInterface{
52+
return null;
5453
}
5554

56-
public function closeSession(int $sessionId) : void{
57-
$buffer = chr(ITCProtocol::PACKET_CLOSE_SESSION) . Binary::writeInt($sessionId);
55+
public function sendRaw(string $address, int $port, string $payload) : void{
56+
$buffer = chr(ITCProtocol::PACKET_RAW) . chr(strlen($address)) . $address . Binary::writeShort($port) . $payload;
5857
$this->channel->write($buffer);
5958
}
6059

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
final class UserToRakLibThreadSessionMessageProtocol{
27+
public const ENCAPSULATED_FLAG_NEED_ACK = 1 << 0;
28+
public const ENCAPSULATED_FLAG_IMMEDIATE = 1 << 1;
29+
30+
/*
31+
* ENCAPSULATED payload:
32+
* byte (flags, last 3 bits, priority)
33+
* byte (reliability)
34+
* int32 (ack identifier)
35+
* byte? (order channel, only when sequenced or ordered reliability)
36+
* byte[] (user packet payload)
37+
*/
38+
public const PACKET_ENCAPSULATED = 0x01;
39+
40+
/* No payload */
41+
public const PACKET_CLOSE_SESSION = 0x02;
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
use pocketmine\utils\Binary;
27+
use raklib\protocol\EncapsulatedPacket;
28+
use raklib\protocol\PacketReliability;
29+
use raklib\server\ipc\UserToRakLibThreadSessionMessageProtocol as ITCSessionProtocol;
30+
use raklib\server\SessionInterface;
31+
use function ord;
32+
use function substr;
33+
34+
final class UserToRakLibThreadSessionMessageReceiver{
35+
36+
private InterThreadChannelReader $channel;
37+
private bool $closed = false;
38+
39+
public function __construct(InterThreadChannelReader $channel){
40+
$this->channel = $channel;
41+
}
42+
43+
public function process(SessionInterface $session) : bool{
44+
if(($packet = $this->channel->read()) !== null){
45+
$id = ord($packet[0]);
46+
$offset = 1;
47+
if($id === ITCSessionProtocol::PACKET_ENCAPSULATED){
48+
$flags = ord($packet[$offset++]);
49+
$immediate = ($flags & UserToRakLibThreadSessionMessageProtocol::ENCAPSULATED_FLAG_IMMEDIATE) !== 0;
50+
$needACK = ($flags & UserToRakLibThreadSessionMessageProtocol::ENCAPSULATED_FLAG_NEED_ACK) !== 0;
51+
52+
$encapsulated = new EncapsulatedPacket();
53+
$encapsulated->reliability = ord($packet[$offset++]);
54+
55+
if($needACK){
56+
$encapsulated->identifierACK = Binary::readInt(substr($packet, $offset, 4));
57+
$offset += 4;
58+
}
59+
60+
if(PacketReliability::isSequencedOrOrdered($encapsulated->reliability)){
61+
$encapsulated->orderChannel = ord($packet[$offset++]);
62+
}
63+
64+
$encapsulated->buffer = substr($packet, $offset);
65+
$session->sendEncapsulated($encapsulated, $immediate);
66+
}elseif($id === ITCSessionProtocol::PACKET_CLOSE_SESSION){
67+
$session->initiateDisconnect("server disconnect");
68+
$this->closed = true;
69+
}
70+
71+
return true;
72+
}
73+
74+
return false;
75+
}
76+
77+
public function isClosed() : bool{ return $this->closed; }
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
*
5+
* ____ _ _ __ __ _ __ __ ____
6+
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7+
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8+
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9+
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Lesser General Public License as published by
13+
* the Free Software Foundation, either version 3 of the License, or
14+
* (at your option) any later version.
15+
*
16+
* @author PocketMine Team
17+
* @link http://www.pocketmine.net/
18+
*
19+
*
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace raklib\server\ipc;
25+
26+
use pocketmine\utils\Binary;
27+
use raklib\protocol\EncapsulatedPacket;
28+
use raklib\protocol\PacketReliability;
29+
use raklib\server\ipc\UserToRakLibThreadSessionMessageProtocol as ITCSessionProtocol;
30+
use raklib\server\SessionInterface;
31+
use function chr;
32+
33+
final class UserToRakLibThreadSessionMessageSender implements SessionInterface{
34+
35+
private InterThreadChannelWriter $channel;
36+
37+
public function __construct(InterThreadChannelWriter $channel){
38+
$this->channel = $channel;
39+
}
40+
41+
public function sendEncapsulated(EncapsulatedPacket $packet, bool $immediate = false) : void{
42+
$flags =
43+
($immediate ? ITCSessionProtocol::ENCAPSULATED_FLAG_IMMEDIATE : 0) |
44+
($packet->identifierACK !== null ? ITCSessionProtocol::ENCAPSULATED_FLAG_NEED_ACK : 0);
45+
46+
$buffer = chr(ITCSessionProtocol::PACKET_ENCAPSULATED) .
47+
chr($flags) .
48+
chr($packet->reliability) .
49+
($packet->identifierACK !== null ? Binary::writeInt($packet->identifierACK) : "") .
50+
(PacketReliability::isSequencedOrOrdered($packet->reliability) ? chr($packet->orderChannel) : "") .
51+
$packet->buffer;
52+
$this->channel->write($buffer);
53+
}
54+
55+
public function initiateDisconnect(string $reason) : void{
56+
$this->channel->write(chr(ITCSessionProtocol::PACKET_CLOSE_SESSION));
57+
}
58+
}

0 commit comments

Comments
 (0)
Please sign in to comment.