Search by

spiral / roadrunner-tcp

wolfy-j

PHP worker for the RoadRunner TCP plugin: handle raw TCP connection events and data in RoadRunner workers

Package info

github.com/roadrunner-php/tcp

Homepage

Issues

Chat

Forum

Documentation

pkg:composer/spiral/roadrunner-tcp

Fund package maintenance!

roadrunner-server

Statistics

Installs: 2 425 458

Dependents: 1

Suggesters: 0

Stars: 9

4.3.0 2026-10-10 16:24 UTC

README

RoadRunner

PHP worker for the RoadRunner TCP plugin

Documentation Sponsor

Psalm Level Type Coverage Codecov Mutation testing badge


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

PHP Latest Version on Packagist License Total Downloads

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);
    }
}
try Spiral Framework

Testing

composer tests