JAVA+Netty根据二级域名实现多租户内网穿透功能【设计实践】

目录

背景

实践

项目结构

原理分析

代码实现

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-代理-客户端-真实服务)完整的通道。

代码实现

cc-common项目

直接复用上一篇的配置和代码

cc-server项目

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();
		}
	}
}

cc-client项目

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();
		}
	}
}

Nginx配置

方案一:适合单独部署专用于内网穿透的网关,监听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/

你可能感兴趣的:(java,netty,内网穿透)