使用 AnyEvent 来实现同一个端口跑二种服务
浏览:2871次 出处信息
前些日子 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^.
建议继续学习:
- HTTPS, SPDY和 HTTP/2性能的简单对比 (阅读:16801)
- 浅析http协议、cookies和session机制、浏览器缓存 (阅读:16710)
- 从输入 URL 到页面加载完成的过程中都发生了什么事情? (阅读:15289)
- HTTP协议Keep-Alive模式详解 (阅读:11498)
- Linux shell脚本使用while循环执行ssh的注意事项 (阅读:7613)
- 各种浏览器审查、监听http头工具介绍 (阅读:7237)
- nginx中对http请求处理的各个阶段分析 (阅读:6720)
- nginx上,http状态200响应,PHP空白返回的问题 (阅读:6353)
- 你不知道的 HTTP (阅读:6101)
- 计算机网络协议赏析-HTTP (阅读:5853)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
扫一扫订阅我的微信号:IT技术博客大学习
<< 前一篇:使用tcpdump搞定一个替换问题
文章信息
- 作者:扶 凯 来源: 扶凯
- 标签: AnyEvent http ssh
- 发布时间:2013-05-01 17:36:13
建议继续学习
近3天十大热文
-
[858] WordPress插件开发 -- 在插件使用 -
[136] 解决 nginx 反向代理网页首尾出现神秘字 -
[56] 分享一个JQUERY颜色选择插件 -
[56] 整理了一份招PHP高级工程师的面试题 -
[55] CloudSMS:免费匿名的云短信 -
[53] Innodb分表太多或者表分区太多,会导致内 -
[53] 如何保证一个程序在单台服务器上只有唯一实例( -
[52] 用 Jquery 模拟 select -
[51] 全站换域名时利用nginx和javascri -
[50] jQuery性能优化指南
