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的cURL快速入门教程 (小偷采集程序)
Jun 02 PHP
php切割页面div内容的实现代码分享
Jul 31 PHP
利用PHP扩展vld查看PHP opcode操作步骤
Mar 04 PHP
在PHP中设置、使用、删除Cookie的解决方法
May 06 PHP
PHP判断是否有Get参数的方法
May 05 PHP
thinkphp模板赋值与替换实例简述
Nov 24 PHP
PHP设计模式之简单投诉页面实例
Feb 24 PHP
PHP实现的限制IP投票程序IP来源分析
May 04 PHP
PHP安装GeoIP扩展根据IP获取地理位置及计算距离的方法
Jul 01 PHP
浅谈htmlentities 、htmlspecialchars、addslashes的使用方法
Dec 09 PHP
php curl上传、下载、https登陆实现代码
Jul 23 PHP
Laravel框架中Blade模板的用法示例
Aug 30 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
短波收音机简介
2021/03/01 无线电
PHP教程 预定义变量
2009/10/23 PHP
使用PHP计算两个路径的相对路径
2013/06/14 PHP
PHP静态文件生成类实例
2014/11/29 PHP
yii2控制器Controller Ajax操作示例
2016/07/23 PHP
PHP接口继承及接口多继承原理与实现方法详解
2017/10/18 PHP
Vagrant(WSL)+PHPStorm+Xdebu 断点调试环境搭建
2019/12/13 PHP
js Dialog 实践分享
2012/10/22 Javascript
javascript怎么禁用浏览器后退按钮
2014/03/27 Javascript
基于 Docker 开发 NodeJS 应用
2014/07/30 NodeJs
JavaScript中获取Radio被选中的值
2015/11/11 Javascript
详解Vue2+Echarts实现多种图表数据可视化Dashboard(附源码)
2017/03/21 Javascript
JS验证input输入框(字母,数字,符号,中文)
2017/03/23 Javascript
三分钟学会用ES7中的Async/Await进行异步编程
2018/06/14 Javascript
搭建基于express框架运行环境的方法步骤
2018/11/15 Javascript
说说Vue.js中的functional函数化组件的使用
2019/02/12 Javascript
vue项目中监听手机物理返回键的实现
2020/01/18 Javascript
vue模块移动组件的实现示例
2020/05/20 Javascript
vue 获取url参数、get参数返回数组的操作
2020/11/12 Javascript
javascript实现时钟动画
2020/12/03 Javascript
Python操作Access数据库基本步骤分析
2016/09/19 Python
Python视频爬虫实现下载头条视频功能示例
2018/05/07 Python
替换python字典中的key值方法
2018/07/06 Python
python实现移位加密和解密
2019/03/22 Python
Python定时任务APScheduler安装及使用解析
2020/08/07 Python
Under Armour安德玛法国官网:美国高端运动科技品牌
2018/06/29 全球购物
Ariat英国官网:为世界顶级马术运动员制造最优质的鞋类和服装
2020/02/14 全球购物
家长对老师的感言
2014/03/11 职场文书
航海技术专业毕业生求职信
2014/04/06 职场文书
客户经理竞聘演讲稿
2014/05/15 职场文书
负责人任命书范本
2014/06/04 职场文书
讲文明懂礼貌演讲稿
2014/09/11 职场文书
学校领导班子四风问题整改意见
2014/10/02 职场文书
违反单位工作制度检讨书
2014/10/25 职场文书
2019年恭贺升学祝福语集锦
2019/08/15 职场文书
Nginx开源可视化配置工具NginxConfig使用教程
2022/06/21 Servers