• JAVA基于Netty实现内网穿透功能【设计实践】


    目录

    背景

    实践

    项目结构

    原理分析

    代码实现

    cc-common项目

     cc-server项目

     cc-client项目

    使用

    启动服务端

    启动客户端 

    备注


    背景

    本文实现了一个简单的内网穿透服务,可以满足代理基于TCP协议的项目,如Tomcat、Redis、MySQL、windows远程桌面等。

    实践

    项目结构

    拆分了三个项目:

    1、cc-common项目:存放了消息格式和消息编解码器

    2、cc-server项目:内网穿透服务端项目

    3、cc-client项目:内网穿透客户端项目

    原理分析

    内网穿透的实现过程主要分三步

    1、启动服务端,这时服务端监听了两个端口(16001,16002,可根据启动参数修改),

    一个用来接收客户端请求(16001端口),

    一个用来接收访客代理(16002端口)

    2、启动客户端,客户端访问服务端提供的(16001端口)建立连接(server-client通道)

    3、访客访问代理接口(16002端口),服务端监听到之后创建访客ID,然后通过(server-client通道)向客户端发送指令,客户端接收指令后连接到真实服务端口(8080,可根据启动参数修改),连接真实服务成功后,客户端会重新向服务端建立一条连接(访客-server通道),服务端把访客和该通道进行绑定

    这三步最终形成了(访客-代理-客户端-真实服务)完整的通道。

    代码实现

    cc-common项目

    maven配置

    1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    3. <modelVersion>4.0.0modelVersion>
    4. <groupId>com.luck.ccgroupId>
    5. <artifactId>cc-commonartifactId>
    6. <version>1.0-SNAPSHOTversion>
    7. <name>cc-commonname>
    8. <url>http://maven.apache.orgurl>
    9. <properties>
    10. <java.home>${env.JAVA_HOME}java.home>
    11. <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    12. <java.version>1.8java.version>
    13. properties>
    14. <dependencies>
    15. <dependency>
    16. <groupId>io.nettygroupId>
    17. <artifactId>netty-allartifactId>
    18. <version>4.1.74.Finalversion>
    19. dependency>
    20. dependencies>
    21. <build>
    22. <plugins>
    23. <plugin>
    24. <artifactId>maven-compiler-pluginartifactId>
    25. <configuration>
    26. <source>1.8source>
    27. <target>1.8target>
    28. configuration>
    29. plugin>
    30. plugins>
    31. build>
    32. project>

     java代码

     消息类

    1. package com.luck.msg;
    2. import java.util.Arrays;
    3. public class MyMsg {
    4. /** 心跳 */
    5. public static final byte TYPE_HEARTBEAT = 0X00;
    6. /** 连接成功 */
    7. public static final byte TYPE_CONNECT = 0X01;
    8. /** 数据传输 */
    9. public static final byte TYPE_TRANSFER = 0X02;
    10. /** 连接断开 */
    11. public static final byte TYPE_DISCONNECT = 0X09;
    12. /** 数据类型 */
    13. private byte type;
    14. /** 消息传输数据 */
    15. private byte[] data;
    16. public byte getType() {
    17. return type;
    18. }
    19. public void setType(byte type) {
    20. this.type = type;
    21. }
    22. public byte[] getData() {
    23. return data;
    24. }
    25. public void setData(byte[] data) {
    26. this.data = data;
    27. }
    28. @Override
    29. public String toString() {
    30. return "MyMsg [type=" + type + ", data=" + Arrays.toString(data) + "]";
    31. }
    32. }

    消息编码类

    1. package com.luck.msg;
    2. import io.netty.buffer.ByteBuf;
    3. import io.netty.channel.ChannelHandlerContext;
    4. import io.netty.handler.codec.MessageToByteEncoder;
    5. public class MyMsgEncoder extends MessageToByteEncoder {
    6. public MyMsgEncoder() {
    7. }
    8. @Override
    9. protected void encode(ChannelHandlerContext ctx, MyMsg msg, ByteBuf out) throws Exception {
    10. int bodyLength = 5;
    11. if (msg.getData() != null) {
    12. bodyLength += msg.getData().length;
    13. }
    14. out.writeInt(bodyLength);
    15. out.writeByte(msg.getType());
    16. if (msg.getData() != null) {
    17. out.writeBytes(msg.getData());
    18. }
    19. }
    20. }

     消息解码类

    1. package com.luck.msg;
    2. import io.netty.buffer.ByteBuf;
    3. import io.netty.channel.ChannelHandlerContext;
    4. import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
    5. public class MyMsgDecoder extends LengthFieldBasedFrameDecoder {
    6. public MyMsgDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
    7. int initialBytesToStrip) {
    8. super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    9. }
    10. public MyMsgDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
    11. int initialBytesToStrip, boolean failFast) {
    12. super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
    13. }
    14. @Override
    15. protected MyMsg decode(ChannelHandlerContext ctx, ByteBuf in2) throws Exception {
    16. ByteBuf in = (ByteBuf) super.decode(ctx, in2);
    17. if (in == null) {
    18. return null;
    19. }
    20. if (in.readableBytes() < 4) {
    21. return null;
    22. }
    23. MyMsg myMsg = new MyMsg();
    24. int dataLength = in.readInt();
    25. byte type = in.readByte();
    26. myMsg.setType(type);
    27. byte[] data = new byte[dataLength - 5];
    28. in.readBytes(data);
    29. myMsg.setData(data);
    30. in.release();
    31. return myMsg;
    32. }
    33. }

     cc-server项目

    maven配置

    1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    3. <modelVersion>4.0.0modelVersion>
    4. <groupId>com.luck.ccgroupId>
    5. <artifactId>cc-serverartifactId>
    6. <version>1.0-SNAPSHOTversion>
    7. <name>cc-servername>
    8. <url>http://maven.apache.orgurl>
    9. <properties>
    10. <java.home>${env.JAVA_HOME}java.home>
    11. <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    12. <java.version>1.8java.version>
    13. properties>
    14. <dependencies>
    15. <dependency>
    16. <groupId>com.luck.ccgroupId>
    17. <artifactId>cc-commonartifactId>
    18. <version>1.0-SNAPSHOTversion>
    19. dependency>
    20. dependencies>
    21. <build>
    22. <plugins>
    23. <plugin>
    24. <artifactId>maven-compiler-pluginartifactId>
    25. <configuration>
    26. <source>1.8source>
    27. <target>1.8target>
    28. configuration>
    29. plugin>
    30. <plugin>
    31. <artifactId>maven-assembly-pluginartifactId>
    32. <version>3.0.0version>
    33. <configuration>
    34. <archive>
    35. <manifest>
    36. <mainClass>com.luck.server.ServerStartmainClass>
    37. manifest>
    38. archive>
    39. <descriptorRefs>
    40. <descriptorRef>jar-with-dependenciesdescriptorRef>
    41. descriptorRefs>
    42. configuration>
    43. <executions>
    44. <execution>
    45. <id>make-assemblyid>
    46. <phase>packagephase>
    47. <goals>
    48. <goal>singlegoal>
    49. goals>
    50. execution>
    51. executions>
    52. plugin>
    53. plugins>
    54. build>
    55. project>

    Java代码 

     常量类

    1. package com.luck.server;
    2. import java.util.Map;
    3. import java.util.concurrent.ConcurrentHashMap;
    4. import io.netty.channel.Channel;
    5. import io.netty.util.AttributeKey;
    6. import io.netty.util.internal.StringUtil;
    7. public class Constant {
    8. /** 客户端服务channel */
    9. public static Channel clientChannel = null;
    10. /** 绑定channel_id */
    11. public static final AttributeKey VID = AttributeKey.newInstance("vid");
    12. /** 访客,客户服务channel */
    13. public static Map vcc = new ConcurrentHashMap<>();
    14. /** 访客,访客服务channel */
    15. public static Map vvc = new ConcurrentHashMap<>();
    16. /** 服务代理端口 */
    17. public static int visitorPort = 16002;
    18. /** 服务端口 */
    19. public static int serverPort = 16001;
    20. /**
    21. * 清除连接
    22. *
    23. * @param vid 访客ID
    24. */
    25. public static void clearVccVvc(String vid) {
    26. if (StringUtil.isNullOrEmpty(vid)) {
    27. return;
    28. }
    29. Channel clientChannel = vcc.get(vid);
    30. if (null != clientChannel) {
    31. clientChannel.attr(VID).set(null);
    32. vcc.remove(vid);
    33. }
    34. Channel visitorChannel = vvc.get(vid);
    35. if (null != visitorChannel) {
    36. visitorChannel.attr(VID).set(null);
    37. vvc.remove(vid);
    38. }
    39. }
    40. /**
    41. * 清除关闭连接
    42. *
    43. * @param vid 访客ID
    44. */
    45. public static void clearVccVvcAndClose(String vid) {
    46. if (StringUtil.isNullOrEmpty(vid)) {
    47. return;
    48. }
    49. Channel clientChannel = vcc.get(vid);
    50. if (null != clientChannel) {
    51. clientChannel.attr(VID).set(null);
    52. vcc.remove(vid);
    53. clientChannel.close();
    54. }
    55. Channel visitorChannel = vvc.get(vid);
    56. if (null != visitorChannel) {
    57. visitorChannel.attr(VID).set(null);
    58. vvc.remove(vid);
    59. visitorChannel.close();
    60. }
    61. }
    62. }

     服务代理handler

    1. package com.luck.server;
    2. import com.luck.msg.MyMsg;
    3. import io.netty.buffer.ByteBuf;
    4. import io.netty.buffer.Unpooled;
    5. import io.netty.channel.Channel;
    6. import io.netty.channel.ChannelFutureListener;
    7. import io.netty.channel.ChannelHandlerContext;
    8. import io.netty.channel.ChannelOption;
    9. import io.netty.channel.SimpleChannelInboundHandler;
    10. import io.netty.handler.timeout.IdleStateEvent;
    11. import io.netty.util.internal.StringUtil;
    12. public class ClientHandler extends SimpleChannelInboundHandler {
    13. @Override
    14. public void channelRead0(ChannelHandlerContext ctx, MyMsg myMsg) {
    15. // 代理服务器读到客户端数据了
    16. byte type = myMsg.getType();
    17. switch (type) {
    18. case MyMsg.TYPE_HEARTBEAT:
    19. MyMsg hb = new MyMsg();
    20. hb.setType(MyMsg.TYPE_HEARTBEAT);
    21. ctx.channel().writeAndFlush(hb);
    22. break;
    23. case MyMsg.TYPE_CONNECT:
    24. String vid = new String(myMsg.getData());
    25. if (StringUtil.isNullOrEmpty(vid) || "client".equals(vid)) {
    26. Constant.clientChannel = ctx.channel();
    27. } else {
    28. // 绑定访客和客户端的连接
    29. Channel visitorChannel = Constant.vvc.get(vid);
    30. if (null != visitorChannel) {
    31. ctx.channel().attr(Constant.VID).set(vid);
    32. Constant.vcc.put(vid, ctx.channel());
    33. // 通道绑定完成可以读取访客数据
    34. visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
    35. }
    36. }
    37. break;
    38. case MyMsg.TYPE_DISCONNECT:
    39. String disVid = new String(myMsg.getData());
    40. Constant.clearVccVvcAndClose(disVid);
    41. break;
    42. case MyMsg.TYPE_TRANSFER:
    43. // 把数据转到用户服务
    44. ByteBuf buf = ctx.alloc().buffer(myMsg.getData().length);
    45. buf.writeBytes(myMsg.getData());
    46. String visitorId = ctx.channel().attr(Constant.VID).get();
    47. Channel vchannel = Constant.vvc.get(visitorId);
    48. if (null != vchannel) {
    49. vchannel.writeAndFlush(buf);
    50. }
    51. break;
    52. default:
    53. // 操作有误
    54. }
    55. // 代理服务器发送数据到用户了
    56. }
    57. @Override
    58. public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
    59. String vid = ctx.channel().attr(Constant.VID).get();
    60. if(StringUtil.isNullOrEmpty(vid)) {
    61. super.channelWritabilityChanged(ctx);
    62. return;
    63. }
    64. Channel visitorChannel = Constant.vvc.get(vid);
    65. if (visitorChannel != null) {
    66. visitorChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
    67. }
    68. super.channelWritabilityChanged(ctx);
    69. }
    70. @Override
    71. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    72. String vid = ctx.channel().attr(Constant.VID).get();
    73. if (StringUtil.isNullOrEmpty(vid)) {
    74. super.channelInactive(ctx);
    75. return;
    76. }
    77. Channel visitorChannel = Constant.vvc.get(vid);
    78. if (visitorChannel != null && visitorChannel.isActive()) {
    79. // 数据发送完成后再关闭连接,解决http1.0数据传输问题
    80. visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    81. visitorChannel.close();
    82. } else {
    83. ctx.channel().close();
    84. }
    85. Constant.clearVccVvc(vid);
    86. super.channelInactive(ctx);
    87. }
    88. @Override
    89. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    90. super.exceptionCaught(ctx, cause);
    91. }
    92. @Override
    93. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    94. if (evt instanceof IdleStateEvent) {
    95. IdleStateEvent event = (IdleStateEvent) evt;
    96. switch (event.state()) {
    97. case READER_IDLE:
    98. ctx.channel().close();
    99. break;
    100. case WRITER_IDLE:
    101. break;
    102. case ALL_IDLE:
    103. break;
    104. }
    105. }
    106. }
    107. }

     访客handler

    1. package com.luck.server;
    2. import java.util.UUID;
    3. import com.luck.msg.MyMsg;
    4. import io.netty.buffer.ByteBuf;
    5. import io.netty.channel.Channel;
    6. import io.netty.channel.ChannelHandlerContext;
    7. import io.netty.channel.ChannelOption;
    8. import io.netty.channel.SimpleChannelInboundHandler;
    9. import io.netty.util.internal.StringUtil;
    10. public class VisitorHandler extends SimpleChannelInboundHandler {
    11. @Override
    12. public void channelActive(ChannelHandlerContext ctx) throws Exception {
    13. // 访客连接上代理服务器了
    14. Channel visitorChannel = ctx.channel();
    15. // 先不读取访客数据
    16. visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
    17. // 生成访客ID
    18. String vid = UUID.randomUUID().toString();
    19. // 绑定访客通道
    20. visitorChannel.attr(Constant.VID).set(vid);
    21. Constant.vvc.put(vid, visitorChannel);
    22. MyMsg myMsg = new MyMsg();
    23. myMsg.setType(MyMsg.TYPE_CONNECT);
    24. myMsg.setData(vid.getBytes());
    25. Constant.clientChannel.writeAndFlush(myMsg);
    26. super.channelActive(ctx);
    27. }
    28. @Override
    29. public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
    30. String vid = ctx.channel().attr(Constant.VID).get();
    31. if (StringUtil.isNullOrEmpty(vid)) {
    32. return;
    33. }
    34. byte[] bytes = new byte[buf.readableBytes()];
    35. buf.readBytes(bytes);
    36. MyMsg myMsg = new MyMsg();
    37. myMsg.setType(MyMsg.TYPE_TRANSFER);
    38. myMsg.setData(bytes);
    39. // 代理服务器发送数据到客户端了
    40. Channel clientChannel = Constant.vcc.get(vid);
    41. clientChannel.writeAndFlush(myMsg);
    42. }
    43. @Override
    44. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    45. String vid = ctx.channel().attr(Constant.VID).get();
    46. if (StringUtil.isNullOrEmpty(vid)) {
    47. super.channelInactive(ctx);
    48. return;
    49. }
    50. Channel clientChannel = Constant.vcc.get(vid);
    51. if (clientChannel != null && clientChannel.isActive()) {
    52. clientChannel.config().setOption(ChannelOption.AUTO_READ, true);
    53. // 通知客户端,访客连接已经断开
    54. MyMsg myMsg = new MyMsg();
    55. myMsg.setType(MyMsg.TYPE_DISCONNECT);
    56. myMsg.setData(vid.getBytes());
    57. clientChannel.writeAndFlush(myMsg);
    58. }
    59. Constant.clearVccVvc(vid);
    60. super.channelInactive(ctx);
    61. }
    62. @Override
    63. public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
    64. Channel visitorChannel = ctx.channel();
    65. String vid = visitorChannel.attr(Constant.VID).get();
    66. if (StringUtil.isNullOrEmpty(vid)) {
    67. super.channelWritabilityChanged(ctx);
    68. return;
    69. }
    70. Channel clientChannel = Constant.vcc.get(vid);
    71. if (clientChannel != null) {
    72. clientChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
    73. }
    74. super.channelWritabilityChanged(ctx);
    75. }
    76. @Override
    77. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    78. ctx.close();
    79. }
    80. }

     服务代理socket

    1. package com.luck.server;
    2. import com.luck.msg.MyMsgDecoder;
    3. import com.luck.msg.MyMsgEncoder;
    4. import io.netty.bootstrap.ServerBootstrap;
    5. import io.netty.channel.ChannelFuture;
    6. import io.netty.channel.ChannelFutureListener;
    7. import io.netty.channel.ChannelInitializer;
    8. import io.netty.channel.ChannelPipeline;
    9. import io.netty.channel.EventLoopGroup;
    10. import io.netty.channel.nio.NioEventLoopGroup;
    11. import io.netty.channel.socket.SocketChannel;
    12. import io.netty.channel.socket.nio.NioServerSocketChannel;
    13. import io.netty.handler.timeout.IdleStateHandler;
    14. public class ServerSocket {
    15. private static EventLoopGroup bossGroup = new NioEventLoopGroup();
    16. private static EventLoopGroup workerGroup = new NioEventLoopGroup();
    17. private static ChannelFuture channelFuture;
    18. /**
    19. * 启动服务端
    20. * @throws Exception
    21. */
    22. public static void startServer() throws Exception {
    23. try {
    24. ServerBootstrap b = new ServerBootstrap();
    25. b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
    26. .childHandler(new ChannelInitializer() {
    27. @Override
    28. public void initChannel(SocketChannel ch) throws Exception {
    29. ChannelPipeline pipeline = ch.pipeline();
    30. pipeline.addLast(new MyMsgDecoder(Integer.MAX_VALUE, 0, 4, -4, 0));
    31. pipeline.addLast(new MyMsgEncoder());
    32. pipeline.addLast(new IdleStateHandler(40, 10, 0));
    33. pipeline.addLast(new ClientHandler());
    34. }
    35. });
    36. channelFuture = b.bind(Constant.serverPort).sync();
    37. channelFuture.addListener((ChannelFutureListener) channelFuture -> {
    38. // 服务器已启动
    39. });
    40. channelFuture.channel().closeFuture().sync();
    41. } finally {
    42. shutdown();
    43. // 服务器已关闭
    44. }
    45. }
    46. public static void shutdown() {
    47. if (channelFuture != null) {
    48. channelFuture.channel().close().syncUninterruptibly();
    49. }
    50. if ((bossGroup != null) && (!bossGroup.isShutdown())) {
    51. bossGroup.shutdownGracefully();
    52. }
    53. if ((workerGroup != null) && (!workerGroup.isShutdown())) {
    54. workerGroup.shutdownGracefully();
    55. }
    56. }
    57. }

    访客代理socket

    1. package com.luck.server;
    2. import io.netty.bootstrap.ServerBootstrap;
    3. import io.netty.channel.ChannelDuplexHandler;
    4. import io.netty.channel.ChannelInitializer;
    5. import io.netty.channel.ChannelPipeline;
    6. import io.netty.channel.EventLoopGroup;
    7. import io.netty.channel.nio.NioEventLoopGroup;
    8. import io.netty.channel.socket.SocketChannel;
    9. import io.netty.channel.socket.nio.NioServerSocketChannel;
    10. public class VisitorSocket {
    11. private static EventLoopGroup bossGroup = new NioEventLoopGroup();
    12. private static EventLoopGroup workerGroup = new NioEventLoopGroup();
    13. /**
    14. * 启动服务代理
    15. *
    16. * @throws Exception
    17. */
    18. public static void startServer() throws Exception {
    19. ServerBootstrap b = new ServerBootstrap();
    20. b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
    21. .childHandler(new ChannelInitializer() {
    22. @Override
    23. public void initChannel(SocketChannel ch) throws Exception {
    24. ChannelPipeline pipeline = ch.pipeline();
    25. pipeline.addLast(new ChannelDuplexHandler());
    26. pipeline.addLast(new VisitorHandler());
    27. }
    28. });
    29. b.bind(Constant.visitorPort).get();
    30. }
    31. }

     启动类

    1. package com.luck.server;
    2. public class ServerStart {
    3. public static void main(String[] args) throws Exception {
    4. if (null != args && args.length == 2) {
    5. int visitorPort = Integer.parseInt(args[1]);
    6. int serverPort = Integer.parseInt(args[0]);
    7. Constant.visitorPort = visitorPort;
    8. Constant.serverPort = serverPort;
    9. // 启动访客服务端,用于接收访客请求
    10. VisitorSocket.startServer();
    11. // 启动代理服务端,用于接收客户端请求
    12. ServerSocket.startServer();
    13. }
    14. }
    15. }

     cc-client项目

    maven配置

    1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    3. <modelVersion>4.0.0modelVersion>
    4. <groupId>com.luck.ccgroupId>
    5. <artifactId>cc-clientartifactId>
    6. <version>1.0-SNAPSHOTversion>
    7. <name>cc-clientname>
    8. <url>http://maven.apache.orgurl>
    9. <properties>
    10. <java.home>${env.JAVA_HOME}java.home>
    11. <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    12. <java.version>1.8java.version>
    13. properties>
    14. <dependencies>
    15. <dependency>
    16. <groupId>com.luck.ccgroupId>
    17. <artifactId>cc-commonartifactId>
    18. <version>1.0-SNAPSHOTversion>
    19. dependency>
    20. dependencies>
    21. <build>
    22. <plugins>
    23. <plugin>
    24. <artifactId>maven-compiler-pluginartifactId>
    25. <configuration>
    26. <source>1.8source>
    27. <target>1.8target>
    28. configuration>
    29. plugin>
    30. <plugin>
    31. <artifactId>maven-assembly-pluginartifactId>
    32. <version>3.0.0version>
    33. <configuration>
    34. <archive>
    35. <manifest>
    36. <mainClass>com.luck.client.ClientStartmainClass>
    37. manifest>
    38. archive>
    39. <descriptorRefs>
    40. <descriptorRef>jar-with-dependenciesdescriptorRef>
    41. descriptorRefs>
    42. configuration>
    43. <executions>
    44. <execution>
    45. <id>make-assemblyid>
    46. <phase>packagephase>
    47. <goals>
    48. <goal>singlegoal>
    49. goals>
    50. execution>
    51. executions>
    52. plugin>
    53. plugins>
    54. build>
    55. project>

    JAVA代码

    常量类

    1. package com.luck.client;
    2. import java.util.Map;
    3. import java.util.concurrent.ConcurrentHashMap;
    4. import io.netty.channel.Channel;
    5. import io.netty.util.AttributeKey;
    6. import io.netty.util.internal.StringUtil;
    7. public class Constant {
    8. /** 代理服务channel */
    9. public static Channel proxyChannel = null;
    10. /** 绑定访客id */
    11. public static final AttributeKey VID = AttributeKey.newInstance("vid");
    12. /** 访客,代理服务channel */
    13. public static Map vpc = new ConcurrentHashMap<>();
    14. /** 访客,真实服务channel */
    15. public static Map vrc = new ConcurrentHashMap<>();
    16. /** 真实服务端口 */
    17. public static int realPort = 8080;
    18. /** 服务端口 */
    19. public static int serverPort = 16001;
    20. /** 服务IP */
    21. public static String serverIp = "127.0.0.1";
    22. /**
    23. * 清除连接
    24. *
    25. * @param vid 访客ID
    26. */
    27. public static void clearvpcvrc(String vid) {
    28. if (StringUtil.isNullOrEmpty(vid)) {
    29. return;
    30. }
    31. Channel clientChannel = vpc.get(vid);
    32. if (null != clientChannel) {
    33. clientChannel.attr(VID).set(null);
    34. vpc.remove(vid);
    35. }
    36. Channel visitorChannel = vrc.get(vid);
    37. if (null != visitorChannel) {
    38. visitorChannel.attr(VID).set(null);
    39. vrc.remove(vid);
    40. }
    41. }
    42. /**
    43. * 清除关闭连接
    44. *
    45. * @param vid 访客ID
    46. */
    47. public static void clearvpcvrcAndClose(String vid) {
    48. if (StringUtil.isNullOrEmpty(vid)) {
    49. return;
    50. }
    51. Channel clientChannel = vpc.get(vid);
    52. if (null != clientChannel) {
    53. clientChannel.attr(VID).set(null);
    54. vpc.remove(vid);
    55. clientChannel.close();
    56. }
    57. Channel visitorChannel = vrc.get(vid);
    58. if (null != visitorChannel) {
    59. visitorChannel.attr(VID).set(null);
    60. vrc.remove(vid);
    61. visitorChannel.close();
    62. }
    63. }
    64. }

      客户端代理handler

    1. package com.luck.client;
    2. import com.luck.msg.MyMsg;
    3. import io.netty.buffer.ByteBuf;
    4. import io.netty.channel.Channel;
    5. import io.netty.channel.ChannelHandlerContext;
    6. import io.netty.channel.ChannelOption;
    7. import io.netty.channel.SimpleChannelInboundHandler;
    8. import io.netty.handler.timeout.IdleStateEvent;
    9. import io.netty.util.internal.StringUtil;
    10. public class ProxyHandler extends SimpleChannelInboundHandler {
    11. @Override
    12. public void channelRead0(ChannelHandlerContext ctx, MyMsg myMsg) {
    13. // 客户端读取到代理过来的数据了
    14. byte type = myMsg.getType();
    15. String vid = new String(myMsg.getData());
    16. switch (type) {
    17. case MyMsg.TYPE_HEARTBEAT:
    18. break;
    19. case MyMsg.TYPE_CONNECT:
    20. RealSocket.connectRealServer(vid);
    21. break;
    22. case MyMsg.TYPE_DISCONNECT:
    23. Constant.clearvpcvrcAndClose(vid);
    24. break;
    25. case MyMsg.TYPE_TRANSFER:
    26. // 把数据转到真实服务
    27. ByteBuf buf = ctx.alloc().buffer(myMsg.getData().length);
    28. buf.writeBytes(myMsg.getData());
    29. String visitorId = ctx.channel().attr(Constant.VID).get();
    30. Channel rchannel = Constant.vrc.get(visitorId);
    31. if (null != rchannel) {
    32. rchannel.writeAndFlush(buf);
    33. }
    34. break;
    35. default:
    36. // 操作有误
    37. }
    38. // 客户端发数据到真实服务了
    39. }
    40. @Override
    41. public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
    42. String vid = ctx.channel().attr(Constant.VID).get();
    43. if (StringUtil.isNullOrEmpty(vid)) {
    44. super.channelWritabilityChanged(ctx);
    45. return;
    46. }
    47. Channel realChannel = Constant.vrc.get(vid);
    48. if (realChannel != null) {
    49. realChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
    50. }
    51. super.channelWritabilityChanged(ctx);
    52. }
    53. @Override
    54. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    55. String vid = ctx.channel().attr(Constant.VID).get();
    56. if (StringUtil.isNullOrEmpty(vid)) {
    57. super.channelInactive(ctx);
    58. return;
    59. }
    60. Channel realChannel = Constant.vrc.get(vid);
    61. if (realChannel != null && realChannel.isActive()) {
    62. realChannel.close();
    63. }
    64. super.channelInactive(ctx);
    65. }
    66. @Override
    67. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    68. super.exceptionCaught(ctx, cause);
    69. cause.printStackTrace();
    70. }
    71. @Override
    72. public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    73. if (evt instanceof IdleStateEvent) {
    74. IdleStateEvent event = (IdleStateEvent) evt;
    75. switch (event.state()) {
    76. case READER_IDLE:
    77. ctx.channel().close();
    78. break;
    79. case WRITER_IDLE:
    80. MyMsg myMsg = new MyMsg();
    81. myMsg.setType(MyMsg.TYPE_HEARTBEAT);
    82. ctx.channel().writeAndFlush(myMsg);
    83. break;
    84. case ALL_IDLE:
    85. break;
    86. }
    87. }
    88. }
    89. }

     真实服务handler

    1. package com.luck.client;
    2. import com.luck.msg.MyMsg;
    3. import io.netty.buffer.ByteBuf;
    4. import io.netty.channel.Channel;
    5. import io.netty.channel.ChannelHandlerContext;
    6. import io.netty.channel.ChannelOption;
    7. import io.netty.channel.SimpleChannelInboundHandler;
    8. import io.netty.util.internal.StringUtil;
    9. public class RealHandler extends SimpleChannelInboundHandler {
    10. @Override
    11. public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
    12. // 客户读取到真实服务数据了
    13. byte[] bytes = new byte[buf.readableBytes()];
    14. buf.readBytes(bytes);
    15. MyMsg myMsg = new MyMsg();
    16. myMsg.setType(MyMsg.TYPE_TRANSFER);
    17. myMsg.setData(bytes);
    18. String vid = ctx.channel().attr(Constant.VID).get();
    19. if (StringUtil.isNullOrEmpty(vid)) {
    20. return;
    21. }
    22. Channel proxyChannel = Constant.vpc.get(vid);
    23. if (null != proxyChannel) {
    24. proxyChannel.writeAndFlush(myMsg);
    25. }
    26. // 客户端发送真实数据到代理了
    27. }
    28. @Override
    29. public void channelActive(ChannelHandlerContext ctx) throws Exception {
    30. super.channelActive(ctx);
    31. }
    32. @Override
    33. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    34. String vid = ctx.channel().attr(Constant.VID).get();
    35. if (StringUtil.isNullOrEmpty(vid)) {
    36. super.channelInactive(ctx);
    37. return;
    38. }
    39. Channel proxyChannel = Constant.vpc.get(vid);
    40. if (proxyChannel != null) {
    41. MyMsg myMsg = new MyMsg();
    42. myMsg.setType(MyMsg.TYPE_DISCONNECT);
    43. myMsg.setData(vid.getBytes());
    44. proxyChannel.writeAndFlush(myMsg);
    45. }
    46. super.channelInactive(ctx);
    47. }
    48. @Override
    49. public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
    50. String vid = ctx.channel().attr(Constant.VID).get();
    51. if (StringUtil.isNullOrEmpty(vid)) {
    52. super.channelWritabilityChanged(ctx);
    53. return;
    54. }
    55. Channel proxyChannel = Constant.vpc.get(vid);
    56. if (proxyChannel != null) {
    57. proxyChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
    58. }
    59. super.channelWritabilityChanged(ctx);
    60. }
    61. @Override
    62. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    63. super.exceptionCaught(ctx, cause);
    64. }
    65. }

     客户端代理socket

    1. package com.luck.client;
    2. import java.util.concurrent.Executors;
    3. import java.util.concurrent.ScheduledExecutorService;
    4. import java.util.concurrent.TimeUnit;
    5. import com.luck.msg.MyMsg;
    6. import com.luck.msg.MyMsgDecoder;
    7. import com.luck.msg.MyMsgEncoder;
    8. import io.netty.bootstrap.Bootstrap;
    9. import io.netty.channel.Channel;
    10. import io.netty.channel.ChannelFuture;
    11. import io.netty.channel.ChannelFutureListener;
    12. import io.netty.channel.ChannelInitializer;
    13. import io.netty.channel.ChannelOption;
    14. import io.netty.channel.ChannelPipeline;
    15. import io.netty.channel.EventLoopGroup;
    16. import io.netty.channel.nio.NioEventLoopGroup;
    17. import io.netty.channel.socket.SocketChannel;
    18. import io.netty.channel.socket.nio.NioSocketChannel;
    19. import io.netty.handler.timeout.IdleStateHandler;
    20. import io.netty.util.internal.StringUtil;
    21. public class ProxySocket {
    22. private static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    23. /** 重连代理服务 */
    24. private static final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
    25. public static Channel connectProxyServer() throws Exception {
    26. reconnectExecutor.scheduleAtFixedRate(new Runnable() {
    27. public void run() {
    28. try {
    29. connectProxyServer(null);
    30. } catch (Exception e) {
    31. e.printStackTrace();
    32. }
    33. }
    34. }, 3, 3, TimeUnit.SECONDS);
    35. return connectProxyServer(null);
    36. }
    37. public static Channel connectProxyServer(String vid) throws Exception {
    38. if (StringUtil.isNullOrEmpty(vid)) {
    39. if (Constant.proxyChannel == null || !Constant.proxyChannel.isActive()) {
    40. newConnect(null);
    41. }
    42. return null;
    43. } else {
    44. Channel channel = Constant.vpc.get(vid);
    45. if (null == channel) {
    46. newConnect(vid);
    47. channel = Constant.vpc.get(vid);
    48. }
    49. return channel;
    50. }
    51. }
    52. private static void newConnect(String vid) throws InterruptedException {
    53. Bootstrap bootstrap = new Bootstrap();
    54. bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
    55. .handler(new ChannelInitializer() {
    56. @Override
    57. public void initChannel(SocketChannel ch) throws Exception {
    58. ChannelPipeline pipeline = ch.pipeline();
    59. pipeline.addLast(new MyMsgDecoder(Integer.MAX_VALUE, 0, 4, -4, 0));
    60. pipeline.addLast(new MyMsgEncoder());
    61. pipeline.addLast(new IdleStateHandler(40, 8, 0));
    62. pipeline.addLast(new ProxyHandler());
    63. }
    64. });
    65. bootstrap.connect(Constant.serverIp, Constant.serverPort).addListener(new ChannelFutureListener() {
    66. @Override
    67. public void operationComplete(ChannelFuture future) throws Exception {
    68. if (future.isSuccess()) {
    69. // 客户端链接代理服务器成功
    70. Channel channel = future.channel();
    71. if (StringUtil.isNullOrEmpty(vid)) {
    72. // 告诉服务端这条连接是client的连接
    73. MyMsg myMsg = new MyMsg();
    74. myMsg.setType(MyMsg.TYPE_CONNECT);
    75. myMsg.setData("client".getBytes());
    76. channel.writeAndFlush(myMsg);
    77. Constant.proxyChannel = channel;
    78. } else {
    79. // 告诉服务端这条连接是vid的连接
    80. MyMsg myMsg = new MyMsg();
    81. myMsg.setType(MyMsg.TYPE_CONNECT);
    82. myMsg.setData(vid.getBytes());
    83. channel.writeAndFlush(myMsg);
    84. // 客户端绑定通道关系
    85. Constant.vpc.put(vid, channel);
    86. channel.attr(Constant.VID).set(vid);
    87. Channel realChannel = Constant.vrc.get(vid);
    88. if (null != realChannel) {
    89. realChannel.config().setOption(ChannelOption.AUTO_READ, true);
    90. }
    91. }
    92. }
    93. }
    94. });
    95. }
    96. }

    真实服务socket 

    1. package com.luck.client;
    2. import io.netty.bootstrap.Bootstrap;
    3. import io.netty.channel.Channel;
    4. import io.netty.channel.ChannelFuture;
    5. import io.netty.channel.ChannelFutureListener;
    6. import io.netty.channel.ChannelInitializer;
    7. import io.netty.channel.ChannelOption;
    8. import io.netty.channel.ChannelPipeline;
    9. import io.netty.channel.EventLoopGroup;
    10. import io.netty.channel.nio.NioEventLoopGroup;
    11. import io.netty.channel.socket.SocketChannel;
    12. import io.netty.channel.socket.nio.NioSocketChannel;
    13. import io.netty.util.internal.StringUtil;
    14. public class RealSocket {
    15. static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    16. /**
    17. * 连接真实服务
    18. *
    19. * @param vid 访客ID
    20. * @return
    21. */
    22. public static Channel connectRealServer(String vid) {
    23. if (StringUtil.isNullOrEmpty(vid)) {
    24. return null;
    25. }
    26. Channel channel = Constant.vrc.get(vid);
    27. if (null == channel) {
    28. newConnect(vid);
    29. channel = Constant.vrc.get(vid);
    30. }
    31. return channel;
    32. }
    33. private static void newConnect(String vid) {
    34. try {
    35. Bootstrap bootstrap = new Bootstrap();
    36. bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
    37. .handler(new ChannelInitializer() {
    38. @Override
    39. public void initChannel(SocketChannel ch) throws Exception {
    40. ChannelPipeline pipeline = ch.pipeline();
    41. pipeline.addLast(new RealHandler());
    42. }
    43. });
    44. bootstrap.connect("127.0.0.1", Constant.realPort).addListener(new ChannelFutureListener() {
    45. @Override
    46. public void operationComplete(ChannelFuture future) throws Exception {
    47. if (future.isSuccess()) {
    48. // 客户端链接真实服务成功
    49. future.channel().config().setOption(ChannelOption.AUTO_READ, false);
    50. future.channel().attr(Constant.VID).set(vid);
    51. Constant.vrc.put(vid, future.channel());
    52. ProxySocket.connectProxyServer(vid);
    53. }
    54. }
    55. });
    56. } catch (Exception e) {
    57. e.printStackTrace();
    58. }
    59. }
    60. }

     启动类

    1. package com.luck.client;
    2. public class ClientStart {
    3. public static void main(String[] args) throws Exception {
    4. if (null != args && args.length == 3) {
    5. int realPort = Integer.parseInt(args[2]);
    6. int serverPort = Integer.parseInt(args[1]);
    7. String serverIp = args[0];
    8. Constant.serverIp = serverIp;
    9. Constant.serverPort = serverPort;
    10. Constant.realPort = realPort;
    11. // 连接代理服务
    12. ProxySocket.connectProxyServer();
    13. }
    14. }
    15. }

    使用

    启动服务端

    java -jar cc-server.jar 16001 16002

    启动客户端 

    java -jar cc-client.jar 127.0.0.1 16001 8080

    备注

    参考:中微子代理: 一款基于netty的内网穿透神器 (gitee.com)

    中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。

    中微子代理项目还在完善中,目前有比较完整的管理功能,支持多租户,代码也比较易读,需要的同学可以去看下。

  • 相关阅读:
    Hybrid Astar 算法剖析和实现(七)
    【ESP32 手机配网教程】
    综合能力 ---- 2. 法律法规
    第六章 图论 7 AcWing 1619. 欧拉路径
    【HCIE考试喜报】2022年11月11日考试通过
    【多态】为什么析构函数的名称统一处理为destructor?
    回归分析:逻辑斯蒂模型,多分类任务
    Pikachu靶场——目录遍历漏洞和敏感信息泄露
    Python爬虫(五)
    docker run和docker start的区别
  • 原文地址:https://blog.csdn.net/anshichuxuezhe/article/details/128193917