java开发内网穿透
用Java做内网穿透,内网最直接的穿透路子就是自己写一个TCP代理。市面上现成的内网工具不少,frp、穿透ngrok、内网花生壳,穿透但如果你手头只有一台公网服务器和Java环境,内网从零撸一个并不复杂。穿透核心逻辑就一句话:把公网服务器收到的内网请求,通过一条长连接转发到内网机器上,穿透再把内网响应原路送回。内网 我去年在一个物联网项目里就踩过这个坑。穿透客户现场的内网设备跑在内网,只有一台跳板机能出外网。穿透当时试了frp,内网配置倒不难,但客户运维死活不让装第三方二进制。没办法,只能自己用Java写。结果发现,Netty处理这种双向转发简直顺手。 假设你有一台公网服务器 这里需要两个角色:服务端(跑在公网)和客户端(跑在内网)。客户端主动连服务端,建立一条控制连接。外部请求进来时,服务端通过控制连接通知客户端:“嘿,有人要访问8080,你开个新连接过来传数据。”客户端收到指令,再跟服务端建一条数据连接,专门搬运这次请求的字节流。 为什么不直接复用控制连接?因为并发请求一多,单条连接会互相阻塞。分开走,每条数据连接对应一个外部请求,清清爽爽。 服务端启动两个ServerBootstrap。一个监听控制端口,比如 控制连接的Pipeline里加一个Handler,收到客户端注册消息后,把 客户端这边,收到指令后连服务端的 连接释放。外部用户关了浏览器,服务端得通知客户端关掉对应的内网连接。反过来,内网服务主动断开,客户端也得告诉服务端释放外部连接。我一开始没做双向清理,跑了两天内存直接爆了,Map里堆了几万个死连接。后来在每个数据连接的 还有心跳。内网出口的NAT映射可能几分钟就超时,控制连接一断,整个穿透就废了。客户端每隔30秒发个ping,服务端回pong,超时三次就重连。别用TCP自带的keepalive,默认两小时,黄花菜都凉了。 服务端核心就三段: 客户端用 这套东西跑在内网穿透场景够用了。延迟比frp高一点,因为多了一次连接建立的开销,但胜在可控。真要上生产,记得加TLS,不然控制连接裸奔,谁都能冒充你的内网客户端。先搞清楚数据怎么流
8.8.8.8,内网机器 192.168.1.100 上跑了个Web服务,端口8080。外部用户访问 8.8.8.8:9000,你希望流量能打到内网的8080。Netty的Channel和Pipeline怎么摆
7000,专门等内网客户端连进来。另一个监听外部访问端口,比如 9000。映射端口 -> 客户端Channel 存进一个ConcurrentHashMap。外部连接进来时,从Map里查出对应的客户端Channel,然后给客户端发一条“新建数据连接”的指令,带上一个唯一标识。7001 端口(数据端口),把标识发过去。服务端根据标识,把这条新连接和之前挂起的外部连接配对。配对成功后,两边直接对拷字节流——用Netty的 ByteBuf 来回writeAndFlush就行。一个容易翻车的地方
channelInactive 里加回调,把配对关系从Map里摘掉,才算稳了。代码骨架
// 控制端口 ServerBootstrap control = new ServerBootstrap(); control.group(boss, worker) .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new ControlHandler(proxyMap)); } }); control.bind(7000).sync(); // 外部访问端口 ServerBootstrap external = new ServerBootstrap(); external.group(boss, worker) .childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new ExternalHandler(proxyMap)); } }); external.bind(9000).sync(); Bootstrap 连服务端7000,断线重连用 EventLoop 的 schedule 做退避。数据连接建立后,直接 ch.pipeline().addLast(new RelayHandler()),里面就干一件事:inbound 收到数据,往对端Channel写。
-
上一篇
-
下一篇
- 随机阅读
- 搜索
-
- 友情链接
-