ホームページ >バックエンド開発 >PHPチュートリアル >Socks5 プロキシ サーバーを実装するための 100 行の PHP コードへの毎日の挑戦
2 日前、ステーション B で、スムーズな画質で LOL をプレイするためにコンピューターを 100 元で組み立てようとしている人を見て、100 行のコードで (単純に) 何か楽しいことが実現できるのではないかと突然思いました。私は主にPHP開発を行っているので、この記事を書いています。
もちろん、PHP (swoole 拡張機能は除く) 自体はネットワーク サーバー プログラミングが得意ではないため、このプロキシは単なるおもちゃであり、日常的な使用からは少し離れています。安定した信頼性の高い暗号化 (インターネットサーフィンを学習できるように) プロキシを使用したい場合は、これを使用できます: https://github.com/momaer/asocks-go これは、100 行の go を使用して実装することもできます。コード。
書いている過程で、PHP のマルチスレッドはまだ難しいことがわかりました。たとえば、接続ごとに新しいスレッドを作成したいと思うようになりました。ただし、このスレッドは保存する必要があります (たとえば、配列に保存する)。たとえば、公式の例では https://github.com/krakjoe/pthreads/blob/master/examples/SocketServer.php を次のように配置する必要があります。それ以外の場合は、(curl -L 301 を必要とするアドレス) を試してみると、何が起こるかわかります。
この例は、現実の世界では、実行されていないクライアントが確実に破棄されるように何かを行うことを示していますが、実行されなくなった接続を破棄する方法については説明しません。親切。 $clients をクラスに入れ、そのクラスをスレッド クラスに渡し、スレッド クラスが終了しようとしたときに $clients 内の対応する接続の設定を解除しようとしましたが、無駄でした。
次に、以下はスレッド プールを使用して実装されたプロキシです。論理的に言えば、終了時にプールを shutdown() する必要があり、監視ソケットもシャットダウンする必要があります。しかし、100 行のコードでは、その必要はありません。 Ctrl + C を押すと、オペレーティング システムにリソースを再利用させます。
PHP はなぜネットワーク プログラミングが苦手なのでしょうか?まずはストリームsocketXXX関連の関数を使ってみました ソケット拡張を使ってみませんか?ソケット拡張機能に問題があるため、https://github.com/krakjoe/pthreads/issues/581 を参照してください。また、ストリーム settimeout は、ストリーム Socketrecvfrom などの高度な操作では機能しません。http://php.net を参照してください。 /manual/en /function.stream-set-timeout.php プロキシを作成するときは、これらを考慮する必要があります。たとえば、リモートのターゲット サーバーに接続する場合、タイムアウト制御がないため、スレッド プールが簡単にいっぱいになる可能性があります。
テストするには、curl を使用してください。 ちなみに、現在はリモート DNS 解決のみがサポートされています。このおもちゃは後でインターネットにアクセスするために使用されるため、次のようになります。 function _construct($client, $remote) { $this->client = $client; $this->remote = $remote; } public function run() { for ( ; ; ) { $data = streamsocket recvfrom( $this->client, 4096); if ($data === false || strlen($data) === 0) { ブレーク; } $sendBytes = streamsocket sendto($this->remote, $ data) ; if ($sendBytes738f952886676aea49f1c076793443c1fd = $fd; }
public function run(){ $data = stream_socket_recvfrom($this->fd, 2); $data = unpack('c*', $data); if ($data[1] !== 0x05) { stream_socket_shutdown($this->fd, STREAM_SHUT_RDWR); echo '协议不正确.', PHP_EOL; return; } $nmethods = $data[2]; $data = stream_socket_recvfrom($this->fd, $nmethods); stream_socket_sendto($this->fd, "\x05\x00"); $data = stream_socket_recvfrom($this->fd, 4); $data = unpack('c*', $data); $addressType = $data[4]; if ($addressType === 0x03) { // domain $domainLength = unpack('c', stream_socket_recvfrom($this->fd, 1))[1]; $data = stream_socket_recvfrom($this->fd, $domainLength + 2); $domain = substr($data, 0, $domainLength); $port = unpack("n", substr($data, -2))[1]; } else { stream_socket_shutdown($this->fd, STREAM_SHUT_RDWR); echo '请使用远程dns解析.', PHP_EOL; } stream_socket_sendto($this->fd, "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00"); echo "{$domain}:{$port}", PHP_EOL; $remote = stream_socket_client("tcp://{$domain}:{$port}"); if ($remote === false) { stream_socket_shutdown($this->fd, STREAM_SHUT_RDWR); return; } $pool = $this->worker->pipePool; $pipe1 = new Pipe($remote, $this->fd); $pipe2 = new Pipe($this->fd, $remote); $pool->submit($pipe1); $pool->submit($pipe2);}
}
class ProxyWorker extends Worker { public function __construct($pipePool) { $this->パイププール = $pipePool; } }
$server = ストリームソケットサーバー('tcp://0.0.0.0:1080', $errno, $errstr); if ($server === false)
$pipePool = 新しいプール(200, Worker::class); $pool = 新しいプール(50, 'ProxyWorker', [$pipePool]);
for( ; ; ) { $fd = @streamソケットaccept( $server , 60); if ($fd === false) $pool->submit(new Client($fd)) } ```