目录
背景
实践
项目结构
原理分析
代码实现
cc-common项目
cc-server项目
cc-client项目
Nginx配置
使用
启动服务端
启动客户端
上一篇JAVA基于Netty实现内网穿透功能【设计实践】_殷长庆的博客-CSDN博客
实现了通过访问外网IP:端口号的形式内网穿透单个本地服务的功能,理论上可以穿透任意基于TCP协议的本地服务
如果想支持本地一台电脑部署多个服务,或者是多台电脑多个服务的多租户内网穿透,在服务端的实现方式大概有两种
1、服务端根据规则向不同的客户端socket推消息,这个规则可以是客户端socket的标识,由客户端提供,服务端校验和自动维护;
2、服务端不同的端口向对应的客户端socket推消息,这个端口对应关系需要服务端主动维护,客户端根据服务端提供的端口直连。
本文通过访问二级域名实现多租户内网穿透,不同的二级域名对应不同的客户端服务,做到服务间互相隔离,因为用到了域名,不通过IP端口号直连的形式,所以只支持基于Http协议的穿透。
三个Java项目,代码直接在上篇基础上修改,一个Nginx:
1、cc-common项目:存放了消息格式和消息编解码器
2、cc-server项目:内网穿透服务端项目
3、cc-client项目:内网穿透客户端项目
4、Nginx代理访客请求
内网穿透的实现过程主要分三步
1、启动服务端,这时服务端监听了两个端口(16001,16002,可根据启动参数修改),
一个用来接收客户端请求(16001端口),
一个用来接收访客代理(16002端口)
2、启动客户端,客户端携带客户端标识(二级域名标识)访问服务端提供的(16001端口)建立专用连接(server-client通道)
3、访客通过二级域名访问Nginx(c1.test.com),Nginx转发请求到服务端访客代理接口(16002端口),服务端监听到之后解析二级域名,获取客户端专用连接通道,创建访客ID,然后通过(server-client通道)向客户端发送指令,客户端接收指令后连接到真实服务端口(8080,可根据启动参数修改),连接真实服务成功后,客户端会重新向服务端建立一条连接(访客-server通道),服务端把访客和该通道进行绑定
这三步最终形成了(访客-Nginx-代理-客户端-真实服务)完整的通道。
直接复用上一篇的配置和代码
maven配置
修改一下启动类
com.host.server.ServerStart
Java代码
常量类
package com.host.server;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import io.netty.util.internal.StringUtil;
public class Constant {
/** 客户端标识,客户端服务channel */
public static Map clientChannels = new ConcurrentHashMap<>();
/** 绑定channel_id */
public static final AttributeKey VID = AttributeKey.newInstance("vid");
/** 访客,客户服务channel */
public static Map vcc = new ConcurrentHashMap<>();
/** 访客,访客服务channel */
public static Map vvc = new ConcurrentHashMap<>();
/** 访客,消息 */
public static Map vm = new ConcurrentHashMap<>();
/** 服务代理端口 */
public static int visitorPort = 16002;
/** 服务端口 */
public static int serverPort = 16001;
/**
* 清除连接
*
* @param vid 访客ID
*/
public static void clearVccVvc(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vcc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vcc.remove(vid);
}
Channel visitorChannel = vvc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vvc.remove(vid);
}
vm.remove(vid);
}
/**
* 清除关闭连接
*
* @param vid 访客ID
*/
public static void clearVccVvcAndClose(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vcc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vcc.remove(vid);
clientChannel.close();
}
Channel visitorChannel = vvc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vvc.remove(vid);
visitorChannel.close();
}
vm.remove(vid);
}
}
服务代理handler
package com.host.server;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.internal.StringUtil;
public class ClientHandler extends SimpleChannelInboundHandler {
@Override
public void channelRead0(ChannelHandlerContext ctx, MyMsg myMsg) {
// 代理服务器读到客户端数据了
byte type = myMsg.getType();
switch (type) {
case MyMsg.TYPE_HEARTBEAT:
MyMsg hb = new MyMsg();
hb.setType(MyMsg.TYPE_HEARTBEAT);
ctx.channel().writeAndFlush(hb);
break;
case MyMsg.TYPE_CONNECT:
String vid = new String(myMsg.getData());
if (StringUtil.isNullOrEmpty(vid) || vid.startsWith("host: ")) {
Constant.clientChannels.put(vid, ctx.channel());
} else {
// 绑定访客和客户端的连接
Channel visitorChannel = Constant.vvc.get(vid);
if (null != visitorChannel) {
ctx.channel().attr(Constant.VID).set(vid);
Constant.vcc.put(vid, ctx.channel());
byte[] bs = Constant.vm.get(vid);
if (null != bs) {
MyMsg firstMsg = new MyMsg();
firstMsg.setType(MyMsg.TYPE_TRANSFER);
firstMsg.setData(bs);
ctx.channel().writeAndFlush(firstMsg);
Constant.vm.remove(vid);
}
}
}
break;
case MyMsg.TYPE_DISCONNECT:
String disVid = new String(myMsg.getData());
Constant.clearVccVvcAndClose(disVid);
break;
case MyMsg.TYPE_TRANSFER:
// 把数据转到用户服务
ByteBuf buf = ctx.alloc().buffer(myMsg.getData().length);
buf.writeBytes(myMsg.getData());
String visitorId = ctx.channel().attr(Constant.VID).get();
Channel vchannel = Constant.vvc.get(visitorId);
if (null != vchannel) {
vchannel.writeAndFlush(buf);
}
break;
default:
// 操作有误
}
// 代理服务器发送数据到用户了
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel visitorChannel = Constant.vvc.get(vid);
if (visitorChannel != null) {
visitorChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel visitorChannel = Constant.vvc.get(vid);
if (visitorChannel != null && visitorChannel.isActive()) {
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
visitorChannel.close();
} else {
ctx.channel().close();
}
Constant.clearVccVvc(vid);
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()) {
case READER_IDLE:
ctx.channel().close();
break;
case WRITER_IDLE:
break;
case ALL_IDLE:
break;
}
}
}
}
访客handler
package com.host.server;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.StringUtil;
public class VisitorHandler extends SimpleChannelInboundHandler {
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
String vid = ctx.channel().attr(Constant.VID).get();
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
if (StringUtil.isNullOrEmpty(vid)) {
int hbl = 512 >= bytes.length ? bytes.length : 512;
byte[] hbs = new byte[hbl];
System.arraycopy(bytes, 0, hbs, 0, hbl);
String headers = new String(hbs).toLowerCase();
List asList = Arrays.asList(headers.split("\r\n"));
String host = asList.stream().filter(x -> x.startsWith("host: ")).findFirst().orElse(null);
if (!StringUtil.isNullOrEmpty(host) && null != Constant.clientChannels.get(host)) {
// 生成访客ID
vid = UUID.randomUUID().toString();
Constant.vm.put(vid, bytes);
// 访客连接上代理服务器了
Channel visitorChannel = ctx.channel();
// 绑定访客通道
visitorChannel.attr(Constant.VID).set(vid);
Constant.vvc.put(vid, visitorChannel);
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_CONNECT);
myMsg.setData(vid.getBytes());
Constant.clientChannels.get(host).writeAndFlush(myMsg);
}
return;
}
// 代理服务器发送数据到客户端了
Channel clientChannel = Constant.vcc.get(vid);
if (null != clientChannel) {
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_TRANSFER);
myMsg.setData(bytes);
clientChannel.writeAndFlush(myMsg);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel clientChannel = Constant.vcc.get(vid);
if (clientChannel != null && clientChannel.isActive()) {
clientChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端,访客连接已经断开
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_DISCONNECT);
myMsg.setData(vid.getBytes());
clientChannel.writeAndFlush(myMsg);
}
Constant.clearVccVvc(vid);
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel visitorChannel = ctx.channel();
String vid = visitorChannel.attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel clientChannel = Constant.vcc.get(vid);
if (clientChannel != null) {
clientChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
服务代理socket
package com.host.server;
import com.luck.msg.MyMsgDecoder;
import com.luck.msg.MyMsgEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
public class ServerSocket {
private static EventLoopGroup bossGroup = new NioEventLoopGroup();
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
private static ChannelFuture channelFuture;
/**
* 启动服务端
*
* @throws Exception
*/
public static void startServer() throws Exception {
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyMsgDecoder(Integer.MAX_VALUE, 0, 4, -4, 0));
pipeline.addLast(new MyMsgEncoder());
pipeline.addLast(new IdleStateHandler(40, 10, 0));
pipeline.addLast(new ClientHandler());
}
});
channelFuture = b.bind(Constant.serverPort).sync();
channelFuture.addListener((ChannelFutureListener) channelFuture -> {
// 服务器已启动
});
channelFuture.channel().closeFuture().sync();
} finally {
shutdown();
// 服务器已关闭
}
}
public static void shutdown() {
if (channelFuture != null) {
channelFuture.channel().close().syncUninterruptibly();
}
if ((bossGroup != null) && (!bossGroup.isShutdown())) {
bossGroup.shutdownGracefully();
}
if ((workerGroup != null) && (!workerGroup.isShutdown())) {
workerGroup.shutdownGracefully();
}
}
}
访客代理socket
package com.host.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class VisitorSocket {
private static EventLoopGroup bossGroup = new NioEventLoopGroup();
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
/**
* 启动服务代理
*
* @throws Exception
*/
public static void startServer() throws Exception {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChannelDuplexHandler());
pipeline.addLast(new VisitorHandler());
}
});
b.bind(Constant.visitorPort).get();
}
}
启动类
package com.host.server;
public class ServerStart {
public static void main(String[] args) throws Exception {
if (null != args && args.length == 2) {
int visitorPort = Integer.parseInt(args[1]);
int serverPort = Integer.parseInt(args[0]);
Constant.visitorPort = visitorPort;
Constant.serverPort = serverPort;
// 启动访客服务端,用于接收访客请求
VisitorSocket.startServer();
// 启动代理服务端,用于接收客户端请求
ServerSocket.startServer();
}
}
}
maven配置
修改一下启动类
com.host.client.ClientStart
JAVA代码
常量类
package com.host.client;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import io.netty.util.internal.StringUtil;
public class Constant {
/** 代理服务channel */
public static Channel proxyChannel = null;
/** 绑定访客id */
public static final AttributeKey VID = AttributeKey.newInstance("vid");
/** 访客,代理服务channel */
public static Map vpc = new ConcurrentHashMap<>();
/** 访客,真实服务channel */
public static Map vrc = new ConcurrentHashMap<>();
/** 真实服务端口 */
public static int realPort = 8080;
/** 服务端口 */
public static int serverPort = 16001;
/** 服务IP */
public static String serverIp = "127.0.0.1";
/** 客户端唯一编码 */
public static String clientCode = "127.0.0.1:16002";
/**
* 清除连接
*
* @param vid 访客ID
*/
public static void clearvpcvrc(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vpc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vpc.remove(vid);
}
Channel visitorChannel = vrc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vrc.remove(vid);
}
}
/**
* 清除关闭连接
*
* @param vid 访客ID
*/
public static void clearvpcvrcAndClose(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vpc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vpc.remove(vid);
clientChannel.close();
}
Channel visitorChannel = vrc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vrc.remove(vid);
visitorChannel.close();
}
}
}
客户端代理handler
package com.host.client;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.internal.StringUtil;
public class ProxyHandler extends SimpleChannelInboundHandler {
@Override
public void channelRead0(ChannelHandlerContext ctx, MyMsg myMsg) {
// 客户端读取到代理过来的数据了
byte type = myMsg.getType();
String vid = new String(myMsg.getData());
switch (type) {
case MyMsg.TYPE_HEARTBEAT:
break;
case MyMsg.TYPE_CONNECT:
RealSocket.connectRealServer(vid);
break;
case MyMsg.TYPE_DISCONNECT:
Constant.clearvpcvrcAndClose(vid);
break;
case MyMsg.TYPE_TRANSFER:
// 把数据转到真实服务
ByteBuf buf = ctx.alloc().buffer(myMsg.getData().length);
buf.writeBytes(myMsg.getData());
String visitorId = ctx.channel().attr(Constant.VID).get();
Channel rchannel = Constant.vrc.get(visitorId);
if (null != rchannel) {
rchannel.writeAndFlush(buf);
}
break;
default:
// 操作有误
}
// 客户端发数据到真实服务了
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel realChannel = Constant.vrc.get(vid);
if (realChannel != null) {
realChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel realChannel = Constant.vrc.get(vid);
if (realChannel != null && realChannel.isActive()) {
realChannel.close();
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()) {
case READER_IDLE:
ctx.channel().close();
break;
case WRITER_IDLE:
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_HEARTBEAT);
ctx.channel().writeAndFlush(myMsg);
break;
case ALL_IDLE:
break;
}
}
}
}
真实服务handler
package com.host.client;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.StringUtil;
public class RealHandler extends SimpleChannelInboundHandler {
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
// 客户读取到真实服务数据了
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_TRANSFER);
myMsg.setData(bytes);
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel proxyChannel = Constant.vpc.get(vid);
if (null != proxyChannel) {
proxyChannel.writeAndFlush(myMsg);
}
// 客户端发送真实数据到代理了
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel proxyChannel = Constant.vpc.get(vid);
if (proxyChannel != null) {
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_DISCONNECT);
myMsg.setData(vid.getBytes());
proxyChannel.writeAndFlush(myMsg);
}
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel proxyChannel = Constant.vpc.get(vid);
if (proxyChannel != null) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
客户端代理socket
package com.host.client;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.luck.msg.MyMsg;
import com.luck.msg.MyMsgDecoder;
import com.luck.msg.MyMsgEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.internal.StringUtil;
public class ProxySocket {
private static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
/** 重连代理服务 */
private static final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
public static Channel connectProxyServer() throws Exception {
reconnectExecutor.scheduleAtFixedRate(new Runnable() {
public void run() {
try {
connectProxyServer(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 3, 3, TimeUnit.SECONDS);
return connectProxyServer(null);
}
public static Channel connectProxyServer(String vid) throws Exception {
if (StringUtil.isNullOrEmpty(vid)) {
if (Constant.proxyChannel == null || !Constant.proxyChannel.isActive()) {
newConnect(null);
}
return null;
} else {
Channel channel = Constant.vpc.get(vid);
if (null == channel) {
newConnect(vid);
channel = Constant.vpc.get(vid);
}
return channel;
}
}
private static void newConnect(String vid) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyMsgDecoder(Integer.MAX_VALUE, 0, 4, -4, 0));
pipeline.addLast(new MyMsgEncoder());
pipeline.addLast(new IdleStateHandler(40, 8, 0));
pipeline.addLast(new ProxyHandler());
}
});
bootstrap.connect(Constant.serverIp, Constant.serverPort).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 客户端链接代理服务器成功
Channel channel = future.channel();
if (StringUtil.isNullOrEmpty(vid)) {
// 告诉服务端这条连接是client的连接
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_CONNECT);
myMsg.setData(("host: " + Constant.clientCode).getBytes());
channel.writeAndFlush(myMsg);
Constant.proxyChannel = channel;
} else {
// 告诉服务端这条连接是vid的连接
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_CONNECT);
myMsg.setData(vid.getBytes());
channel.writeAndFlush(myMsg);
// 客户端绑定通道关系
Constant.vpc.put(vid, channel);
channel.attr(Constant.VID).set(vid);
Channel realChannel = Constant.vrc.get(vid);
if (null != realChannel) {
realChannel.config().setOption(ChannelOption.AUTO_READ, true);
}
}
}
}
});
}
}
真实服务socket
package com.host.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.internal.StringUtil;
public class RealSocket {
static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
/**
* 连接真实服务
*
* @param vid 访客ID
* @return
*/
public static Channel connectRealServer(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return null;
}
Channel channel = Constant.vrc.get(vid);
if (null == channel) {
newConnect(vid);
channel = Constant.vrc.get(vid);
}
return channel;
}
private static void newConnect(String vid) {
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new RealHandler());
}
});
bootstrap.connect("127.0.0.1", Constant.realPort).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 客户端链接真实服务成功
future.channel().config().setOption(ChannelOption.AUTO_READ, false);
future.channel().attr(Constant.VID).set(vid);
Constant.vrc.put(vid, future.channel());
ProxySocket.connectProxyServer(vid);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
启动类
package com.host.client;
public class ClientStart {
public static void main(String[] args) throws Exception {
if (null != args && args.length == 4) {
int realPort = Integer.parseInt(args[2]);
int serverPort = Integer.parseInt(args[1]);
String serverIp = args[0];
Constant.serverIp = serverIp;
Constant.serverPort = serverPort;
Constant.realPort = realPort;
Constant.clientCode = args[3];
// 连接代理服务
ProxySocket.connectProxyServer();
}
}
}
方案一:适合单独部署专用于内网穿透的网关,监听80端口转发所有请求
在根节点下创建stream节点,直接转发80到16002
stream{
upstream servsocket{
server 127.0.0.1:16002 weight=1 max_fails=2 fail_timeout=1;
}
server {
listen 80;
proxy_pass servsocket;
}
}
方案二:普通网关,可以灵活配置监听的server,(慎用长连接,可能导致接口串数据)
http {
...
upstream servsocket{
server 127.0.0.1:16002 weight=1 max_fails=2 fail_timeout=1;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server{
listen 80;
server_name ~^(?.+)\.mydomain\.com$;
location /{
proxy_pass http://servsocket;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 60;
proxy_read_timeout 60;
proxy_send_timeout 60;
}
location /ws {
proxy_pass http://servsocket;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_connect_timeout 120;
proxy_read_timeout 120;
proxy_send_timeout 120;
proxy_http_version 1.1;
}
}
server{
listen 80;
server_name mydomain.com;
...
}
}
java -jar cc-server.jar 16001 16002
16001:客户端访问端口
16002:nginx转发代理访问端口
java -jar cc-client.jar 127.0.0.1 16001 8080 c1.test.com
127.0.0.1:服务端IP地址
16001:服务端端口
8080:真实服务端口
c1.test.com:二级域名
打开浏览器访问,看是否能展示本地8080服务的页面
http://c1.test.com/