100多行PHP代码实现socks5代理服务器[2]

5年以前  |  阅读数:1060 次  |  编程语言:PHP 

100多行PHP代码实现socks5代理服务器,这次是使用swoole纯异步来写,使用状态机来处理数据。目前用它访问开源中国木有压力,但访问网易新闻就压力山大。我发现我用别的语言写得代理,访问网易新闻都压力大。嘎嘎,学艺不精。

对swoole理解不深,不知道怎么处理socket shutdown只关闭读/写这样,还有就是连接超时,读写超时这种怎么处理。在网上看到作者说要用定时器,感觉好麻烦,所以,这次的代理,虽然个人用,一般不会有什么问题,但离产品级的代理,还有段路要走。

如果要利用多核,就使用process模式,设置worker个数为cpu数量即可。



    <?php
    class 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, "\x05\x00");
          $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, "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00");
         $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}\n";
           $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();
 相关文章:
PHP分页显示制作详细讲解
SSH 登录失败:Host key verification failed
获取IMSI
将二进制数据转为16进制以便显示
获取IMEI
文件下载
贪吃蛇
双位运算符
PHP自定义函数获取搜索引擎来源关键字的方法
Java生成UUID
发送邮件
年的日历图
提取后缀名
在Zeus Web Server中安装PHP语言支持
让你成为最历害的git提交人
Yii2汉字转拼音类的实例代码
再谈PHP中单双引号的区别详解
指定应用ID以获取对应的应用名称
Python 2与Python 3版本和编码的对比
php封装的page分页类完整实例