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


Posted in PHP onApril 28, 2016

前两天在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执行速度全攻略(下)
Oct 09 PHP
PHP 在线翻译函数代码
May 07 PHP
解析在apache里面给php写虚拟目录的详细方法
Jun 24 PHP
PHP实现显示照片exif信息的方法
Jul 11 PHP
PHP基本语法总结
Sep 06 PHP
详解WordPress中给链接添加查询字符串的方法
Dec 18 PHP
PHP实现的字符串匹配算法示例【sunday算法】
Dec 19 PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
Feb 06 PHP
针对thinkPHP5框架存储过程bug重写的存储过程扩展类完整实例
Jun 16 PHP
PHP随机数函数rand()与mt_rand()的讲解
Mar 25 PHP
ThinkPHP5 框架引入 Go AOP,PHP AOP编程项目详解
May 12 PHP
PHP中->和=>的意思
Mar 31 PHP
Yii2实现ajax上传图片插件用法
Apr 28 #PHP
thinkphp3.2实现上传图片的控制器方法
Apr 28 #PHP
PHP简单实现文本计数器的方法
Apr 28 #PHP
Yii2 rbac权限控制之菜单menu实例教程
Apr 28 #PHP
Yii2搭建后台并实现rbac权限控制完整实例教程
Apr 28 #PHP
PHP在线调试执行的实现方法(附demo源码)
Apr 28 #PHP
thinkphp项目部署到Linux服务器上报错“模板不存在”如何解决
Apr 27 #PHP
You might like
Apache, PHP在Windows 9x/NT下的安装与配置 (二)
2006/10/09 PHP
PHP日期时间函数的高级应用技巧
2009/05/16 PHP
PHP5中新增stdClass 内部保留类
2011/06/13 PHP
jquery 屏蔽一个区域内的所有元素,禁止输入
2009/10/22 Javascript
ExtJS 设置级联菜单的默认值
2010/06/13 Javascript
从URL中提取参数与将对象转换为URL查询参数的实现代码
2012/01/12 Javascript
Javascript中匿名函数的多种调用方式总结
2013/12/06 Javascript
IE中JS跳转丢失referrer问题的2个解决方法
2014/07/18 Javascript
jquery删除指定子元素代码实例
2015/01/13 Javascript
JavaScript学习笔记之内置对象
2015/01/22 Javascript
JavaScript模拟鼠标右键菜单效果
2020/12/08 Javascript
JS读写CSS样式的方法汇总
2016/08/16 Javascript
BootStrap实现带有增删改查功能的表格(DEMO详解)
2016/10/26 Javascript
Angular的$http的ajax的请求操作(推荐)
2017/01/10 Javascript
d3.js实现立体柱图的方法详解
2017/04/28 Javascript
create-react-app 修改为多入口编译的方法
2018/08/01 Javascript
vue.js指令v-for使用以及下标索引的获取
2019/01/31 Javascript
微信小程序云开发 生成带参小程序码流程
2019/05/18 Javascript
[06:24]DOTA2 2015国际邀请赛中国区预选赛第二日TOP10
2015/05/27 DOTA
[01:03]PWL开团时刻DAY6——别打我
2020/11/05 DOTA
Python图像处理之识别图像中的文字(实例讲解)
2018/05/10 Python
Django之模型层多表操作的实现
2019/01/08 Python
Python实现非正太分布的异常值检测方式
2019/12/09 Python
Windows下Pycharm远程连接虚拟机中Centos下的Python环境(图文教程详解)
2020/03/19 Python
Python中使用socks5设置全局代理的方法示例
2020/04/15 Python
Python不支持 i ++ 语法的原因解析
2020/07/22 Python
Python join()函数原理及使用方法
2020/11/14 Python
python中的对数log函数表示及用法
2020/12/09 Python
css3一个简易的 LED 数字时钟实现方法
2020/01/15 HTML / CSS
Hotels.com英国:全球领先的酒店住宿提供商
2019/01/24 全球购物
介绍一下SQL Server里面的索引视图
2016/07/31 面试题
销售业务实习自我鉴定
2013/09/23 职场文书
单位领导证婚词
2014/01/14 职场文书
统计员岗位职责
2015/02/11 职场文书
大专护理专业自荐信
2015/03/25 职场文书
《惊弓之鸟》教学反思
2016/02/20 职场文书