技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 系统运维 --> 使用 AnyEvent 来实现同一个端口跑二种服务

使用 AnyEvent 来实现同一个端口跑二种服务

浏览:2487次  出处信息

前些日子 Linode 的远程 ssh 服务被伟大的 GFW 过滤了。但 80 口还开着,很是神奇,相信一堆的人都出现过这种问题。我们这些 IT 民工们基本没有权势,在国内目前搞个备案是非常麻烦。所以一堆堆的做计算机的人都给自己的个人网站移到了大日本帝国中的 Linode ,这是目前连接中国最快的 Linode 节点。

正好以前看过资料怎么样使用一个端口来跑二个服务。其实说白了就是做个代理,然后前端进行协议的识别。然后绑定到后端的应用。所以我也搞了一个,GFW 没有过滤掉 80 那我就让我的服务器 80 端口同时服务 HTTP 协议和 SSH 协议好了。

如果是 HTTP 协议非常好识别,请求的第一行一定有如下的信息:

GET / HTTP/1.1
Host: www.php-oa.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: wp-settings-xxx
Connection: keep-alive

如果是 SSH 协议,就比较麻烦一点,我测试有好多种可能,默认的 Linux 中的客户端是不会主动发任何信息的。但是如果是 SecureCRT 会发送

SSH-2.0-SecureCRT_7.0.0 (build 326) SecureCRT

如果是 Tunnelier 的话,会主动发送

SSH-2.0-1.91 sshlib: Bitvise SSH Client (Tunnelier) 4.50

所以要根据这二个来处理。我下面的程序参考国外的 Perl 的实现,自己在他的基础上修复了一下 bug 重写和包装了一下。使用的 AnyEvent 实现的协议调度代理。还是得讲这个真的很容易实现高性能服务器。

packageProtoR;
 
useMoo;
useAnyEvent;
useAnyEvent::Socket;
useAnyEvent::Handle;
useCarp;
useSmart::Comments;
 
has upstream => is => 'rw';
has client   => is => 'rw';
 
my$DEBUG= 0;
my$HTTP_PORT= 80;
my$SSH_PORT  = 22;
 
subprotocol_recognition {
    my$self= shift;
    returnsub{
        my$handle= shift;
        my$protocal_port;
        if(!$handle->{rbuf}) {
            $protocal_port= $SSH_PORT;
        }
        elsif($handle->{rbuf} =~ /GET|POST|HEAD|PUT/i) {
            $protocal_port= $HTTP_PORT;
        }
        elsif($handle->{rbuf} =~ /SSH/i) {
            $protocal_port= $SSH_PORT;
        }
 
        $handle->rtimeout(0); # 如果 http 超时,不然也会进入 ssh 的 upstream.
 
        warn"$self 连接 upstream 端口 $protocal_port \n"if$DEBUG;
 
        unless($self->upstream) {
            tcp_connect('127.0.0.1', $protocal_port, $self->on_upstream($protocal_port));
        }
    };
}
 
subon_upstream {
    my($self, $port) = @_;
 
    returnsub{
        my$fh= shift;
 
        unless(defined$fh) {
            warn"Can't connect to upstream on port $port: $!\n";
            $self->close;
            return;
        }
 
        my$upstream= AnyEvent::Handle->new(
            fh       => $fh,
            on_error => $self->on_serv_error,
            on_read  => $self->bind_upstream_to_client
        );
 
        warn"$upstream serv_connected\n"if$DEBUG;
 
        $self->upstream($upstream);
 
        # 绑定客户端的读到后端
        $self->client->on_read($self->bind_client_to_upstream);
    };
}
 
 
subbind_upstream_to_client {
    my$self= shift;
 
    # 读服务器的数据给用户
    returnsub{
        my$handle= shift;
        warn"UPSTREAM -> CLIENT: ". length($handle->{rbuf}) . "bytes\n"if$DEBUG; 
        $self->client->push_write(delete$handle->{rbuf});
    };
}
 
subbind_client_to_upstream {
    my$self= shift;
 
    # 外网的请求连接进来
    returnsub{
        my$handle= shift;
        warn"CLIENT -> UPSTREAM: ". length($handle->{rbuf}) . " bytes\n"if$DEBUG;
        $self->upstream->push_write(delete$handle->{rbuf});
    };
}
 
subon_serv_error {
    my$self= shift;
 
    returnsub{
        my($upstream, undef, $msg) = @_;
        warn"UPSTREAM 出错 $msg\n"if$DEBUG;
        $self->close;
    };
}
 
subDESTROY {
    my$self= shift;
    $self->close;
}
 
subclose{
    my$self= shift;
    warn"关掉双向的连接\n"if$DEBUG;
 
    if($self->client) {
        $self->client->destroy;
    }
 
    if($self->upstream) {
        $self->upstream->destroy;
    }
}
 
 
subon_error {
    my$self= shift;
    returnsub{ $self->close} if$self;
}
1;
 
packagemain;
usestrict;
usewarnings;
  
useAnyEvent::Socket;
useAnyEvent::Handle;
useEV;
  
die"usage: $0 绑定的地址\n"if@ARGV!= 1;
  
 
my$ip_address= shift;
  
tcp_server($ip_address, 80, sub{
   my($fh, $host, $port) = @_;
 
   my$prp= ProtoR->new;
   $prp->client(
       AnyEvent::Handle->new(
           fh          => $fh,
           rtimeout    => 2,
           on_error    => $prp->on_error,
           on_rtimeout => $prp->protocol_recognition,
           on_read     => $prp->protocol_recognition, 
        )
    );
});
 
 
EV::run;

嗯,你现在见到的网页,就是通过这个转发提供出来的数据。唉,没法做 CDN 了啊 ^v^.


建议继续学习:

  1. HTTPS, SPDY和 HTTP/2性能的简单对比    (阅读:15647)
  2. 浅析http协议、cookies和session机制、浏览器缓存    (阅读:15479)
  3. 从输入 URL 到页面加载完成的过程中都发生了什么事情?    (阅读:14224)
  4. HTTP协议Keep-Alive模式详解    (阅读:10341)
  5. Linux shell脚本使用while循环执行ssh的注意事项    (阅读:6409)
  6. 各种浏览器审查、监听http头工具介绍    (阅读:6021)
  7. nginx中对http请求处理的各个阶段分析    (阅读:5822)
  8. nginx上,http状态200响应,PHP空白返回的问题    (阅读:5281)
  9. 你不知道的 HTTP    (阅读:5167)
  10. 计算机网络协议赏析-HTTP    (阅读:4752)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1