純 PHP 實作 SOCKS5 代理(非 Swoole),展示 pthreads 多執行緒、stream_socket 非阻塞 IO、執行緒池模式。注意:此為教學玩具,**生產環境請用 Go/Rust 實作**(如 asocks-go)。
<?php
// 需安裝 pthreads 擴展(PHP 7.x 非 ZTS 不支援)
// 此代碼需在 CLI 模式執行
class Pipe extends Threaded {
private $client, $remote;
public function __construct($client, $remote) {
$this->client = $client; $this->remote = $remote;
}
public function run() {
for (;;) {
$data = stream_socket_recvfrom($this->client, 4096);
if ($data === false || strlen($data) === 0) break;
$sent = stream_socket_sendto($this->remote, $data);
if ($sent <= 0) break;
}
stream_socket_shutdown($this->client, STREAM_SHUT_RD);
stream_socket_shutdown($this->remote, STREAM_SHUT_WR);
}
}
class Client extends Threaded {
public $fd;
public function __construct($fd) { $this->fd = $fd; }
public function run() {
// 1. SOCKS5 握手
$data = stream_socket_recvfrom($this->fd, 2);
$data = unpack('C*', $data);
if ($data[1] !== 0x05) { $this->fail('版本不符'); return; }
$nmethods = $data[2];
stream_socket_recvfrom($this->fd, $nmethods); // 讀取方法列表
stream_socket_sendto($this->fd, "\x05\x00"); // 無認證
// 2. 連線請求
$data = stream_socket_recvfrom($this->fd, 4);
$data = unpack('C*', $data);
if ($data[4] !== 0x03) { $this->fail('僅支援域名模式'); return; }
$domainLen = unpack('C', stream_socket_recvfrom($this->fd, 1))[1];
$payload = stream_socket_recvfrom($this->fd, $domainLen + 2);
$domain = substr($payload, 0, $domainLen);
$port = unpack('n', substr($payload, -2))[1];
// 3. 回應連線成功
stream_socket_sendto($this->fd, "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00");
echo "連線目標: {$domain}:{$port}\n";
// 4. 建立目標連線並雙向轉發
$remote = stream_socket_client("tcp://{$domain}:{$port}");
if (!$remote) { $this->fail('目標連線失敗'); return; }
$pipePool = $this->worker->pipePool;
$pipePool->submit(new Pipe($remote, $this->fd)); // Target -> Client
$pipePool->submit(new Pipe($this->fd, $remote)); // Client -> Target
}
private function fail($msg) {
echo $msg . "\n";
stream_socket_shutdown($this->fd, STREAM_SHUT_RDWR);
}
}
class ProxyWorker extends Worker {
public $pipePool;
public function __construct($pipePool) { $this->pipePool = $pipePool; }
}
// 主程式
$server = stream_socket_server('tcp://0.0.0.0:1080', $errno, $errstr);
if (!$server) exit($errstr);
$pipePool = new Pool(200, Worker::class); // 轉發執行緒池
$workerPool = new Pool(50, 'ProxyWorker', [$pipePool]); // 握手 Worker 池
echo "SOCKS5 代理啟動於 1080 端口...\n";
for (;;) {
$fd = @stream_socket_accept($server, 60);
if ($fd === false) continue;
$workerPool->submit(new Client($fd));
}
Threaded 才能在執行緒間傳遞Pool::submit() 必須傳遞 Threaded 物件stream_socket_recvfrom/sendto 替代傳統 fread/fwritestream_set_timeout() 對 recvfrom **無效**(PHP Bug)0x03 域名模式(遠端 DNS 解析)0x000.0.0.0:0curl --socks5-hostname 127.0.0.1:1080 https://ip.cn
PHP 不擅長網路服務端編程,此代碼僅為教學展示。實際專案請選擇適合的技術棧。