Download the PHP package hiblaphp/socket without Composer
On this page you can find all versions of the php package hiblaphp/socket. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package socket
Hibla Socket
Async, non-blocking TCP, TLS, and Unix domain socket library for PHP.
Part of the Hibla ecosystem. Built on top of hiblaphp/stream, hiblaphp/promise and hiblaphp/event-loop. All I/O is non-blocking and driven by the same loop that powers your fibers, timers, and promises.
Contents
Getting started
- Installation
- Introduction
- Quick Start
- Two Styles: Promise Chains vs Await
Connections
- Connection Events
- Event reference
- Ordering guarantees
- Connection lifecycle
- Attaching listeners
Servers
- SocketServer — high-level facade
- TcpServer
- SecureServer — TLS
- UnixServer — Unix domain sockets
- FdServer — file descriptor
- LimitingServer — connection limits
Clients
- Connector — high-level facade
- TcpConnector
- SecureConnector — TLS
- UnixConnector — Unix domain sockets
- TimeoutConnector
- FixedUriConnector
- DNS Resolution
- Happy Eyeballs — RFC 8305
Working with connections
- Reading and writing
- Backpressure
- Cancellation
- Manual cancellation
- Timeouts are rejections not cancellations
- Structured cancellation with CancellationToken
- Cancellation support by connector
- Mid-flight TLS upgrade
- Address inspection
Reference
- Interface summary
- Exception reference
Meta
- Development
- Credits
- License
Installation
This package is currently in beta. Before installing, ensure your
composer.jsonallows beta releases:
Requirements:
- PHP 8.4+
Introduction
PHP's built-in socket functions (stream_socket_server(), stream_socket_client(), stream_socket_accept()) are synchronous and blocking. stream_socket_accept() stalls the entire PHP thread until a client connects. fread() on a socket blocks until data arrives. stream_socket_client() blocks during the TCP handshake. For a single connection in a simple script this is fine. The moment you need to handle multiple connections concurrently (a TCP server serving hundreds of clients, a client making parallel upstream requests, a proxy routing between two streams) blocking on any one operation freezes everything else. The event loop cannot fire timers, cannot resume Fibers, cannot read from other sockets while a blocking call is in progress.
The solution is to hand all socket I/O to the event loop entirely. Instead of calling stream_socket_accept() and waiting, you register a read watcher on the server socket and supply a callback. Instead of calling stream_socket_client() and blocking for the handshake, you open the socket in STREAM_CLIENT_ASYNC_CONNECT mode and register a write watcher. The event loop fires the callback the instant the OS confirms the connection is established. All reads and writes go through non-blocking streams backed by hiblaphp/stream watchers, so the event loop continues driving all other activity while I/O is in flight.
hiblaphp/socket is that abstraction. It provides:
- Servers that accept connections without blocking: TCP, TLS, Unix domain sockets, and file descriptor inheritance for zero-downtime restarts and systemd socket activation.
- Connectors that establish connections without blocking: with automatic DNS resolution, Happy Eyeballs RFC 8305 dual-stack racing, TLS, configurable timeouts, and full cancellation support.
- Connections that expose an event-driven interface for reading and writing: with automatic backpressure tracking, mid-flight TLS upgrade, and direct pipe integration with
hiblaphp/stream.
Every established connection (server-side or client-side) is a ConnectionInterface backed by a hiblaphp/stream DuplexResourceStream. Data arrives as data events. Writes are buffered and drained asynchronously. Backpressure is tracked automatically via write()'s return value and the drain event. Connections integrate directly with hiblaphp/stream's pipe() so you can wire a file stream to a socket, or two sockets to each other, with one line and no manual flow control.
The library supports two coding styles throughout. Promise chains give you maximum throughput and zero Fiber overhead. This is the right choice for performance-critical paths where you are establishing thousands of connections per second. Optionally, await() from hiblaphp/async gives you sequential-looking code that is easier to read and reason about. This is the right choice for application-level logic where the overhead of Fiber suspension is invisible. Both styles compose freely: you can mix them in the same codebase, the same function, or the same Promise::all() call.
Quick Start
Echo server
TCP client
Two Styles: Promise Chains vs Await
Every connector method returns a PromiseInterface. You can consume it using raw promise chains or using await() from hiblaphp/async. Both styles are fully supported. Choose based on context.
await() suspends the current Fiber and resumes it when the promise settles, letting you write sequential-looking code without blocking the event loop. Promise chains are better when you need fine-grained control over branching, when you are not inside a Fiber context, or when you want to fire multiple connections concurrently without waiting on each one.
For performance-critical code or high-throughput scenarios (a proxy handling thousands of simultaneous connections, a load balancer, or any path where you are establishing connections in a tight loop) prefer pure promise chains over await(). Fiber suspension and resumption carries a small but measurable overhead per operation. At low concurrency this is invisible; at high concurrency it accumulates. If you are benchmarking or squeezing every last RPS, remove await() from the hot path and use .then() chains instead.
Connecting — promise chain style
Connecting — await style
Concurrent connections — promise chain style
Promise chains are the natural fit when firing multiple connections at once. Promise::all() runs them concurrently and resolves when every connection settles:
Concurrent connections — await style
TLS — promise chain style
TLS — await style
Connection Events
All established connections (whether from a server's connection event or a connector's resolved promise) implement ConnectionInterface and expose the same event API. Understanding these events before working with servers and clients makes every example in this document easier to follow.
Event reference
| Event | Arguments | When it fires |
|---|---|---|
data |
string $chunk |
A chunk of data arrives from the remote end |
end |
— | The remote end half-closes; no more data events will follow |
drain |
— | The write buffer drops below the soft limit; safe to write again |
close |
— | The connection is fully closed and the resource is freed |
error |
\Throwable $e |
A stream error occurred; the connection closes immediately after |
Ordering guarantees
endalways fires beforecloseon a clean remote half-close. If the remote end closes abruptly (TCP RST, process killed),endis skipped andclosefires directly.erroris always followed byclose. The connection closes itself after emittingerror, so you do not need to callclose()inside an error handler.- After
closefires, all listeners are removed. Any listener attached afterclosewill never fire. drainonly fires if a previouswrite()returnedfalse. If the buffer never fills,drainnever fires.
Always attach an
errorlistener on every connection. An unhandlederrorevent on anEventEmitterpropagates and may terminate your process.
Connection lifecycle
Attaching listeners
Promise chain style:
Await style:
Servers
SocketServer
SocketServer is the recommended entry point for most use cases. It inspects the URI scheme and instantiates the appropriate server (TcpServer, SecureServer, or UnixServer) automatically.
The $context array accepts three top-level keys (tcp, tls, and unix) each containing standard PHP stream context options for that transport:
All server types emit the same events:
| Event | Arguments | When |
|---|---|---|
connection |
ConnectionInterface $connection |
A new client connects |
error |
\Throwable $error |
A non-fatal error occurs (e.g. accept failure) |
TcpServer
Low-level TCP server. Binds to an IP and port.
Context options: any socket stream context option is accepted:
SecureServer
Wraps a TcpServer and performs the TLS handshake on every incoming connection before emitting it. Connections are only emitted after encryption is fully established.
Failed TLS handshakes emit an error event on the server and close the connection. They do not crash the server.
UnixServer
Listens on a Unix domain socket path.
The socket file is created on construction and removed automatically on close(). If the path already exists and is actively in use, AddressInUseException is thrown. A stale socket file (no listener) is removed and replaced automatically.
FdServer
Inherits a listening socket from a parent process via a file descriptor number. Useful for socket-activated services (systemd) or zero-downtime restarts.
LimitingServer
Decorates any ServerInterface to enforce a maximum number of concurrent connections.
Two modes when the limit is reached:
Pause mode provides true backpressure. The kernel's accept queue backs up instead of dropping connections. Reject mode is better when you want to send an explicit error response to the client before closing.
Clients
Connector
Connector is the recommended entry point for client connections. It handles DNS resolution, Happy Eyeballs dual-stack racing, TLS, Unix sockets, and timeouts behind a single connect() call.
Configuration options:
TcpConnector
Establishes a raw non-blocking TCP connection to an IP address. Does not perform DNS resolution. Pass a resolved IP.
SecureConnector
Wraps a TcpConnector (or any ConnectorInterface) and performs a TLS handshake after the transport is established.
UnixConnector
Connects to a Unix domain socket. The connection is established synchronously. By the time connect() returns a promise, the connection is already made and the promise is already resolved.
TimeoutConnector
Decorates any ConnectorInterface and rejects the promise with TimeoutException if the connection is not established within the given number of seconds. The timeout covers the entire connection process including DNS and TLS.
FixedUriConnector
Always connects to a pre-configured URI regardless of the URI passed to connect(). Useful for proxies, tunnels, and test mocks.
DNS Resolution
Connector handles DNS automatically. Under the hood it uses DnsConnector (single-stack) or HappyEyeBallsConnector (dual-stack, default).
If you need manual DNS resolution, inject a custom ResolverInterface:
To bypass DNS entirely (IP-only environments or when you resolve yourself):
Happy Eyeballs
When happy_eyeballs is enabled (the default), Connector implements RFC 8305:
- AAAA (IPv6) resolution starts immediately.
- A (IPv4) resolution starts after a 50ms delay, or immediately if AAAA resolves first.
- Connection attempts interleave IPv6 and IPv4 addresses from the queue.
- A 250ms delay is inserted between each attempt.
- The first successful connection wins. All others are cancelled.
Working with Connections
Reading and writing
Backpressure
write() returns false when the write buffer is full. Stop writing and wait for the drain event before continuing.
Promise chain style:
Await style:
If you are piping a Hibla stream into a connection, backpressure is handled automatically with no manual pause()/resume() needed:
Cancellation
Every connect() call returns a PromiseInterface. You can cancel an in-flight connection attempt by calling cancel() on the returned promise. Cancellation immediately aborts the underlying operation. DNS lookups, TCP handshakes, and TLS negotiations are all torn down cleanly with no dangling file descriptors or event loop watchers left behind.
Cancellation in Hibla is a distinct state from rejection. Calling cancel() on a promise does not trigger any registered then() or catch() handlers. The promise silently transitions to the cancelled state and all pending callbacks are cleared. Use onCancel() on the promise if you need to react to cancellation in a promise chain, or use await() with a CancellationToken, which throws CancelledException that you can catch with a normal try/catch.
Manual cancellation
In a pure promise chain there is no await(), so cancellation is silent: then() and catch() never fire. Register an onCancel() handler before cancelling if you need to react:
Timeouts are rejections, not cancellations
TimeoutConnector behaves differently from manually calling cancel(). When the timeout fires, it rejects the outer promise with a TimeoutException. This is a rejection and does trigger registered catch() handlers. Internally it cancels the pending connection, but the promise your code receives is rejected, not cancelled:
Structured cancellation with CancellationToken
For coordinating cancellation across multiple connections or operations from a single control point, use CancellationTokenSource from hiblaphp/cancellation. The source owns the cancel signal. You pass the readonly $token into operations and call cancel() on the source when you want everything to stop.
Single connection — await style
When using await($promise, $token), the token automatically tracks the promise with no manual track() needed. If the token is cancelled while await() is suspended, CancelledException is thrown:
Single connection — promise chain style
In a promise chain you must call $token->track() manually. Since cancellation is not rejection, catch() does not fire. Use onCancel() to react:
Multiple concurrent connections
One token cancels all tracked connections at once regardless of which phase each is in (DNS, TCP handshake, or TLS):
Combining user abort and timeout
createLinkedTokenSource() creates a token that cancels when any of the linked tokens fires. This is the standard way to combine a user-initiated abort with a hard deadline:
Optional token with CancellationToken::none()
If you are writing a function that should optionally support cancellation, use CancellationToken::none() as the default. All token methods work correctly on it without any null checks. track() is a safe no-op and throwIfCancelled() never throws:
Cancellation support by connector
Not every connector supports cancellation. The table below documents which connectors respond to cancel() and what happens when you call it.
| Connector | Cancellable | What cancellation does |
|---|---|---|
TcpConnector |
✅ | Removes the write watcher and closes the socket immediately |
SecureConnector |
✅ | Cancels the pending TCP connection or TLS handshake, whichever is in flight |
DnsConnector |
✅ | Cancels the DNS lookup if still resolving, or the connection attempt if DNS already completed |
HappyEyeBallsConnector |
✅ | Cancels all pending resolver promises, connection attempts, and clears all internal timers |
TimeoutConnector |
✅ | Cancels the timeout timer and propagates cancellation to the underlying connector |
FixedUriConnector |
✅ | Delegates to the underlying connector and inherits its cancellation behaviour |
Connector (facade) |
✅ | Delegates to whichever connector handles the URI scheme |
UnixConnector |
❌ | Not cancellable — see note below |
UnixConnector is not cancellable. Unix domain socket connections are established synchronously via a single stream_socket_client() call. By the time connect() returns a promise, the connection is already established and the promise is already resolved. There is nothing in flight to cancel. Calling cancel() on the returned promise is a no-op. Passing a CancellationToken to await() with a UnixConnector promise is equally a no-op. The promise is already settled before the token has a chance to fire.
If you need to enforce a time limit on a Unix socket connection, wrap it in a TimeoutConnector. The timeout fires as a rejection (not a cancellation) so catch() handlers fire normally:
Mid-flight TLS upgrade
Useful for protocols that start in plaintext and upgrade to TLS (MySQL, SMTP STARTTLS, PostgreSQL).
Important: Use
once()instead ofon()for the plain negotiation phase.enableEncryption()upgrades the underlying stream in place but does not remove listeners you registered before the upgrade. If you useon()for the plain phase, that listener stays attached and will fire again for every subsequent chunk, including data that arrives over the secure channel after the upgrade.once()removes itself automatically after the first call, so secure data only reaches the listeners you register after the upgrade completes.
Server side — promise chain style:
Server side — await style:
Client side — promise chain style:
Client side — await style:
Address inspection
Interface summary
| Interface | Implemented by |
|---|---|
ServerInterface |
TcpServer, UnixServer, SecureServer, FdServer, SocketServer, LimitingServer |
ConnectorInterface |
TcpConnector, UnixConnector, SecureConnector, DnsConnector, HappyEyeBallsConnector, TimeoutConnector, FixedUriConnector, Connector |
ConnectionInterface |
Connection |
Type-hint against the interfaces rather than concrete classes:
Exception reference
All exceptions extend Hibla\Socket\Exceptions\SocketException which extends \RuntimeException.
| Exception | When it is thrown |
|---|---|
ConnectionFailedException |
TCP handshake failed, DNS lookup failed, or connection was refused |
TimeoutException |
Connection attempt exceeded the configured timeout (extends ConnectionFailedException) |
EncryptionFailedException |
TLS handshake failed or connection was lost during handshake |
InvalidUriException |
Malformed or unsupported URI passed to a connector or server |
BindFailedException |
Server failed to bind to the given address (port in use, bad path, invalid FD) |
AddressInUseException |
Unix socket path is already actively in use (extends BindFailedException) |
AcceptFailedException |
stream_socket_accept() failed on an otherwise healthy server |
Development
Credits
- API Design: Inspired by ReactPHP Socket. If you are familiar with ReactPHP's socket API, Hibla's will feel immediately familiar, with the addition of native promise-based methods, Fiber-aware
await()support, Happy Eyeballs dual-stack connection racing out of the box, and first-class structured cancellation viahiblaphp/cancellation. - Event Emitter: Built on evenement/evenement.
- Stream Layer: Built on hiblaphp/stream.
- Event Loop Integration: Powered by hiblaphp/event-loop.
- Promise Integration: Built on hiblaphp/promise.
- DNS Resolution: Powered by hiblaphp/dns.
License
MIT License. See LICENSE for more information.
All versions of socket with dependencies
evenement/evenement Version ^3.0
hiblaphp/event-loop Version ^1.0
hiblaphp/stream Version ^1.0
hiblaphp/promise Version ^1.0
hiblaphp/dns Version ^1.0