首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 100 行實作 SOCKS5 代理伺服器:pthreads 多執行緒與非阻塞 IO 實戰

     編輯:PHP 相關知識

前言

純 PHP 實作 SOCKS5 代理(非 Swoole),展示 pthreads 多執行緒、stream_socket 非阻塞 IO、執行緒池模式。注意:此為教學玩具,**生產環境請用 Go/Rust 實作**(如 asocks-go)。

核心架構

  • 主執行緒:監聽端口,接受連線,分派給 Worker 池
  • Worker 池:處理 SOCKS5 握手、目標連線建立
  • Pipe 執行緒:雙向資料轉發(Client ↔ Target)
  • Pipe 池:管理轉發執行緒生命週期

完整代碼(約 100 行)

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

關鍵技術點解析

1. pthreads 限制與坑

  • 僅支援 PHP ZTS (Zend Thread Safety) 編譯版本
  • 物件需繼承 Threaded 才能在執行緒間傳遞
  • Pool::submit() 必須傳遞 Threaded 物件
  • 執行緒結束時需手動清理資源,否則洩漏

2. stream_socket 非阻塞 IO

  • stream_socket_recvfrom/sendto 替代傳統 fread/fwrite
  • stream_set_timeout() 對 recvfrom **無效**(PHP Bug)
  • 無超時控制易導致執行緒池耗盡

3. SOCKS5 協議簡化實作

  • 僅支援 0x03 域名模式(遠端 DNS 解析)
  • 無認證 0x00
  • 回應固定綁定地址 0.0.0.0:0

測試方法

curl --socks5-hostname 127.0.0.1:1080 https://ip.cn

生產級替代方案

  • Go:asocks-go (100 行、高併發、穩定)
  • Swoole:PHP 協程網路框架,原生支援 TCP/UDP Server
  • Workerman:PHP 多進程 Socket 框架

原文作者說明

PHP 不擅長網路服務端編程,此代碼僅為教學展示。實際專案請選擇適合的技術棧。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved