100多行PHP代码实现socks5代理服务器,这次是使用swoole纯异步来写,使用状态机来处理数据。目前用它访问开源中国木有压力,但访问网易新闻就压力山大。我发现我用别的语言写得代理,访问网易新闻都压力大。嘎嘎,学艺不精。
对swoole理解不深,不知道怎么处理socket shutdown只关闭读/写这样,还有就是连接超时,读写超时这种怎么处理。在网上看到作者说要用定时器,感觉好麻烦,所以,这次的代理,虽然个人用,一般不会有什么问题,但离产品级的代理,还有段路要走。
如果要利用多核,就使用process模式,设置worker个数为cpu数量即可。
<?phpclass Client{ public $connected = true; public $data = ""; public $remote = null; public $status = 0;}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; } 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], PHP_EOL;$server->close($fd);break; } $nmethods = $request[2]; if (strlen($client->data) >= 2 + $nmethods) {$client->data = substr($client->data, 2 + $nmethods);$server->send($fd, "x05x00");$client->status = 1; }} } case 1: {if (strlen($client->data) < 5) break;$request = unpack("c*", $client->data);$aType = $request[4];if ($aType === 0x03) { // domain $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);} else if ($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, PHP_EOL; $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, "x05x00x00x01x00x00x00x00x00x00"); $client->status = 2; $client->remote = $remote;});$remote->on("error", function(swoole_client $cli) use($server, $fd) { //$server->send($fd, ""); // todo 连接不上remote echo "connect to remote error.", PHP_EOL; $server->close($fd);});$remote->on("receive", function($cli, $data) use($server, $fd, $client) { if (!$client->connected) {echo "connection has been closed.", PHP_EOL;return; } $server->send($fd, $data);});$remote->on("close", function($cli) use($server, $fd, $client) { $client->remote = null;});if ($aType === 0x03) { swoole_async_dns_lookup($domain, function($host, $ip) use($remote, $port, $server, $fd) {//todo 当host为空时的处理。貌似不存在的域名都解析成了本机的外网ip,奇怪if (empty($ip) || empty($host)) { echo "host:{$host}, ip:{$ip}
"; $server->close($fd); return;}$remote->connect($ip, $port); });} else { $remote->connect($domain, $port);} } case 2: {if (strlen($client->data) === 0) { break;}if ($client->remote === null) { echo "remote connection has been closed.", PHP_EOL; break;}$sendByteCount = $client->remote->send($client->data);if ($sendByteCount === false || $sendByteCount < strlen($client->data)) { echo "data length:" , strlen($client->data), " send byte count:", $sendByteCount, PHP_EOL;echo $client->data, PHP_EOL; $server->close($fd); }$client->data = ""; }} }}(new Server())->start();