PHPackages                             hiblaphp/socket - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. hiblaphp/socket

ActiveLibrary[HTTP &amp; Networking](/categories/http)

hiblaphp/socket
===============

Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for HiblaPHP

04701PHPCI passing

Since Mar 30Pushed 1mo agoCompare

[ Source](https://github.com/hiblaphp/socket)[ Packagist](https://packagist.org/packages/hiblaphp/socket)[ RSS](/packages/hiblaphp-socket/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (1)

Hibla Socket
============

[](#hibla-socket)

**Async, non-blocking TCP, TLS, and Unix domain socket library for PHP.**

Part of the [Hibla](https://github.com/hiblaphp) ecosystem. Built on top of `hiblaphp/async`'s event loop. All I/O is non-blocking and driven by the same loop that powers your fibers, timers, and promises.

[![Latest Release](https://camo.githubusercontent.com/d38a91a00887e1bd6527ac84ae87e1276ae6b32c5552ed446bd3425a6e2844a9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f6869626c617068702f736f636b65742e7376673f7374796c653d666c61742d737175617265)](https://github.com/hiblaphp/socket/releases)[![MIT License](https://camo.githubusercontent.com/942e017bf0672002dd32a857c95d66f28c5900ab541838c6c664442516309c8a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e7376673f7374796c653d666c61742d737175617265)](./LICENSE)

---

Contents
--------

[](#contents)

**Getting started**

- [Installation](#installation)
- [Introduction](#introduction)
- [Quick Start](#quick-start)
- [Two Styles: Promise Chains vs Await](#two-styles-promise-chains-vs-await)

**Connections**

- [Connection Events](#connection-events)
    - [Event reference](#event-reference)
    - [Ordering guarantees](#ordering-guarantees)
    - [Connection lifecycle](#connection-lifecycle)
    - [Attaching listeners](#attaching-listeners)

**Servers**

- [SocketServer — high-level facade](#socketserver)
- [TcpServer](#tcpserver)
- [SecureServer — TLS](#secureserver)
- [UnixServer — Unix domain sockets](#unixserver)
- [FdServer — file descriptor](#fdserver)
- [LimitingServer — connection limits](#limitingserver)

**Clients**

- [Connector — high-level facade](#connector)
- [TcpConnector](#tcpconnector)
- [SecureConnector — TLS](#secureconnector)
- [UnixConnector — Unix domain sockets](#unixconnector)
- [TimeoutConnector](#timeoutconnector)
- [FixedUriConnector](#fixeduriconnector)
- [DNS Resolution](#dns-resolution)
- [Happy Eyeballs — RFC 8305](#happy-eyeballs)

**Working with connections**

- [Reading and writing](#reading-and-writing)
- [Backpressure](#backpressure)
- [Cancellation](#cancellation)
    - [Manual cancellation](#manual-cancellation)
    - [Timeouts are rejections not cancellations](#timeouts-are-rejections-not-cancellations)
    - [Structured cancellation with CancellationToken](#structured-cancellation-with-cancellationtoken)
    - [Cancellation support by connector](#cancellation-support-by-connector)
- [Mid-flight TLS upgrade](#mid-flight-tls-upgrade)
- [Address inspection](#address-inspection)

**Reference**

- [Interface summary](#interface-summary)
- [Exception reference](#exception-reference)

**Meta**

- [Development](#development)
- [Credits](#credits)
- [License](#license)

---

Installation
------------

[](#installation)

```
composer require hiblaphp/socket
```

**Requirements:**

- PHP 8.3+
- `hiblaphp/stream`
- `hiblaphp/promise`
- `hiblaphp/event-loop`
- `hiblaphp/dns`
- `evenement/evenement`

---

Introduction
------------

[](#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
-----------

[](#quick-start)

### Echo server

[](#echo-server)

```
use Hibla\Socket\SocketServer;

$server = new SocketServer('tcp://127.0.0.1:8080');

$server->on('connection', function ($connection) {
    $connection->on('data', function (string $data) use ($connection) {
        $connection->write($data);
    });

    $connection->on('error', function (\Throwable $e) {
        echo "Connection error: " . $e->getMessage() . "\n";
    });
});

$server->on('error', function (\Throwable $e) {
    echo "Server error: " . $e->getMessage() . "\n";
});

echo "Listening on " . $server->getAddress() . "\n";
```

### TCP client

[](#tcp-client)

```
use Hibla\Socket\Connector;
use function Hibla\await;

$connector  = new Connector();
$connection = await($connector->connect('tcp://example.com:80'));

$connection->write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");

$connection->on('data', function (string $data) {
    echo $data;
});

$connection->on('error', function (\Throwable $e) {
    echo "Error: " . $e->getMessage() . "\n";
});
```

---

Two Styles: Promise Chains vs Await
-----------------------------------

[](#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--promise-chain-style)

```
use Hibla\Socket\Connector;
use Hibla\Socket\Exceptions\ConnectionFailedException;

$connector = new Connector();

$connector->connect('tcp://example.com:80')
    ->then(function ($connection) {
        $connection->on('data', function (string $data) use ($connection) {
            echo $data;
            $connection->close();
        });

        $connection->on('error', function (\Throwable $e) {
            echo "Connection error: " . $e->getMessage() . "\n";
        });

        $connection->write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");
    })
    ->catch(function (ConnectionFailedException $e) {
        echo "Could not connect: " . $e->getMessage() . "\n";
    });
```

### Connecting — await style

[](#connecting--await-style)

```
use Hibla\Socket\Connector;
use Hibla\Socket\Exceptions\ConnectionFailedException;
use function Hibla\{async, await};

await(async(function () {
    $connector = new Connector();

    try {
        $connection = await($connector->connect('tcp://example.com:80'));
    } catch (ConnectionFailedException $e) {
        echo "Could not connect: " . $e->getMessage() . "\n";
        return;
    }

    $connection->write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");

    $connection->on('data', function (string $data) use ($connection) {
        echo $data;
        $connection->close();
    });

    $connection->on('error', function (\Throwable $e) {
        echo "Connection error: " . $e->getMessage() . "\n";
    });
}));
```

### Concurrent connections — promise chain 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:

```
use Hibla\Socket\Connector;
use Hibla\Promise\Promise;

$connector = new Connector();

$hosts = [
    'tcp://server-a.internal:9000',
    'tcp://server-b.internal:9000',
    'tcp://server-c.internal:9000',
];

Promise::all(array_map(
    fn(string $uri) => $connector->connect($uri)
        ->then(function ($connection) use ($uri) {
            $connection->write("PING\n");
            $connection->on('error', fn(\Throwable $e) => echo "$uri error: " . $e->getMessage() . "\n");
            return $uri . ' OK';
        })
        ->catch(fn(\Throwable $e) => $uri . ' FAILED: ' . $e->getMessage()),
    $hosts
))->then(function (array $results) {
    foreach ($results as $result) {
        echo $result . "\n";
    }
});
```

### Concurrent connections — await style

[](#concurrent-connections--await-style)

```
use Hibla\Socket\Connector;
use Hibla\Promise\Promise;
use function Hibla\{async, await};

await(async(function () {
    $connector = new Connector();

    $hosts = [
        'tcp://server-a.internal:9000',
        'tcp://server-b.internal:9000',
        'tcp://server-c.internal:9000',
    ];

    $results = await(Promise::all(array_map(
        fn(string $uri) => $connector->connect($uri)
            ->then(function ($connection) use ($uri) {
                $connection->write("PING\n");
                $connection->on('error', fn(\Throwable $e) => echo "$uri error: " . $e->getMessage() . "\n");
                return $uri . ' OK';
            })
            ->catch(fn(\Throwable $e) => $uri . ' FAILED: ' . $e->getMessage()),
        $hosts
    )));

    foreach ($results as $result) {
        echo $result . "\n";
    }
}));
```

### TLS — promise chain style

[](#tls--promise-chain-style)

```
use Hibla\Socket\Connector;
use Hibla\Socket\Exceptions\EncryptionFailedException;
use Hibla\Socket\Exceptions\ConnectionFailedException;

$connector = new Connector(['tls' => ['verify_peer' => true]]);

$connector->connect('tls://example.com:443')
    ->then(function ($connection) {
        echo "Connected: " . $connection->getRemoteAddress() . "\n";
        $connection->write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");

        $connection->on('data', fn(string $data) => echo $data);
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    })
    ->catch(function (EncryptionFailedException $e) {
        echo "TLS failed: " . $e->getMessage() . "\n";
    })
    ->catch(function (ConnectionFailedException $e) {
        echo "Connection failed: " . $e->getMessage() . "\n";
    });
```

### TLS — await style

[](#tls--await-style)

```
use Hibla\Socket\Connector;
use Hibla\Socket\Exceptions\EncryptionFailedException;
use Hibla\Socket\Exceptions\ConnectionFailedException;
use function Hibla\{async, await};

await(async(function () {
    $connector = new Connector(['tls' => ['verify_peer' => true]]);

    try {
        $connection = await($connector->connect('tls://example.com:443'));
    } catch (EncryptionFailedException $e) {
        echo "TLS failed: " . $e->getMessage() . "\n";
        return;
    } catch (ConnectionFailedException $e) {
        echo "Connection failed: " . $e->getMessage() . "\n";
        return;
    }

    echo "Connected: " . $connection->getRemoteAddress() . "\n";
    $connection->write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");

    $connection->on('data', fn(string $data) => echo $data);
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
}));
```

---

Connection Events
-----------------

[](#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-reference)

EventArgumentsWhen 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

[](#ordering-guarantees)

- `end` always fires before `close` on a clean remote half-close. If the remote end closes abruptly (TCP RST, process killed), `end` is skipped and `close` fires directly.
- `error` is always followed by `close`. The connection closes itself after emitting `error`, so you do not need to call `close()` inside an error handler.
- After `close` fires, all listeners are removed. Any listener attached after `close` will never fire.
- `drain` only fires if a previous `write()` returned `false`. If the buffer never fills, `drain` never fires.

> **Always attach an `error` listener** on every connection. An unhandled `error` event on an `EventEmitter` propagates and may terminate your process.

### Connection lifecycle

[](#connection-lifecycle)

```
Connection established (server 'connection' event or connector promise resolved)
       │
       ▼
  ┌──────────┐
  │  OPEN    │
  └────┬─────┘
       │
       │ data arrives from remote
       ├──────────────── emit('data', $chunk)     ← repeats for each chunk
       │
       │ write buffer exceeds soft limit
       ├──────────────── write() returns false    ← backpressure signal
       │
       │ write buffer drains below soft limit
       ├──────────────── emit('drain')            ← safe to write again
       │
       │ remote half-closes (clean EOF)
       ├──────────────── emit('end')
       │                 emit('close')            ← always follows 'end'
       │                 resource freed
       │
       │ close() called locally
       ├──────────────── emit('close')
       │                 resource freed
       │
       │ stream error (broken pipe, reset, etc.)
       └──────────────── emit('error', $e)
                         emit('close')            ← always follows 'error'
                         resource freed

```

### Attaching listeners

[](#attaching-listeners)

**Promise chain style:**

```
$connector->connect('tcp://example.com:9000')
    ->then(function ($connection) {
        $connection->on('data', function (string $chunk) {
            echo "Received: " . $chunk;
        });

        $connection->on('end', function () {
            echo "Remote closed the write side\n";
        });

        $connection->on('drain', function () use ($connection) {
            echo "Buffer drained — resuming writes\n";
        });

        $connection->on('close', function () {
            echo "Connection fully closed\n";
        });

        $connection->on('error', function (\Throwable $e) {
            echo "Error: " . $e->getMessage() . "\n";
        });

        $connection->write("Hello\n");
    })
    ->catch(function (\Throwable $e) {
        echo "Could not connect: " . $e->getMessage() . "\n";
    });
```

**Await style:**

```
use function Hibla\{async, await};

await(async(function () use ($connector) {
    try {
        $connection = await($connector->connect('tcp://example.com:9000'));
    } catch (\Throwable $e) {
        echo "Could not connect: " . $e->getMessage() . "\n";
        return;
    }

    $connection->on('data', function (string $chunk) {
        echo "Received: " . $chunk;
    });

    $connection->on('end', function () {
        echo "Remote closed the write side\n";
    });

    $connection->on('drain', function () use ($connection) {
        echo "Buffer drained — resuming writes\n";
    });

    $connection->on('close', function () {
        echo "Connection fully closed\n";
    });

    $connection->on('error', function (\Throwable $e) {
        echo "Error: " . $e->getMessage() . "\n";
    });

    $connection->write("Hello\n");
}));
```

---

Servers
-------

[](#servers)

### SocketServer

[](#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.

```
use Hibla\Socket\SocketServer;

// TCP
$server = new SocketServer('tcp://0.0.0.0:8080');

// TLS
$server = new SocketServer('tls://0.0.0.0:8443', [
    'tls' => [
        'local_cert' => '/path/to/cert.pem',
        'local_pk'   => '/path/to/key.pem',
    ],
]);

// Unix domain socket
$server = new SocketServer('unix:///var/run/app.sock');
```

The `$context` array accepts three top-level keys (`tcp`, `tls`, and `unix`) each containing standard PHP stream context options for that transport:

```
$server = new SocketServer('tcp://0.0.0.0:8080', [
    'tcp' => [
        'so_reuseport' => true,
        'backlog'      => 65535,
    ],
]);
```

All server types emit the same events:

EventArgumentsWhen`connection``ConnectionInterface $connection`A new client connects`error``\Throwable $error`A non-fatal error occurs (e.g. accept failure)```
$server->on('connection', function ($connection) {
    echo "New connection from " . $connection->getRemoteAddress() . "\n";

    $connection->on('data', fn(string $data) => $connection->write($data));
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    $connection->on('close', fn() => echo "Client disconnected\n");
});

$server->on('error', function (\Throwable $e) {
    echo "Server error: " . $e->getMessage() . "\n";
});
```

---

### TcpServer

[](#tcpserver)

Low-level TCP server. Binds to an IP and port.

```
use Hibla\Socket\TcpServer;

$server = new TcpServer('127.0.0.1:8080');
// or bind to all interfaces
$server = new TcpServer('0.0.0.0:8080');
// or a random available port
$server = new TcpServer('127.0.0.1:0');

echo $server->getAddress(); // tcp://127.0.0.1:43210
```

**Context options:** any `socket` stream context option is accepted:

```
$server = new TcpServer('0.0.0.0:8080', [
    'so_reuseport' => true,
    'backlog'      => 65535,
]);
```

---

### SecureServer

[](#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.

```
use Hibla\Socket\TcpServer;
use Hibla\Socket\SecureServer;

$tcp    = new TcpServer('0.0.0.0:8443');
$server = new SecureServer($tcp, [
    'local_cert'        => '/path/to/cert.pem',
    'local_pk'          => '/path/to/key.pem',
    'verify_peer'       => false,
    'allow_self_signed' => true,
]);

$server->on('connection', function ($connection) {
    echo $connection->getRemoteAddress() . "\n"; // tls://...
    $connection->on('data', fn(string $data) => $connection->write($data));
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
});
```

Failed TLS handshakes emit an `error` event on the server and close the connection. They do not crash the server.

---

### UnixServer

[](#unixserver)

Listens on a Unix domain socket path.

```
use Hibla\Socket\UnixServer;

$server = new UnixServer('/var/run/app.sock');
// or with scheme prefix
$server = new UnixServer('unix:///var/run/app.sock');
```

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

[](#fdserver)

Inherits a listening socket from a parent process via a file descriptor number. Useful for socket-activated services (systemd) or zero-downtime restarts.

```
use Hibla\Socket\FdServer;

// From a file descriptor number
$server = new FdServer(3);

// From a php://fd URI
$server = new FdServer('php://fd/3');
```

---

### LimitingServer

[](#limitingserver)

Decorates any `ServerInterface` to enforce a maximum number of concurrent connections.

```
use Hibla\Socket\TcpServer;
use Hibla\Socket\LimitingServer;

$tcp    = new TcpServer('0.0.0.0:8080');
$server = new LimitingServer($tcp, connectionLimit: 100);

$server->on('connection', function ($connection) {
    // At most 100 connections active simultaneously
});
```

**Two modes when the limit is reached:**

```
// Mode 1: Reject (default) — accept and immediately close excess connections
$server = new LimitingServer($tcp, connectionLimit: 100, pauseOnLimit: false);

// Mode 2: Pause — stop accepting at the OS level until a slot opens
$server = new LimitingServer($tcp, connectionLimit: 100, pauseOnLimit: true);
```

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
-------

[](#clients)

### Connector

[](#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.

```
use Hibla\Socket\Connector;
use function Hibla\await;

$connector = new Connector();

// TCP (with automatic DNS resolution)
$conn = await($connector->connect('tcp://example.com:80'));

// TLS
$conn = await($connector->connect('tls://example.com:443'));

// Unix domain socket
$conn = await($connector->connect('unix:///var/run/app.sock'));
```

**Configuration options:**

```
$connector = new Connector([
    // Connection timeout in seconds (default: default_socket_timeout ini)
    'timeout' => 5.0,

    // TCP context options (passed to stream_socket_client)
    'tcp' => [
        'bindto' => '192.168.1.100:0',
    ],

    // TLS context options (see https://www.php.net/manual/en/context.ssl.php)
    'tls' => [
        'verify_peer'      => true,
        'verify_peer_name' => true,
        'cafile'           => '/etc/ssl/certs/ca-certificates.crt',
    ],

    // DNS: true (system resolver), false (skip, IP only), array of nameservers,
    // or a ResolverInterface instance
    'dns' => ['1.1.1.1', '8.8.8.8'],

    // Happy Eyeballs RFC 8305 (true by default)
    'happy_eyeballs' => true,

    // Pre-check if IPv6 is actually routable before attempting AAAA queries
    'ipv6_precheck' => false,

    // Disable any transport entirely
    'unix' => false,
    'tls'  => false,
]);
```

---

### TcpConnector

[](#tcpconnector)

Establishes a raw non-blocking TCP connection to an IP address. Does not perform DNS resolution. Pass a resolved IP.

```
use Hibla\Socket\TcpConnector;
use function Hibla\await;

$connector = new TcpConnector();
$conn      = await($connector->connect('tcp://93.184.216.34:80'));
```

---

### SecureConnector

[](#secureconnector)

Wraps a `TcpConnector` (or any `ConnectorInterface`) and performs a TLS handshake after the transport is established.

```
use Hibla\Socket\TcpConnector;
use Hibla\Socket\SecureConnector;
use function Hibla\await;

$connector = new SecureConnector(new TcpConnector(), [
    'verify_peer'      => true,
    'verify_peer_name' => true,
    'cafile'           => '/etc/ssl/certs/ca-certificates.crt',
]);

$conn = await($connector->connect('tls://example.com:443'));
```

---

### UnixConnector

[](#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.

```
use Hibla\Socket\UnixConnector;
use function Hibla\await;

$connector = new UnixConnector();
$conn      = await($connector->connect('unix:///var/run/app.sock'));
```

---

### TimeoutConnector

[](#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.

```
use Hibla\Socket\TcpConnector;
use Hibla\Socket\TimeoutConnector;
use Hibla\Socket\Exceptions\TimeoutException;
use function Hibla\{async, await};

// Promise chain style
$connector = new TimeoutConnector(new TcpConnector(), timeout: 3.0);

$connector->connect('tcp://example.com:80')
    ->then(function ($conn) {
        $conn->write("GET / HTTP/1.0\r\n\r\n");
        $conn->on('data', fn(string $data) => echo $data);
        $conn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    })
    ->catch(function (TimeoutException $e) {
        echo "Timed out: " . $e->getMessage() . "\n";
    });

// Await style
await(async(function () {
    $connector = new TimeoutConnector(new TcpConnector(), timeout: 3.0);

    try {
        $conn = await($connector->connect('tcp://example.com:80'));
        $conn->write("GET / HTTP/1.0\r\n\r\n");
        $conn->on('data', fn(string $data) => echo $data);
        $conn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    } catch (TimeoutException $e) {
        echo "Timed out: " . $e->getMessage() . "\n";
    }
}));
```

---

### FixedUriConnector

[](#fixeduriconnector)

Always connects to a pre-configured URI regardless of the URI passed to `connect()`. Useful for proxies, tunnels, and test mocks.

```
use Hibla\Socket\TcpConnector;
use Hibla\Socket\FixedUriConnector;
use function Hibla\await;

$connector = new FixedUriConnector(
    'tcp://proxy.internal:1080',
    new TcpConnector()
);

// All connect() calls go to the proxy regardless of the target URI
$conn = await($connector->connect('tcp://example.com:80'));
```

---

### DNS Resolution

[](#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`:

```
use Hibla\Dns\Dns;
use Hibla\Socket\Connector;

$resolver  = Dns::builder()
    ->withNameservers(['1.1.1.1', '8.8.8.8'])
    ->withCache()
    ->build();

$connector = new Connector(['dns' => $resolver]);
```

To bypass DNS entirely (IP-only environments or when you resolve yourself):

```
$connector = new Connector(['dns' => false]);
```

---

### Happy Eyeballs

[](#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.

```
// Disable if your environment is IPv4-only and you want to skip the delay
$connector = new Connector(['happy_eyeballs' => false]);

// Enable IPv6 pre-check to skip AAAA entirely when IPv6 isn't routable
$connector = new Connector(['ipv6_precheck' => true]);
```

---

Working with Connections
------------------------

[](#working-with-connections)

### Reading and writing

[](#reading-and-writing)

```
// Write data — returns false if the internal buffer exceeds the soft limit
$connection->write("Hello, world!\n");

// Listen for incoming data
$connection->on('data', function (string $chunk) {
    echo "Received: " . $chunk;
});

// Half-close — flush remaining writes then close the write side
$connection->end();

// Full close — immediately closes both sides, discarding any buffered data
$connection->close();
```

---

### Backpressure

[](#backpressure)

`write()` returns `false` when the write buffer is full. Stop writing and wait for the `drain` event before continuing.

**Promise chain style:**

```
$connector->connect('tcp://example.com:9000')
    ->then(function ($connection) use ($source) {
        $source->on('data', function (string $chunk) use ($connection, $source) {
            if ($connection->write($chunk) === false) {
                $source->pause();
            }
        });

        $connection->on('drain', function () use ($source) {
            $source->resume();
        });

        $connection->on('close', function () use ($source) {
            $source->close();
        });

        $connection->on('error', function (\Throwable $e) {
            echo "Error: " . $e->getMessage() . "\n";
        });
    });
```

**Await style:**

```
use function Hibla\{async, await};

await(async(function () use ($connector, $source) {
    $connection = await($connector->connect('tcp://example.com:9000'));

    $source->on('data', function (string $chunk) use ($connection, $source) {
        if ($connection->write($chunk) === false) {
            $source->pause();
        }
    });

    $connection->on('drain', fn() => $source->resume());
    $connection->on('close', fn() => $source->close());
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
}));
```

If you are piping a Hibla stream into a connection, backpressure is handled automatically with no manual `pause()`/`resume()` needed:

```
use Hibla\Stream\ReadableResourceStream;

$file = new ReadableResourceStream(fopen('/tmp/large.bin', 'rb'));
$file->pipe($connection);
```

---

### Cancellation

[](#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

[](#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:

```
use Hibla\Socket\Connector;
use Hibla\EventLoop\Loop;

$connector = new Connector();
$promise   = $connector->connect('tcp://example.com:80');

$promise->onCancel(function () {
    echo "Connection attempt was cancelled\n";
});

$promise->then(function ($connection) use (&$timerId) {
    Loop::cancelTimer($timerId);
    $connection->write("GET / HTTP/1.0\r\n\r\n");
    $connection->on('data', fn(string $data) => echo $data);
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
});

// Cancel after 2 seconds if not yet connected.
// onCancel() fires; then() and catch() do not.
$timerId = Loop::addTimer(2.0, fn() => $promise->cancel());
```

### Timeouts are rejections, not cancellations

[](#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:

```
use Hibla\Socket\TcpConnector;
use Hibla\Socket\TimeoutConnector;
use Hibla\Socket\Exceptions\TimeoutException;
use function Hibla\{async, await};

// Promise chain — catch() fires because TimeoutConnector rejects
$connector = new TimeoutConnector(new TcpConnector(), timeout: 3.0);

$connector->connect('tcp://example.com:80')
    ->then(function ($connection) {
        $connection->write("GET / HTTP/1.0\r\n\r\n");
        $connection->on('data', fn(string $data) => echo $data);
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    })
    ->catch(function (TimeoutException $e) {
        echo "Timed out: " . $e->getMessage() . "\n";
    });

// Await style
await(async(function () {
    $connector = new TimeoutConnector(new TcpConnector(), timeout: 3.0);

    try {
        $connection = await($connector->connect('tcp://example.com:80'));
        $connection->write("GET / HTTP/1.0\r\n\r\n");
        $connection->on('data', fn(string $data) => echo $data);
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    } catch (TimeoutException $e) {
        echo "Timed out: " . $e->getMessage() . "\n";
    }
}));
```

### Structured cancellation with CancellationToken

[](#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:

```
use Hibla\Cancellation\CancellationTokenSource;
use Hibla\Promise\Exceptions\CancelledException;
use Hibla\Socket\Connector;
use function Hibla\{async, await};

$cts       = new CancellationTokenSource();
$connector = new Connector();

await(async(function () use ($cts, $connector) {
    try {
        $connection = await($connector->connect('tcp://example.com:80'), $cts->token);

        $connection->write("GET / HTTP/1.0\r\n\r\n");
        $connection->on('data', fn(string $data) => echo $data);
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    } catch (CancelledException $e) {
        echo "Connection cancelled\n";
    }
}));

// Cancel from anywhere — the connect promise is cancelled synchronously
$cts->cancel();
```

**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:

```
use Hibla\Cancellation\CancellationTokenSource;
use Hibla\Socket\Connector;

$cts       = new CancellationTokenSource();
$connector = new Connector();

$promise = $connector->connect('tcp://example.com:80');

// Manually track — token will cancel this promise when $cts->cancel() is called
$cts->token->track($promise);

$promise->onCancel(function () {
    echo "Connection cancelled\n";
});

$promise->then(function ($connection) {
    $connection->write("GET / HTTP/1.0\r\n\r\n");
    $connection->on('data', fn(string $data) => echo $data);
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
});

// then() and catch() do not fire — only onCancel() does
$cts->cancel();
```

**Multiple concurrent connections**

One token cancels all tracked connections at once regardless of which phase each is in (DNS, TCP handshake, or TLS):

```
use Hibla\Cancellation\CancellationTokenSource;
use Hibla\Promise\Exceptions\CancelledException;
use Hibla\Promise\Promise;
use Hibla\Socket\Connector;
use function Hibla\{async, await};

$cts       = new CancellationTokenSource();
$connector = new Connector();

$hosts = [
    'tcp://server-a.internal:9000',
    'tcp://server-b.internal:9000',
    'tcp://server-c.internal:9000',
];

await(async(function () use ($cts, $connector, $hosts) {
    try {
        $connections = await(Promise::all(array_map(
            fn(string $uri) => $connector->connect($uri)
                ->then(function ($connection) use ($uri) {
                    $connection->write("PING\n");
                    $connection->on('error', fn(\Throwable $e) => echo "$uri error: " . $e->getMessage() . "\n");
                    return $connection;
                }),
            $hosts
        )), $cts->token);

        echo "All " . count($connections) . " connections established\n";
    } catch (CancelledException $e) {
        echo "All connections cancelled\n";
    }
}));

// Cancels all three connect promises at once — synchronously
$cts->cancel();
```

**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:

```
use Hibla\Cancellation\CancellationTokenSource;
use Hibla\Promise\Exceptions\CancelledException;
use Hibla\Socket\Connector;
use function Hibla\{async, await};

$userCts    = new CancellationTokenSource();      // user clicks abort
$timeoutCts = new CancellationTokenSource(10.0);  // 10 second hard ceiling

// Cancels if user aborts OR 10 seconds elapse, whichever comes first
$linkedCts = CancellationTokenSource::createLinkedTokenSource(
    $userCts->token,
    $timeoutCts->token
);

$connector = new Connector();

await(async(function () use ($linkedCts, $connector) {
    try {
        $connection = await(
            $connector->connect('tcp://example.com:80'),
            $linkedCts->token
        );

        $connection->write("GET / HTTP/1.0\r\n\r\n");
        $connection->on('data', fn(string $data) => echo $data);
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    } catch (CancelledException $e) {
        echo "Cancelled — either user aborted or 10s timeout hit\n";
    }
}));

// Wire to your UI abort button
$abortButton->onClick(fn() => $userCts->cancel());
```

**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:

```
use Hibla\Cancellation\CancellationToken;
use Hibla\Socket\Connector;
use function Hibla\{async, await};

function connectToService(
    string $uri,
    CancellationToken $token = null
): \Hibla\Promise\Interfaces\PromiseInterface {
    $token ??= CancellationToken::none();

    $connector = new Connector();
    $promise   = $connector->connect($uri);

    $token->track($promise); // safe no-op when token is none()

    return $promise;
}

// Works without a token
await(async(function () {
    $conn = await(connectToService('tcp://example.com:80'));
    $conn->write("PING\n");
    $conn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
}));

// Works with a token too
await(async(function () use ($cts) {
    try {
        $conn = await(connectToService('tcp://example.com:80', $cts->token));
        $conn->write("PING\n");
        $conn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    } catch (CancelledException $e) {
        echo "Cancelled\n";
    }
}));
```

### Cancellation support by connector

[](#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.

ConnectorCancellableWhat 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:

```
use Hibla\Socket\UnixConnector;
use Hibla\Socket\TimeoutConnector;
use Hibla\Socket\Exceptions\TimeoutException;
use function Hibla\{async, await};

// Promise chain
$connector = new TimeoutConnector(new UnixConnector(), timeout: 2.0);

$connector->connect('unix:///var/run/app.sock')
    ->then(function ($connection) {
        $connection->write("PING\n");
        $connection->on('data', fn(string $data) => echo $data);
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    })
    ->catch(function (TimeoutException $e) {
        echo "Unix socket timed out: " . $e->getMessage() . "\n";
    });

// Await style
await(async(function () {
    $connector = new TimeoutConnector(new UnixConnector(), timeout: 2.0);

    try {
        $connection = await($connector->connect('unix:///var/run/app.sock'));
        $connection->write("PING\n");
        $connection->on('data', fn(string $data) => echo $data);
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    } catch (TimeoutException $e) {
        echo "Unix socket timed out: " . $e->getMessage() . "\n";
    }
}));
```

---

### Mid-flight TLS upgrade

[](#mid-flight-tls-upgrade)

Useful for protocols that start in plaintext and upgrade to TLS (MySQL, SMTP STARTTLS, PostgreSQL).

> **Important:** Use `once()` instead of `on()` for the plain negotiation phase. `enableEncryption()` upgrades the underlying stream in place but does not remove listeners you registered before the upgrade. If you use `on()` 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.

```
// WRONG — plain listener stays attached after upgrade
$connection->on('data', function (string $data) use ($connection) {
    if (str_contains($data, 'STARTTLS')) {
        $connection->enableEncryption([...])->then(function ($secureConn) {
            $secureConn->on('data', fn(string $data) => echo "Secure: " . $data);
            // Secure data now fires through BOTH the plain on('data') above
            // AND this secure on('data') handler
        });
    }
});

// CORRECT — plain listener removed automatically after STARTTLS fires
$connection->once('data', function (string $data) use ($connection) {
    if (str_contains($data, 'STARTTLS')) {
        $connection->enableEncryption([...])->then(function ($secureConn) {
            $secureConn->on('data', fn(string $data) => echo "Secure: " . $data);
            // Only this handler fires for secure data
        });
    }
});
```

**Server side — promise chain style:**

```
$server->on('connection', function ($connection) use ($certFile, $keyFile) {
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");

    $connection->once('data', function (string $data) use ($connection, $certFile, $keyFile) {
        if (str_contains($data, 'STARTTLS')) {
            $connection->write("+OK Begin TLS\r\n");

            $connection->enableEncryption([
                'local_cert'  => $certFile,
                'local_pk'    => $keyFile,
                'verify_peer' => false,
            ], isServer: true)
            ->then(function ($secureConn) {
                $secureConn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
                $secureConn->on('data', fn(string $data) => echo "Secure: " . $data);
            })
            ->catch(function (\Throwable $e) use ($connection) {
                echo "TLS upgrade failed: " . $e->getMessage() . "\n";
                // Always close the connection on failure — leaving it open
                // causes the client to hang waiting for a handshake that
                // will never complete
                $connection->close();
            });
        }
    });
});
```

**Server side — await style:**

```
use Hibla\Socket\Exceptions\EncryptionFailedException;
use function Hibla\async;

$server->on('connection', function ($connection) use ($certFile, $keyFile) {
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");

    $connection->once('data', function (string $data) use ($connection, $certFile, $keyFile) {
        if (str_contains($data, 'STARTTLS')) {
            $connection->write("+OK Begin TLS\r\n");

            async(function () use ($connection, $certFile, $keyFile) {
                try {
                    $secureConn = await($connection->enableEncryption([
                        'local_cert'  => $certFile,
                        'local_pk'    => $keyFile,
                        'verify_peer' => false,
                    ], isServer: true));
                } catch (EncryptionFailedException $e) {
                    echo "TLS upgrade failed: " . $e->getMessage() . "\n";
                    // Always close the connection on failure — leaving it open
                    // causes the client to hang waiting for a handshake that
                    // will never complete
                    $connection->close();
                    return;
                }

                $secureConn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
                $secureConn->on('data', fn(string $data) => echo "Secure: " . $data);
            });
        }
    });
});
```

**Client side — promise chain style:**

```
$connector->connect('tcp://mail.example.com:25')
    ->then(function ($connection) {
        $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");

        $connection->once('data', function (string $data) use ($connection) {
            if (str_contains($data, 'STARTTLS')) {
                $connection->write("STARTTLS\r\n");

                $connection->enableEncryption(['verify_peer' => true])
                    ->then(function ($secureConn) {
                        $secureConn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
                        $secureConn->write("EHLO client.example.com\r\n");
                    })
                    ->catch(function (\Throwable $e) use ($connection) {
                        echo "TLS upgrade failed: " . $e->getMessage() . "\n";
                        $connection->close();
                    });
            }
        });
    });
```

**Client side — await style:**

```
use Hibla\Socket\Exceptions\EncryptionFailedException;
use function Hibla\{async, await};

await(async(function () use ($connector) {
    $connection = await($connector->connect('tcp://mail.example.com:25'));
    $connection->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");

    // ... wait for 220 greeting, send EHLO, receive STARTTLS capability ...

    try {
        $secureConn = await($connection->enableEncryption(['verify_peer' => true]));
    } catch (EncryptionFailedException $e) {
        echo "TLS upgrade failed: " . $e->getMessage() . "\n";
        // Always close the connection on failure — leaving it open
        // causes the remote end to hang waiting for a handshake that
        // will never complete
        $connection->close();
        return;
    }

    $secureConn->on('error', fn(\Throwable $e) => echo "Error: " . $e->getMessage() . "\n");
    $secureConn->write("EHLO client.example.com\r\n");
}));
```

---

### Address inspection

[](#address-inspection)

```
$connection->getRemoteAddress(); // tcp://93.184.216.34:43210
$connection->getLocalAddress();  // tcp://192.168.1.100:54321

// TLS connections report the scheme correctly
$connection->getRemoteAddress(); // tls://93.184.216.34:443

// Unix connections
$connection->getRemoteAddress(); // unix:///var/run/app.sock
```

---

Interface summary
-----------------

[](#interface-summary)

InterfaceImplemented 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:

```
use Hibla\Socket\Interfaces\ConnectorInterface;
use Hibla\Socket\Interfaces\ServerInterface;
use Hibla\Socket\Interfaces\ConnectionInterface;
```

---

Exception reference
-------------------

[](#exception-reference)

All exceptions extend `Hibla\Socket\Exceptions\SocketException` which extends `\RuntimeException`.

ExceptionWhen 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```
use Hibla\Socket\Exceptions\ConnectionFailedException;
use Hibla\Socket\Exceptions\TimeoutException;
use Hibla\Socket\Exceptions\EncryptionFailedException;
use function Hibla\{async, await};

// Promise chain style
$connector->connect('tls://example.com:443')
    ->then(fn($connection) => $connection->write("GET / HTTP/1.0\r\n\r\n"))
    ->catch(fn(TimeoutException $e)          => print("Timed out: "       . $e->getMessage() . "\n"))
    ->catch(fn(EncryptionFailedException $e) => print("TLS failed: "      . $e->getMessage() . "\n"))
    ->catch(fn(ConnectionFailedException $e) => print("Connect failed: "  . $e->getMessage() . "\n"));

// Await style
await(async(function () use ($connector) {
    try {
        $connection = await($connector->connect('tls://example.com:443'));
        $connection->write("GET / HTTP/1.0\r\n\r\n");
    } catch (TimeoutException $e) {
        echo "Timed out: " . $e->getMessage() . "\n";
    } catch (EncryptionFailedException $e) {
        echo "TLS failed: " . $e->getMessage() . "\n";
    } catch (ConnectionFailedException $e) {
        echo "Connect failed: " . $e->getMessage() . "\n";
    }
}));
```

---

Development
-----------

[](#development)

```
git clone https://github.com/hiblaphp/socket.git
cd socket
composer install
./vendor/bin/pest
./vendor/bin/phpstan analyse
```

---

Credits
-------

[](#credits)

- **API Design:** Inspired by [ReactPHP Socket](https://github.com/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 via `hiblaphp/cancellation`.
- **Event Emitter:** Built on [evenement/evenement](https://github.com/igorw/evenement).
- **Stream Layer:** Built on [hiblaphp/stream](https://github.com/hiblaphp/stream).
- **Event Loop Integration:** Powered by [hiblaphp/event-loop](https://github.com/hiblaphp/event-loop).
- **Promise Integration:** Built on [hiblaphp/promise](https://github.com/hiblaphp/promise).
- **DNS Resolution:** Powered by [hiblaphp/dns](https://github.com/hiblaphp/dns).

---

License
-------

[](#license)

MIT License. See [LICENSE](./LICENSE) for more information.

###  Health Score

25

—

LowBetter than 37% of packages

Maintenance60

Regular maintenance activity

Popularity18

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/adefeba01f7aab9f1eed1cc3f5015be296d86126a4adb50257d81ec0bf23c8ca?d=identicon)[RCalicdan](/maintainers/RCalicdan)

---

Top Contributors

[![rcalicdan](https://avatars.githubusercontent.com/u/163510169?v=4)](https://github.com/rcalicdan "rcalicdan (43 commits)")

### Embed Badge

![Health badge](/badges/hiblaphp-socket/health.svg)

```
[![Health](https://phpackages.com/badges/hiblaphp-socket/health.svg)](https://phpackages.com/packages/hiblaphp-socket)
```

###  Alternatives

[friendsofsymfony/rest-bundle

This Bundle provides various tools to rapidly develop RESTful API's with Symfony

2.8k73.3M319](/packages/friendsofsymfony-rest-bundle)[php-http/discovery

Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations

1.3k309.5M1.2k](/packages/php-http-discovery)[nyholm/psr7

A fast PHP7 implementation of PSR-7

1.3k235.4M2.4k](/packages/nyholm-psr7)[pusher/pusher-php-server

Library for interacting with the Pusher REST API

1.5k94.8M293](/packages/pusher-pusher-php-server)[spatie/crawler

Crawl all internal links found on a website

2.8k16.3M52](/packages/spatie-crawler)[react/http

Event-driven, streaming HTTP client and server implementation for ReactPHP

78126.4M414](/packages/react-http)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
