spiral / roadrunner-tcp
PHP worker for the RoadRunner TCP plugin: handle raw TCP connection events and data in RoadRunner workers
Package info
pkg:composer/spiral/roadrunner-tcp
Fund package maintenance!
Requires
- php: >=8.2
- ext-json: *
- roadrunner/worker: ^3.8
- spiral/roadrunner: ^3.0
Requires (Dev)
- jetbrains/phpstorm-attributes: ^1.0
- mockery/mockery: ^1.6
- spiral/code-style: ^2.3.1
- testo/bridge-mockery: ^0.1.2
- testo/testo: ^0.10.55
- vimeo/psalm: ^6 || ^7
Suggests
- roadrunner/cli: Provides RoadRunner installation and management CLI tools
Provides
None
Conflicts
None
Replaces
- spiral/roadrunner-tcp: 4.3.0
- 4.x-dev
- 4.3.0
- v4.2.0
- v4.1.1
- v4.1.0
- v4.0.0
- 3.x-dev
- v3.1.0
- 3.0.0
- 2.x-dev
- v2.0.1
- v2.0.0
- dev-release-please--branches--4.x--components--roadrunner/tcp
- dev-release-please--branches--4.x--components--spiral/roadrunner-tcp
- dev-rr-v3
- dev-static-analysis
- dev-ci/mutation-testing
- dev-coverage
- dev-testo
- dev-ci/code-style
- dev-chore/codeowners
- dev-docs/readme
- dev-ci/security-audit
- dev-ci/release-please
This package is auto-updated.
Last update: 2026-10-10 16:26:21 UTC
README
PHP worker for the RoadRunner TCP plugin
RoadRunner can serve raw TCP connections and pass their events to PHP workers. This package provides the worker side: it receives connection events and data from the TCP servers configured in RoadRunner and responds, keeps reading, or closes the connection.
Warning
The TCP plugin is not included in the standard RoadRunner v3 build. To use the tcp: plugin with RoadRunner v3,
build the server yourself with Velox
and add the github.com/roadrunner-server/tcp/v6 plugin, or stay on RoadRunner v2025.
At the time of the RoadRunner v3.0.0 release, github.com/roadrunner-server/tcp/v6 has only beta tags.
This does not affect the tcp:// transport for RPC or worker relays.
See the TCP plugin documentation.
Get Started
Installation
composer require roadrunner/tcp
Application Server
The package contains only the PHP worker; the RoadRunner binary is installed separately. You can use the convenient installer to download the latest available compatible version of RoadRunner assembly:
composer require roadrunner/cli --dev
To download latest version of application server (the standard build, without the TCP plugin):
vendor/bin/rr get
Configuration
Declare the TCP servers and the worker pool in .rr.yaml:
server: command: "php worker.php" tcp: servers: smtp: addr: tcp://127.0.0.1:1025 delimiter: "\r\n" # by default server2: addr: tcp://127.0.0.1:8889 pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 60s
If you have more than 1 worker in your pool TCP server will send received packets to different workers, and if you need to collect data you have to use storage, that can be accessed by all workers, for example RoadRunner Key Value.
See the TCP plugin documentation for all options.
Writing a Worker
worker.php wraps the RoadRunner worker into TcpWorker and handles connection events in a loop:
<?php require __DIR__ . '/vendor/autoload.php'; use Spiral\RoadRunner\Worker; use Spiral\RoadRunner\Tcp\TcpWorker; use Spiral\RoadRunner\Tcp\TcpResponse; use Spiral\RoadRunner\Tcp\TcpEvent; // Create new RoadRunner worker from global environment $worker = Worker::create(); $tcpWorker = new TcpWorker($worker); while ($request = $tcpWorker->waitRequest()) { try { if ($request->getEvent() === TcpEvent::Connected) { // You can close connection according your restrictions if ($request->getRemoteAddress() !== '127.0.0.1') { $tcpWorker->close(); continue; } // ----------------- // Or continue read data from server // By default, server closes connection if a worker doesn't send CONTINUE response $tcpWorker->read(); // ----------------- // Or send response to the TCP connection, for example, to the SMTP client $tcpWorker->respond("220 mailamie \r\n"); } elseif ($request->getEvent() === TcpEvent::Data) { $body = $request->getBody(); // ... handle request from TCP server [smtp] if ($request->getServer() === 'smtp') { // Send response and close connection $tcpWorker->respond('Access denied', TcpResponse::RespondClose); // ... handle request from TCP server [server2] } elseif ($request->getServer() === 'server2') { // Send response to the TCP connection and wait for the next request $tcpWorker->respond(\json_encode([ 'remote_addr' => $request->getRemoteAddress(), 'server' => $request->getServer(), 'uuid' => $request->getConnectionUuid(), 'body' => $request->getBody(), 'event' => $request->getEvent() ])); } // Handle closed connection event } elseif ($request->getEvent() === TcpEvent::Close) { // Do something ... // You don't need to send response on closed connection } } catch (\Throwable $e) { $tcpWorker->respond("Something went wrong\r\n", TcpResponse::RespondClose); $worker->error((string)$e); } }
Testing
composer tests