Melang之协程并发代理实战

2 篇文章 0 订阅
2 篇文章 0 订阅

之前的文章中,给大家介绍了一种新的协程语言——Melang
今天,给大家带来的是这款语言的企业首战,虽然是个较小的项目,但对于一款新语言的意义无疑是巨大的。并且,利用这款语言,让整个程序结构极为清晰与模块化。
由于笔者公司想搭建一个代理服务供其他网段机器上网之用,因此有了本文的项目。
注意:本文只是用于介绍语言特性和使用,并不鼓励读者违背国家政策法规,请勿将此文内容用于技术讨论外的一切其他用途。

程序结构

在之前的文章中我们介绍过,Melang的每一个脚本任务都是一个协程,协程之间的调度是抢占式的,协程之间的运行环境是隔离的,且一个协程还可以拉起其他协程。
所以,本文的socks5代理将采用协程并发的模式,协程结构如下:
在这里插入图片描述
每一个工作协程独立处理一个TCP连接。
故此,在访问web站点时,会由浏览器发起多个TCP到本代理,由主协程完成TCP的建立,然后拉起一个独立的工作协程处理该TCP上的协议和数据收发。当TCP连接断开收尾工作结束后,工作协程退出释放。
注:本文给出的代理目前仅支持TCP代理。

Socks5

这里捎带提及一下socks5协议。
这个协议是比较简单的,大致流程如下:

  1. 建立TCP后,代理服务器会收到客户端的握手报文
  2. 服务器端验证握手报文中的验证方式,并回复响应报文
  3. 客户端收到后验证完会发送本次代理数据的目的地址(可能是域名或IP)和端口
  4. 代理服务器尝试向该地址建立TCP,然后给客户端返回响应报文
  5. 当前4步完成后,就进入了数据透传的阶段

实现

废话再多不如代码上桌。
代码分为两个文件,一个是主协程脚本proxy.mln,另一个是工作协程脚本worker.mln,我们分别给出:

proxy.mln
recvTimeout = 50;
fd = @mln_tcp_listen('0.0.0.0', '1080');
@mln_print('Ready');
while (1) {
  sockfd = @mln_tcp_accept(fd);
  if (sockfd) {
    conf = [
      'fd': sockfd,
      'recvTimeout': recvTimeout,
    ];
    @mln_eval('worker.mln', @mln_json_encode(conf));
  } fi
}

可以看到,主协程的任务非常简单:

  1. 建立监听套接字
  2. 死循环建立TCP,并为每个TCP拉起一个worker.mln任务进行处理。
worker.mln
conf = @mln_json_decode(EVAL_DATA);
localFd = @mln_int(conf['fd']);
recvTimeout = @mln_int(conf['recvTimeout']);
state = 1;
localSend = '';
remoteSend = '';
remoteFd = nil;

@closeLocalSock()
{
  @mln_tcp_close(_localFd);
  _localFd = nil;
  _localSend = '';
}

@closeRemoteSock()
{
  @mln_tcp_close(_remoteFd);
  _remoteFd = nil;
  _remoteSend = '';
}

@localRecvHandler()
{
  if (_state == 1) {
    if (@mln_strlen(_remoteSend) < 3 || @mln_bin2int(_remoteSend[0]) != 5) {
      @closeLocalSock();
      return;
    } fi
    n = @mln_bin2int(_remoteSend[1]);
    if (@mln_strlen(_remoteSend) < n+2) {
      @closeLocalSock();
      return;
    } fi
    for (i = 0; i < n; ++i) {
      if (@mln_bin2int(_remoteSend[2+i]) == 0) {
        break;
      } fi
    }
    if (i >= n) {
      @closeLocalSock();
      return;
    } fi
    ret = @mln_tcp_send(_localFd, @mln_int2bin(5)[-1]+@mln_int2bin(0)[-1]);
    if (!ret) {
      @closeLocalSock();
      return;
    } fi
    _remoteSend = @mln_split(_remoteSend, n+2);
    _state = 2;
  } else if (_state == 2) {
    arr = [5, 7, 0, 1, 0, 0, 0, 0, 0, 0];
    err = '';
    for (i = 0; i < @mln_size(arr); ++i) {
      err += @mln_int2bin(arr[i])[-1];
    }
    len = @mln_strlen(_remoteSend);
    if (len < 8 || @mln_bin2int(_remoteSend[0]) != 5 || @mln_bin2int(_remoteSend[1]) != 1 || @mln_bin2int(_remoteSend[2]) != 0) {
      goto fail;
    } fi
    type = @mln_bin2int(_remoteSend[3]);
    addr = '';
    if (type == 1) {
      if (len < 10) {
        goto fail;
      } fi
      for (i = 0; i < 4; ++i) {
        addr += @mln_str(@mln_bin2int(_remoteSend[4+i]));
        if (i < 3) {
          addr += '.';
        } fi
      }
      n = 8;
    } else if (type == 3) {
      n = 5+@mln_bin2int(_remoteSend[4]);
      if (len < n+2) {
        goto fail;
      } fi
      addr = @mln_split(_remoteSend, 5, @mln_bin2int(_remoteSend[4]));
    } else if (type == 4) {
      if (len < 22) {
        goto fail;
      } fi
      for (i = 0; i < 8; ++i) {
        addr += @mln_bin2hex(_remoteSend[4+i*2]);
        addr += @mln_bin2hex(_remoteSend[4+i*2+1]);
        if (i < 7) {
          addr += ':';
        } fi
      }
      n = 20;
    } else {
      goto fail;
    }
    if (len < n + 2) {
      goto fail;
    } fi
    port = (@mln_bin2int(_remoteSend[n])<<8)|@mln_bin2int(_remoteSend[n+1]);

    @mln_print('connect['+addr+']');
    ret = @mln_tcp_connect(addr, @mln_str(port), 30000);
    if (!ret) {
      goto fail;
    } fi
    _remoteFd = ret;

    ret = ''+@mln_int2bin(5)[-1]+@mln_int2bin(0)[-1]+@mln_int2bin(0)[-1]+_remoteSend[3];
    ret += @mln_split(_remoteSend, 4, n - 2);
    ret = @mln_tcp_send(_localFd, ret);
    if (!ret) {
      @closeRemoteSock();
      @closeLocalSock();
      return;
    } fi
    _remoteSend = @mln_split(_remoteSend, n+2);
    _state = 3;
  } else {
    ret = @mln_tcp_send(_remoteFd, _remoteSend);
    if (!ret) {
      @closeRemoteSock();
    } else {
      _remoteSend = '';
    }
  }
  return;

fail:
  @mln_tcp_send(_localFd, err);
  @closeLocalSock();
  return;
}

//@mln_print(''+localFd);
while (1) {
  if (localFd) {
      if (state == 3 && !remoteFd && !localSend) {
        @closeLocalSock();
      } else {
        res = @mln_tcp_recv(localFd, recvTimeout);
        if (res) {
          if (@mln_is_bool(res)) {
            @closeLocalSock();
          } else {
            remoteSend += res;
          }
        } else if (@mln_is_bool(res)) {
          @closeLocalSock();
        } fi
      }
  } fi
  if (remoteFd) {
      if (state == 3 && !localFd && !remoteSend) {
        @closeRemoteSock();
      } else {
        res = @mln_tcp_recv(remoteFd, recvTimeout);
        if (res) {
          if (@mln_is_bool(res)) {
            @closeRemoteSock();
          } else {
            localSend += res;
          }
        } else if (@mln_is_bool(res)) {
            @closeRemoteSock();
        } fi
      }
  } fi
  if (@mln_is_nil(localFd) && @mln_is_nil(remoteFd)) {
    break;
  } fi
  if (remoteSend) {
    @localRecvHandler();
  } fi
  if (localSend) {
    ret = @mln_tcp_send(_localFd, _localSend);
    _localSend = '';
    if (!ret) {
      @closeLocalSock();
    } fi
  } fi
}
@mln_print('quit');

worker协程不足200行,简单说一下:

  • conf是从主协程传过来的配置;
  • locaFd就是刚刚建立的套接字;
  • recvTimeout是tcp接收数据的超时时间(毫秒),由于Melang的代码是同步代码(底层异步执行),因此需要有防止长时间阻塞在一个函数上的机制。但由于是同步模式代码,因此执行流程是非常清晰的;
  • state是用来标记socks5的状态:1-处理握手报文阶段,2-处理目的地址报文并建立连接阶段,3-数据透传阶段;
  • remoteFd是state为2时向目的地址建立的TCP套接字;
  • localSend和remoteSend是两个发送缓冲区,localSend是发送给localFd的数据,而remoteSend是发送给remoteFd的数据(state不为3时不会发给remoteFd);
  • closeLocalSock函数就是用来关闭与客户端通信的TCP连接并清空发送缓冲区;
  • closeRemoteSock函数就是用来关闭与目的地址通信的TCP连接并清空发送缓冲区;
  • localRecvHandler函数用来处理与客户端通信的套接字数据的,由于有两个阶段的socks5相关处理,因此相对冗长一些;
  • while死循环,这个循环就是用来接收两个TCP上数据,然后处理,然后再由指定的TCP发送出去;

额外说明,在函数中可以看到一些对全局变量名前加下划线的变量,这样的变量在本例中依旧是指全局变量。如不加下划线,那么变量名只会在当前函数作用域内搜寻,因此无法获取调用栈上层的变量值,而加了前置下划线的变量则会延调用栈顺序由内向外查询变量。

结尾

感兴趣的读者可以去到Melang的Github repo(https://github.com/Water-Melon/Melang)上按照README的内容下载并安装Melang进行尝试运行。
感谢阅读,欢迎大家留言评论或私信交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码哥比特

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值