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一些公用函数的集合
Mar 27 PHP
PHP支持多种格式图片上传(支持jpg、png、gif)
Nov 03 PHP
Php图像处理类代码分享
Jan 19 PHP
Linux中用PHP判断程序运行状态的2个方法
May 04 PHP
PHP实现通过正则表达式替换回调的内容标签
Jun 15 PHP
PHP获取文件夹大小函数用法实例
Jul 01 PHP
PHP的邮件群发系统phplist配置方法详细总结
Mar 30 PHP
PHP登录验证码的实现与使用方法
Jul 07 PHP
PHP的自定义模板引擎
Mar 24 PHP
thinkphp5.0自定义验证规则使用方法
Nov 16 PHP
关于laravel后台模板laravel-admin select框的使用详解
Oct 03 PHP
PHP7 foreach() 函数修改
Mar 09 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
在 PHP 中使用随机数的三个步骤
2006/10/09 PHP
php后台程序与Javascript的两种交互方式
2009/10/25 PHP
php使用cookie显示用户上次访问网站日期的方法
2015/01/26 PHP
PHP文件操作方法汇总
2015/07/01 PHP
解决PHP使用CURL发送GET请求时传递参数的问题
2019/10/11 PHP
javascript天然的迭代器
2010/10/29 Javascript
JQuery自适应IFrame高度(支持嵌套 兼容IE,ff,safafi,chrome)
2011/03/28 Javascript
js 固定悬浮效果实现思路代码
2013/08/02 Javascript
使用jQueryMobile实现滑动翻页效果的方法
2015/02/04 Javascript
javascript实现移动端上的触屏拖拽功能
2016/03/04 Javascript
JavaScript中的操作符类型转换示例总结
2016/05/30 Javascript
JS重载实现方法分析
2016/12/16 Javascript
jQuery图片瀑布流的简单实现代码
2017/03/15 Javascript
原生JS实现小小的音乐播放器
2017/10/16 Javascript
webpack搭建vue 项目的步骤
2017/12/27 Javascript
微信小程序自定义toast的实现代码
2018/11/16 Javascript
小程序采集录音并上传到后台
2019/11/22 Javascript
nuxt+axios实现打包后动态修改请求地址的方法
2020/04/22 Javascript
使用Python抓取模板之家的CSS模板
2015/03/16 Python
python将回车作为输入内容的实例
2018/06/23 Python
详解分布式任务队列Celery使用说明
2018/11/29 Python
梅尔倒谱系数(MFCC)实现
2019/06/19 Python
PyCharm下载和安装详细步骤
2019/12/17 Python
Python 实现一个简单的web服务器
2021/01/03 Python
一款纯css3实现的漂亮的404页面的实例教程
2014/11/27 HTML / CSS
利用CSS3实现进度条的两种姿势详解
2017/03/21 HTML / CSS
html通过canvas转成base64的方法
2019/07/18 HTML / CSS
解决H5的a标签的download属性下载service上的文件出现跨域问题
2019/07/16 HTML / CSS
中国电子产品外贸网站:MiniIntheBox
2017/02/06 全球购物
Guess荷兰官网:美国服饰品牌
2020/01/22 全球购物
工伤赔偿协议书范本
2014/04/15 职场文书
老公婚前保证书
2015/02/28 职场文书
2015个人年度工作总结范文
2015/05/28 职场文书
诺贝尔奖获得者名言100句:句句启人心智,值永久收藏
2019/08/09 职场文书
nginx里的rewrite跳转的实现
2021/03/31 Servers
HTML常用标签超详细整理
2022/03/19 HTML / CSS