100行PHP代码实现socks5代理服务器

 更新时间:2016年04月28日 10:17:58   作者:秣马儿  
这个例子说了in the real world, do something here to ensure clients not running are destroyed 但是,如何把不再运行的连接销毁却没有讲。恩。我试了把$clients放到一个类里,把类传给线程类,然后在线程类要结束时把$clients里对应的连接给unset掉,无果。
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

前两天在B站上看到一个小伙纸100元组装个电脑打LOL画质流畅,突发奇想100行代码能(简单)实现个啥好玩的。我主要是做php开发的,于是就有了本文。

当然,由于php(不算swoole扩展)本身不擅长做网络服务端编程,所以这个代理,只是个玩具,离日常使用有点距离。如果想使用稳定可靠的加密(所以能禾斗学上网)代理,可以用这个:https://github.com/momaer/asocks-go也是100来行代码使用go实现。

写的过程中发现php多线程还是难的。比如我开始想每个连接新建一个线程。但这个线程得保存起来(比如保存到数组),比如官方例子中的这个:https://github.com/krakjoe/pthreads/blob/master/examples/SocketServer.php 要放到$clients这个数组里,不然,你试试(curl -L一个要301的地址)就知道出现什么情况了。

这个例子说了in the real world, do something here to ensure clients not running are destroyed 但是,如何把不再运行的连接销毁却没有讲。恩。我试了把$clients放到一个类里,把类传给线程类,然后在线程类要结束时把$clients里对应的连接给unset掉,无果。

那,以下就是使用线程池来实现的代理,按道理讲,退出时池要shutdown(),监听socket也要shutdown的,但百行代码,就不勉强了,随着ctrl + c,就让操作系统来回收资源吧。

php不擅长网络编程体现在哪里呢?首先我用的是stream_socket_XXX相关的函数,为啥不用socket扩展呢?因为socket扩展有问题,参见:https://github.com/krakjoe/pthreads/issues/581 而stream_set_timeout对stream_socket_recvfrom这些高级操作,不起作用,参见:http://php.net/manual/en/function.stream-set-timeout.php 而这些,在写代理时都需要考虑的。比如连接远程目标服务器时,没有超时控制,很容易就线程池跑满了。

测试的话,使用curl即可,对了,目前只支持远程dns解析,为啥呢?因为这个玩具后期可是要实现禾斗学上网的哟: curl --socks5-hostname 127.0.0.1:1080 http://ip.cn

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 '协议不正确.', 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函数(简单整理)

    一些被忽视的PHP函数(简单整理)

    真的是不用不知道,其实我们熟悉的 PHP 还有很多好东西没有发掘。看到这篇文章,当时就泪奔了好几回,重点推荐下,顺便我自己也做个整理。
    2010-04-04
  • 一个PHP的远程图片抓取函数分享

    一个PHP的远程图片抓取函数分享

    远程图片抓取的方法有很多,在本文将为大家介绍下php中是如何实现的,感兴趣的朋友可以了解下
    2013-09-09
  • PHP base64编码后解码乱码的解决办法

    PHP base64编码后解码乱码的解决办法

    这篇文章主要介绍了PHP base64编码后解码乱码的解决办法,导致乱码的原因就是base64编码后包含一些特殊字符,替换一下就可以了,需要的朋友可以参考下
    2014-06-06
  • Linux系统下php获得系统分区信息的方法

    Linux系统下php获得系统分区信息的方法

    这篇文章主要介绍了Linux系统下php获得系统分区信息的方法,涉及Linux下php系统分析的操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • 基于php中echo用逗号和用点号的区别详解

    基于php中echo用逗号和用点号的区别详解

    下面小编就为大家分享一篇基于php中echo用逗号和用点号的区别详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-01-01
  • PHP5.2中PDO的简单使用方法

    PHP5.2中PDO的简单使用方法

    这篇文章主要介绍了PHP5.2中PDO的简单使用方法,较为详细的分析了PHP5.2中PDO的配置与数据库的连接,查询等基本操作技巧,需要的朋友可以参考下
    2016-03-03
  • 推荐php模板技术[转]

    推荐php模板技术[转]

    推荐php模板技术[转]...
    2007-01-01
  • 使用PHP强制下载PDF文件示例

    使用PHP强制下载PDF文件示例

    当需要下载一个PDF文件时,如果不经处理会直接在浏览器里打开PDF文件,然后再需要通过另存为才能保存下载文件,下面通过PHP来实现直接下载PDF文件
    2014-01-01
  • thinkphp自定义权限管理之名称判断方法

    thinkphp自定义权限管理之名称判断方法

    下面小编就为大家带来一篇thinkphp自定义权限管理之名称判断方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • php使用数组填充下拉列表框的方法

    php使用数组填充下拉列表框的方法

    这篇文章主要介绍了php使用数组填充下拉列表框的方法,涉及php操作数组的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03

最新评论

?


http://www.vxiaotou.com