A pure PHP implementation of the RakNet protocol (the UDP transport used by Minecraft Bedrock/PE). Includes both server and client with built-in UDP sockets — it's not just a codec, it handles the full connection lifecycle.
- PHP >= 8.1
socketsextension enabled (php -m | grep sockets)
On Termux/proot-Ubuntu:
apt update && apt install -y php php-cli
php -m | grep sockets # if it doesn't show up, you may need php-sockets or a build with --enable-socketsIf php-sockets doesn't exist as a separate package on your distro, check php -i | grep sockets — on many builds it's already compiled into the base binary.
cd RakNet-php
composer dump-autoload # no external dependencies, this just generates the PSR-4 autoloaderIf you don't have composer, you can use a minimal manual autoloader (see tests/loopback_test.php for reference on which classes to import).
php tests/loopback_test.phpThis spins up a server and a client in the same process on 127.0.0.1:39132, runs the full handshake, and tests:
- Full handshake (Open Connection 1/2 + Connection Request/Accepted + New Incoming Connection)
- Small reliable-ordered message with roundtrip
- Large message (5000+ bytes) that forces fragmentation and reassembly
- Delivery order with 10 messages fired quickly on an order channel
If something fails, the output tells you exactly which assertion failed — send it over as-is.
The snippets below are illustrative. For code you can run as-is, check examples/:
examples/server_echo.php— standalone server that logs connect/disconnect/message events and echoes back any message it receives.examples/client_connect.php— client that first sends an Unconnected Ping (to read the MOTD), then does the full handshake, then sends a Connected Ping every 2s. Use it againstserver_echo.phpor against a real RakNet server (e.g. a Bedrock server).
Try them in two terminals:
# Terminal A
php examples/server_echo.php 19132
# Terminal B
php examples/client_connect.php 127.0.0.1 19132<?php
require 'vendor/autoload.php';
use RakNet\Socket\RakNetClient;
use RakNet\Session\SessionState;
use RakNet\FrameSet\Reliability;
$client = new RakNetClient('127.0.0.1', 19132, protocolVersion: 11);
$client->connect();
while ($client->getState() !== SessionState::CONNECTED) {
$client->tick();
usleep(10_000);
}
// 0xfe = Game Packet id (see wiki) - everything after that is your application protocol
$client->send("\xfe" . "my payload", Reliability::RELIABLE_ORDERED);
while (true) {
$client->tick();
foreach ($client->receive() as $message) {
// handle $message (raw bytes, starts with your own protocol's id)
}
usleep(10_000);
}<?php
require 'vendor/autoload.php';
use RakNet\Socket\RakNetServer;
use RakNet\Socket\ServerEventType;
use RakNet\FrameSet\Reliability;
$server = new RakNetServer('0.0.0.0', 19132, protocolVersion: 11, serverIdStringProvider: function () {
// MOTD format: see the "Unconnected Pong" table on the wiki
return "MCPE;My Server;11;1.0.0;0;20;{$guid};World;Survival;1;19132;19132;";
});
$server->start();
while (true) {
$server->tick();
foreach ($server->pollEvents() as $event) {
switch ($event->type) {
case ServerEventType::CONNECT:
echo "Client connected: {$event->sessionKey} (guid={$event->guid})\n";
break;
case ServerEventType::MESSAGE:
// $event->message is the raw Game Packet bytes
$server->send($event->sessionKey, "\xfe" . "response", Reliability::RELIABLE_ORDERED);
break;
case ServerEventType::DISCONNECT:
echo "Client disconnected: {$event->sessionKey}\n";
break;
}
}
usleep(10_000);
}src/
Utils/BinaryStream.php - byte reading/writing, all data types from the wiki
Protocol/
DataType/InternetAddress.php - the "address" type (reversed IPv4 octets / IPv6)
MessageIdentifiers.php - packet ID constants
OfflinePackets.php - ping/pong, open connection request/reply 1 and 2, incompatible protocol
ConnectedPackets.php - connected ping/pong, connection request/accepted, new incoming connection
PacketDispatcher.php - identifies an incoming buffer's packet type from its first byte
FrameSet/
Reliability.php - constants + helpers for the 8 reliability types
Frame.php - a single Frame (encode/decode)
FrameSetPacket.php - the 0x80-0x8d datagram that wraps Frames
AcknowledgePacket.php - ACK (0xc0) / NACK (0xa0) with range compression
FragmentAssembler.php - fragment reassembly by compound id
OrderingChannel.php - in-order delivery buffer (reliable ordered) and sequenced
Session/
Session.php - CORE: reliability tracking, resend, fragmentation, ordering, handshake state
SessionState.php - connection lifecycle enum
Socket/
RakNetClient.php - real client over UDP
RakNetServer.php - real server over UDP (multi-session)
ServerEvent.php - events emitted by the server (connect/disconnect/message)
examples/
server_echo.php - standalone server that logs events and echoes messages
client_connect.php - client that does ping + handshake + connected ping against any RakNet server
Session is transport-agnostic (it never touches sockets directly) — it only produces/consumes raw bytes. This keeps the reliability logic (testable without real networking) separate from the I/O layer.
Not hardcoded to 11. It's passed as a parameter (protocolVersion) to both RakNetClient and RakNetServer. If the server receives an Open Connection Request 1 with a version different from its own, it responds with Incompatible Protocol Version (0x19) as specified by the wiki.
- Doesn't implement the full "security cookie" challenge (only echoes it back) — the wiki itself notes it's optional and rarely used in real implementations.
- IPv6 in
InternetAddressis implemented but hasn't been tested against a real peer (only IPv4 was exercised in the loopback test). - No Game Packet compression/encryption — that's a Bedrock protocol layer, outside the scope of pure RakNet.
Session::$receivedReliableIndexesgrows indefinitely in very long sessions (no pruning of old, already-acknowledged indexes). Doesn't affect short-term correctness, but it's memory that never gets freed.