phasync / server
A high performance async TCP and UDP socket server for PHP.
Installs: 23
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/phasync/server
Requires
- php: ^8.2
- phasync/phasync: ^1.1@rc
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- pestphp/pest: ^4.0
This package is auto-updated.
Last update: 2026-01-08 08:36:31 UTC
README
Note: This package will be renamed to
phasync/netin a future release.
Efficient TCP and UDP networking for PHP using phasync coroutines.
All stream resources are wrapped with AsyncStream, providing transparent async I/O - no need to call phasync::readable() or phasync::writable() manually.
Installation
composer require phasync/server
TcpServer
Accept TCP connections using a simple iterator pattern:
use phasync\Net\TcpServer; phasync::run(function() { $server = new TcpServer('0.0.0.0:8080'); foreach ($server->accept() as $peer => $stream) { phasync::go(function() use ($stream, $peer) { $request = fread($stream, 65536); fwrite($stream, "HTTP/1.1 200 OK\r\n" . "Connection: close\r\n" . "Content-Length: 13\r\n" . "\r\n" . "Hello, $peer!" ); fclose($stream); }); } });
Multiple Listen Addresses
Listen on multiple ports or interfaces simultaneously:
$server = new TcpServer(['0.0.0.0:80', '0.0.0.0:443']); foreach ($server->accept() as $peer => $stream) { // Connections from both ports arrive here }
Unix Domain Sockets
$server = new TcpServer('unix:///var/run/myapp.sock'); foreach ($server->accept() as $peer => $stream) { // Handle connection }
API
// Constructor new TcpServer(string|array $addresses, array $context = []) // Accept connections (generator) $server->accept(): Generator<string, resource> // yields peer => stream // Management $server->close(): void $server->isClosed(): bool $server->getAddresses(): array
Dns
Async DNS resolution (used automatically by TcpClient):
use phasync\Net\Dns; use phasync\Net\DnsRecordType; phasync::run(function() { // Default: tries A (IPv4) first, then AAAA (IPv6) // Returns random IP when multiple records exist (load balancing) $ip = Dns::resolve('example.com'); // Get all IPs for a hostname $allIps = Dns::resolveAll('example.com', DnsRecordType::A); // Explicit IPv4 only $ipv4 = Dns::resolve('example.com', DnsRecordType::A); // Explicit IPv6 only $ipv6 = Dns::resolve('example.com', DnsRecordType::AAAA); });
Features:
- Non-blocking UDP queries to system nameserver
- IPv4 (A) and IPv6 (AAAA) support
- Random selection for load balancing when multiple records exist
resolveAll()to get all IPs- Reads
/etc/hostsfirst (both IPv4 and IPv6 entries) - Caches results using DNS TTL
- Falls back to 8.8.8.8 if no nameserver configured
API
Dns::resolve(string $hostname, DnsRecordType $type = DnsRecordType::ANY, float $timeout = 2.0): ?string Dns::resolveAll(string $hostname, DnsRecordType $type = DnsRecordType::ANY, float $timeout = 2.0): array Dns::clearCache(): void // DnsRecordType enum DnsRecordType::A // IPv4 DnsRecordType::AAAA // IPv6 DnsRecordType::ANY // Try A first, then AAAA (default)
TcpClient
Connect to TCP servers asynchronously (with async DNS):
use phasync\Net\TcpClient; phasync::run(function() { $conn = TcpClient::connect('example.com:80'); fwrite($conn, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"); $response = stream_get_contents($conn); fclose($conn); echo $response; });
Unix Domain Sockets
$conn = TcpClient::connectUnix('/var/run/app.sock');
API
// Connect to TCP server TcpClient::connect(string $address, float $timeout = 30, array $context = []): resource // Connect to Unix socket TcpClient::connectUnix(string $path, float $timeout = 30, array $context = []): resource
UdpServer
Receive UDP datagrams with correct reply routing:
use phasync\Net\UdpServer; phasync::run(function() { $server = new UdpServer('0.0.0.0:9000'); foreach ($server->receive() as $peer => [$data, $socket]) { // Reply using the same socket (correct interface routing) stream_socket_sendto($socket, "Echo: $data", 0, $peer); } });
Multiple Interfaces
When bound to multiple interfaces, the $socket returned ensures replies come from the correct source IP:
$server = new UdpServer(['192.168.1.10:9000', '10.0.0.10:9000']); foreach ($server->receive() as $peer => [$data, $socket]) { // $socket is the interface that received the packet // Reply will have the correct source IP stream_socket_sendto($socket, "Reply", 0, $peer); }
API
// Constructor new UdpServer(string|array $addresses, array $context = []) // Receive datagrams (generator) $server->receive(int $maxLength = 65536): Generator<string, array{string, resource}> // yields peer => [data, socket] // Send (for initiating sends, not replies) $server->send(string $address, string $data, int $flags = 0): int|false // Management $server->close(): void $server->isClosed(): bool $server->getAddresses(): array
Performance
These implementations leverage phasync coroutines and non-blocking I/O for high performance:
- Zero-copy buffers: Read/write buffers are disabled for direct kernel access
- TCP_NODELAY: Disabled Nagle algorithm for low-latency responses
- SO_REUSEPORT: Enabled by default for graceful restarts and multi-process scaling
- Large chunk size: 64KB chunks for efficient data transfer
In synthetic benchmarks, this can handle 50% more requests per second than Node.js.
Design Guidelines
Read/Write Strategy
- Read big: Always read large chunks (65536 bytes) and buffer yourself if needed
- Write for your use case: Large chunks for bandwidth, small chunks for low latency
Socket Options
| Option | Default | Reason |
|---|---|---|
so_reuseport |
true |
Graceful restarts, multi-process scaling |
tcp_nodelay |
true |
Low-latency delivery of small messages |
backlog |
511 |
Handle connection bursts |
Override via the $context parameter:
$server = new TcpServer('0.0.0.0:8080', [ 'socket' => [ 'tcp_nodelay' => false, // Enable Nagle algorithm 'backlog' => 1024, ] ]);
Deprecated: Server Class
The original Server class is deprecated. Migrate to TcpServer or UdpServer:
// Old (deprecated) Server::serve('tcp://127.0.0.1:8080', function($stream, $peer) { // handle connection }); // New $server = new TcpServer('127.0.0.1:8080'); foreach ($server->accept() as $peer => $stream) { phasync::go(fn() => /* handle connection */); }