Search by

matasarei / php-tcp

matasarei

Lightweight TCP client for PHP with pluggable socket transports, configurable timeouts, delimiter-based response framing and PSR-3 logging

Package info

github.com/matasarei/php-tcp

pkg:composer/matasarei/php-tcp

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.3.1 2026-09-07 00:39 UTC

This package is auto-updated.

Last update: 2026-09-07 00:39:51 UTC


README

Tests Latest Version PHP Version Require License

A lightweight TCP client for PHP with no runtime dependencies beyond a PSR-3 logger interface. It wraps PHP's native stream functions in a small, testable API: pluggable socket transports, configurable timeouts, optional delimiter-based response framing and PSR-3 logging.

Well suited for simple text-based request/response protocols — line-delimited JSON, JSON-RPC over TCP, custom device or legacy service protocols, and similar.

Features

  • Simple connect / request / disconnect API over plain TCP
  • Two built-in transports — StreamSocket (stream_socket_client) and FSocket (fsockopen) — plus a one-method SocketInterface for custom transports
  • Optional end-of-response delimiter for line-based protocols (see Response framing)
  • Detects broken streams and connections closed by the peer; completes partial writes
  • Configurable connection, request and read timeouts, fractions of a second included
  • A response size limit, so a peer that keeps sending cannot exhaust memory (8 MiB by default)
  • PSR-3 (psr/log v1, v2 or v3) logger support for debugging

Requirements

  • PHP 7.4 or newer

Installation

composer require matasarei/php-tcp

Quick start

use Matasar\PhpTcp\Client;
use Matasar\PhpTcp\Request;
use Matasar\PhpTcp\Socket\StreamSocket;

$client = new Client('ip_or_hostname', 8888, new StreamSocket());
$client->connect();

$response = $client->request(new Request('request data'));
$client->disconnect();

var_dump($response->getData());

Request accepts an optional per-request timeout in seconds (default 30):

$request = new Request('request data', 5);

Socket transports

The library includes two transports: StreamSocket and FSocket. The difference is stream_socket_client() vs fsockopen() under the hood; pick whichever you prefer, or implement Socket\SocketInterface to supply your own (e.g. for TLS wrappers or unit-test stubs).

Both transports take a read timeout (seconds, default 1) that controls how long a single read waits for data before reporting "no data yet", and an optional blocking flag:

use Matasar\PhpTcp\Socket\FSocket;

new FSocket(2);        // wait up to 2 seconds per read cycle
new FSocket(0);        // non-blocking mode
new FSocket(2, false); // non-blocking, but still a 2 second read timeout

The second argument defaults to null, which means "blocking when the timeout is positive" — the rule the first argument used to decide on its own.

The host is the address only: a value carrying a scheme, such as udp://198.51.100.7, is rejected rather than quietly opening something other than a TCP socket. IPv6 literals are accepted as-is (::1).

Client settings

use Matasar\PhpTcp\Client;
use Matasar\PhpTcp\Socket\FSocket;

$client = new Client('hostname', 1234, new FSocket());

$client->setChunkSize(16384);          // read data by 16 KB per cycle (default 8 KB).
$client->setPollInterval(5000);        // wait 5 ms between data availability checks (default 1 ms).
$client->setDelimiter("\n");           // treat "\n" as the end of a response (see below).
$client->setMaxResponseSize(65536);    // refuse a response over 64 KB (default 8 MiB, null for no limit).
$client->setLogger(new PsrLogger());   // any PSR-3 logger, for debugging.

$client->connect(5);   // connection timeout in seconds (default 2).
$client->connect(0.5); // fractions are allowed.

Response framing

By default, the client considers a response complete when the server stops sending data for a moment (a silent interval on the stream). This works without any protocol knowledge, but it has two downsides: every read costs an extra blocking-timeout interval, and a server that stalls mid-response can have its reply cut short.

If your protocol marks the end of a message — like line-based protocols such as JSON-RPC over TCP — set a delimiter instead:

$client->setDelimiter("\n");

With a delimiter set, the client returns as soon as the response ends with the delimiter (the delimiter is kept in the response data). It throws a RequestException if a complete response does not arrive within the request timeout, and a ConnectionException if the connection is closed before the response is completed.

Error handling

All exceptions live under Matasar\PhpTcp\Exception:

Exception Thrown when
ConnectionException Connection could not be established, is already open, was closed by the peer, or the stream broke mid-transfer.
RequestException No (or no complete) response arrived within the request timeout, or the response exceeded the size limit.
SocketException Low-level transport failure; wrapped into ConnectionException by connect().
use Matasar\PhpTcp\Exception\ConnectionException;
use Matasar\PhpTcp\Exception\RequestException;

try {
    $client->connect();
    $response = $client->request($request);
} catch (ConnectionException $exception) {
    // failed to connect / connection lost
} catch (RequestException $exception) {
    // the server did not respond in time
}

Testing

The test suite runs against PHP 7.4–8.5 in CI.

Locally, the easiest way is Docker — no PHP or Composer installation required:

docker run --rm -v $(pwd):/app -w /app composer:2 composer install
docker run --rm -v $(pwd):/app -w /app composer:2 vendor/bin/phpunit

Or with a local PHP setup:

composer install
vendor/bin/phpunit

To check the dependencies against published advisories:

docker run --rm -v $(pwd):/app -w /app composer:2 composer audit

License

Released under the MIT license.