PHP code example of hiblaphp / socket

1. Go to this page and download the library: Download hiblaphp/socket library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

hiblaphp / socket example snippets


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

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";
});

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";
    });

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";
    });
}));

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";
    }
});

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";
    }
}));

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";
    });

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");
}));

$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";
    });

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");
}));

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');

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

$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";
});

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

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

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");
});

use Hibla\Socket\UnixServer;

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

use Hibla\Socket\FdServer;

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

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

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
});

// 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);

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'));

$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,
]);

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

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

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'));

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

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

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";
    }
}));

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'));

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]);

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

// 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]);

// 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();

$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";
        });
    });

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");
}));

use Hibla\Stream\ReadableResourceStream;

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

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());

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";
    }
}));

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();

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();

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();

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());

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";
    }
}));

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";
    }
}));

// 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->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();
            });
        }
    });
});

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);
            });
        }
    });
});

$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();
                    });
            }
        });
    });

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");
}));

$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

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

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";
    }
}));