《PHP應(yīng)用:100行PHP代碼實(shí)現(xiàn)socks5代理服務(wù)器》要點(diǎn):
本文介紹了PHP應(yīng)用:100行PHP代碼實(shí)現(xiàn)socks5代理服務(wù)器,希望對您有用。如果有疑問,可以聯(lián)系我們。
前兩天在B站上看到一個(gè)小伙紙100元組裝個(gè)電腦打LOL畫質(zhì)流暢,突發(fā)奇想100行代碼能(簡單)實(shí)現(xiàn)個(gè)啥好玩的.我主要是做php開發(fā)的,于是就有了本文.PHP教程
當(dāng)然,由于php(不算swoole擴(kuò)展)自己不擅長做網(wǎng)絡(luò)服務(wù)端編程,所以這個(gè)代理,只是個(gè)玩具,離日常使用有點(diǎn)距離.如果想使用穩(wěn)定可靠的加密(所以能禾斗學(xué)上網(wǎng))代理,可以用這個(gè):https://github.com/momaer/asocks-go也是100來行代碼使用go實(shí)現(xiàn).PHP教程
寫的過程中發(fā)現(xiàn)php多線程還是難的.比如我開始想每個(gè)連接新建一個(gè)線程.但這個(gè)線程得保留起來(比如保留到數(shù)組),比如官方例子中的這個(gè):https://github.com/krakjoe/pthreads/blob/master/examples/SocketServer.php?要放到$clients這個(gè)數(shù)組里,不然,你試試(curl -L一個(gè)要301的地址)就知道出現(xiàn)什么情況了.PHP教程
這個(gè)例子說了in the real world, do something here to ensure clients not running are destroyed 但是,如何把不再運(yùn)行的連接銷毀卻沒有講.恩.我試了把$clients放到一個(gè)類里,把類傳給線程類,然后在線程類要結(jié)束時(shí)把$clients里對應(yīng)的連接給unset掉,無果.PHP教程
那,以下便是使用線程池來實(shí)現(xiàn)的代理,按道理講,退出時(shí)池要shutdown(),監(jiān)聽socket也要shutdown的,但百行代碼,就不勉強(qiáng)了,隨著ctrl + c,就讓操作系統(tǒng)來回收資源吧.PHP教程
php不擅長網(wǎng)絡(luò)編程體現(xiàn)在哪里呢?首先我用的是stream_socket_XXX相關(guān)的函數(shù),為啥不用socket擴(kuò)展呢?因?yàn)閟ocket擴(kuò)展有問題,參見:https://github.com/krakjoe/pthreads/issues/581?而stream_set_timeout對stream_socket_recvfrom這些高級操作,不起作用,參見:http://php.net/manual/en/function.stream-set-timeout.php?而這些,在寫代理時(shí)都需要考慮的.好比連接遠(yuǎn)程目標(biāo)服務(wù)器時(shí),沒有超時(shí)控制,很容易就線程池跑滿了.PHP教程
測試的話,使用curl即可,對了,目前只支持長途dns解析,為啥呢?因?yàn)檫@個(gè)玩具后期可是要實(shí)現(xiàn)禾斗學(xué)上網(wǎng)的喲: curl --socks5-hostname 127.0.0.1:1080?http://ip.cnPHP教程
Class Pipe extends Threaded { private $client; private $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; } $sendBytes = stream_socket_sendto($this->remote, $data); if ($sendBytes <= 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() { $data = stream_socket_recvfrom($this->fd, 2); $data = unpack('c*', $data); if ($data[1] !== 0x05) { stream_socket_shutdown($this->fd, STREAM_SHUT_RDWR); echo '協(xié)議不正確.', 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 $pipePool; public function __construct($pipePool) { $this->pipePool = $pipePool; } } $server = stream_socket_server('tcp://0.0.0.0:1080', $errno, $errstr); if ($server === false) exit($errstr); $pipePool = new Pool(200, Worker::class); $pool = new Pool(50, 'ProxyWorker', [$pipePool]); for( ; ; ) { $fd = @stream_socket_accept($server, 60); if ($fd === false) continue; $pool->submit(new Client($fd)); }
維易PHP培訓(xùn)學(xué)院每天發(fā)布《PHP應(yīng)用:100行PHP代碼實(shí)現(xiàn)socks5代理服務(wù)器》等實(shí)戰(zhàn)技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養(yǎng)人才。
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.snjht.com/jiaocheng/6827.html