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

100 行 PHP 實現 SOCKS5 代理服務器(基於 Swoole 異步非阻塞)

     編輯:PHP 相關知識

設計思路

  • 使用 Swoole 異步非阻塞 模型,單進程處理高併發連接
  • 狀態機 解析 SOCKS5 協議握手、認證、轉發階段
  • 異步 DNS 解析(swoole_async_dns_lookup)支援域名地址
  • 雙向管道:Client ↔ Server ↔ Remote

完整代碼

<?php
class Client {
    public $connected = true;
    public $data = '';
    public $remote = null;
    public $status = 0; // 0=握手, 1=請求解析, 2=轉發中
}

class Server {
    public $clients = [];

    public function start() {
        $server = new swoole_server('0.0.0.0', 8388, SWOOLE_BASE, SWOOLE_SOCK_TCP);
        $server->set([
            'max_conn' => 1000,
            'daemonize' => 1,
            'reactor_num' => 1,
            'worker_num' => 1,
            'dispatch_mode' => 2,
            'buffer_output_size' => 128 * 1024 * 1024,
            'open_cpu_affinity' => 1,
            'open_tcp_nodelay' => 1,
            'log_file' => 'socks5_server.log',
        ]);

        $server->on('connect', [$this, 'onConnect']);
        $server->on('receive', [$this, 'onReceive']);
        $server->on('close', [$this, 'onClose']);
        $server->start();
    }

    public function onConnect($server, $fd, $fromID) {
        $this->clients[$fd] = new Client();
    }

    public function onReceive($server, $fd, $fromID, $data) {
        $this->clients[$fd]->data .= $data;
        $this->parse($server, $fd);
    }

    public function onClose($server, $fd, $fromID) {
        $client = $this->clients[$fd];
        $client->connected = false;
        if ($client->remote) {
            $client->remote->close();
        }
    }

    private function parse($server, $fd) {
        $client = $this->clients[$fd];

        switch ($client->status) {
            case 0: // 協議握手
                if (strlen($client->data) >= 2) {
                    $request = unpack('c*', substr($client->data, 0, 2));
                    if ($request[1] !== 0x05) {
                        echo "協議錯誤: {$request[1]}\n";
                        $server->close($fd); break;
                    }
                    $nmethods = $request[2];
                    if (strlen($client->data) >= 2 + $nmethods) {
                        $client->data = substr($client->data, 2 + $nmethods);
                        $server->send($fd, "\x05\x00"); // 無需認證
                        $client->status = 1;
                    }
                }
                break;

            case 1: // 請求解析
                if (strlen($client->data) < 5) break;
                $request = unpack('c*', $client->data);
                $aType = $request[4];

                if ($aType === 0x03) { // 域名
                    $domainLen = $request[5];
                    if (strlen($client->data) < 5 + $domainLen + 2) break;
                    $domain = substr($client->data, 5, $domainLen);
                    $port = unpack('n', substr($client->data, 5 + $domainLen, 2))[1];
                    $client->data = substr($client->data, 5 + $domainLen + 2);
                } elseif ($aType === 0x01) { // IPv4
                    $domain = long2ip(unpack('N', substr($client->data, 4, 4))[1]);
                    $port = unpack('n', substr($client->data, 8, 2))[1];
                    $client->data = substr($client->data, 10);
                } else {
                    echo "不支持的 ATYPE: $aType\n";
                    $server->close($fd); break;
                }

                // 連接遠程目標
                $remote = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
                $remote->on('connect', function($cli) use ($client, $server, $fd, $remote) {
                    $server->send($fd, "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00"); // 成功響應
                    $client->status = 2;
                    $client->remote = $remote;
                });
                $remote->on('error', function($cli) use ($server, $fd) {
                    echo "連接遠程失敗\n";
                    $server->close($fd);
                });
                $remote->on('receive', function($cli, $data) use ($server, $fd, $client) {
                    if (!$client->connected) return;
                    $server->send($fd, $data);
                });
                $remote->on('close', function($cli) use ($client) {
                    $client->remote = null;
                });

                if ($aType === 0x03) {
                    swoole_async_dns_lookup($domain, function($host, $ip) use ($remote, $port, $server, $fd) {
                        if (empty($ip)) { $server->close($fd); return; }
                        $remote->connect($ip, $port);
                    });
                } else {
                    $remote->connect($domain, $port);
                }
                break;

            case 2: // 數據轉發
                if (strlen($client->data) === 0) break;
                if (!$client->remote) break;
                $sent = $client->remote->send($client->data);
                if ($sent === false || $sent < strlen($client->data)) {
                    echo "發送不完整: len=" . strlen($client->data) . " sent=$sent\n";
                    $server->close($fd);
                }
                $client->data = '';
                break;
        }
    }
}

(new Server())->start();

已知限制與改進方向

  • 連接/讀寫超時: 缺乏心跳與空閒檢測,建議引入 swoole_timer_tick 定時掃描清理
  • 半關閉處理: Swoole 當前版本對 shutdown(SHUT_RD/WR) 支援有限,需升級版本或應用層協議心跳
  • 多核擴展: 啟用 SWOOLE_PROCESS 模式,worker_num = CPU 核心數
  • 認證支援: 當前僅實現無認證模式(\x05\x00),生產環境需添加用戶名密碼驗證(RFC 1929)
  • UDP 關聯: SOCKS5 UDP ASSOCIATE 未實現

測試驗證

# 瀏覽器/工具配置 SOCKS5 代理 127.0.0.1:8388
# 訪問 https://www.oschina.net 正常
# 訪問網易新聞等複雜站點可能因超時/協議細節失敗

代碼已開源,歡迎 PR 完善超時、認證、UDP、多進程等企業級特性。

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