当前位置:

首页 > 编程开发 > 如何使用黑盒方式模拟实现内网穿透的Java实现。

如何使用黑盒方式模拟实现内网穿透的Java实现。

1.内网穿透简介从黑盒的角度理解:通常个人电脑无论是连接WIFI上网还是用网线上网,都是属于局域网里边的,外网无法直接访问到你的电脑,内网穿透可以让你的局域网中的电脑实现外网访问功能。举一个例子:你在本地运行了一个Web服务,占用端口是8080,那么你本地进行测试就是://localhost:8080。但是如果你想给一个好朋友分享你的服务,那怎么办呢?是的,就是采用内网穿透的方式。实际上,内网穿透是很复杂的一个操作,百度百科上面的解释为:内网穿透,也即NAT穿透,进行NAT穿透是为了使具有某一个特定源IP

本文目录

    1. 内网穿透简介

    从黑盒的角度理解: 通常个人电脑无论是连接WIFI上网还是用网线上网,都是属于局域网里边的,外网无法直接访问到你的电脑,内网穿透可以让你的局域网中的电脑实现外网访问功能。举一个例子: 你在本地运行了一个Web服务,占用端口是8080,那么你本地进行测试就是://localhost:8080。但是如果你想给一个好朋友分享你的服务,那怎么办呢?是的,就是采用内网穿透的方式。 实际上,内网穿透是很复杂的一个操作,百度百科上面的解释为:

    内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。

    这里,我显然是做不了的。我需要的只是从外网访问到内网的服务,至于具体的过程我不关心,只需要达到这个目的即可了。

    2. 具体想法和实现细节

    2.1 具体想法

    无论是哪种方式实现内网穿透都是需要一个公网IP地址的,我这里使用的一台阿里云的服务器。下面是整个模拟的示意图:

    如何使用黑盒方式模拟实现内网穿透的Java实现。

    注:

    1.内网穿透服务端部署在具有公网IP的机器上。

    2.内网服务和内网穿透客户端部署在内网机器上。

    说明:

    我的想法很简单,即用户访问内网穿透服务器,然后内网穿透服务器将用户的请求报文转发给内网穿透客户端,接着内网穿透客户端将请求报文转发给内网服务,然后接收内网服务的响应报文,将其转发给内网穿透服务端,最后由内网穿透服务端将其转发给用户。大致流程是这样的,对于外部的用户来说它只会认为它访问了一个外网服务,因为用户面对的是一个黑盒系统。

    2.2 实现细节

    为了实现上面那个目标,其中最为关键的就是维持内网穿透客户端和内网穿透服务端的一个长连接,我需要使用这个长连接来交换双方的报文信息。因此,这个长连接需要在系统启动后就建立好,当有用户的请求进来的时候,内网穿透服务端首先接收这个请求,然后使用长连接将其转给内网穿透客户端,内网穿透客户端使用该报文作为请求访问内网服务,然后接收内网服务的响应,将其转发给内网穿透服务端,最后将其转发给用户。

    3. 代码实现

    3.1 目录结构

    如何使用黑盒方式模拟实现内网穿透的Java实现。

    说明: 这个是内网穿透的服务端和客户端代码,我是放在一起了,没有分开写,因为双方需要使用到一些公用的类。但是建议还是分开成两个工程,因为需要分开部署。或者导出成jar包的时候,分别选择不同的主类即可。

    客户端代码文件:Client.java、Connection.java、Msg.java、ProxyConnection.java。
    服务端代码文件:Server.java、Connection.java、Msg.java、ProxyConnection.java。

    3.2 Client 类

    package org.dragon;
    import java.io.IOException;
    import java.net.Socket;
    import java.net.UnknownHostException;
    /**
     * 用于双向通信的客户端
     * */
    public class Client {
    	private static final String REMOTE_HOST = "公网IP";
    	private static final String LOCAL_HOST = "127.0.0.1";
    	public static void main(String[] args) {
    		try {
    			Socket proxy = new Socket(REMOTE_HOST, 10000);       
    			System.out.println("Connect Server Successfully!");
    			ProxyConnection proxyConnection = new ProxyConnection(proxy);  // 维持和内网穿透服务端的长连接
    			// 可以实现同一个人多次访问
    			while (true) {
    				Msg msg = proxyConnection.receiveMsg();
    				Connection connection = new Connection(new Socket(LOCAL_HOST, 8080));
    				connection.sendMsg(msg);    // 将请求报文发送给内网服务器,即模拟发送请求报文
    				msg = connection.receiveMsg();  // 接收内网服务器的响应报文
    				proxyConnection.sendMsg(msg);  // 将内网服务器的响应报文转发给公网服务器(内网穿透服务端)
    			}
    		} catch (UnknownHostException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }

    3.3 Connection 类

    package org.dragon;
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    /**
     * 维持用户和服务器的连接
     * */
    public class Connection {
    	private InputStream input;
    	private OutputStream output;
    	public Connection(Socket client) throws IOException {
    		this.input = new BufferedInputStream(client.getInputStream());
    		this.output = new BufferedOutputStream(client.getOutputStream());
    	}
    	public Msg receiveMsg() throws IOException {
    		byte[] msg = new byte[2*1024];
    		int len = input.read(msg);
    		return new Msg(len, msg);
    	}
    	public void sendMsg(Msg msg) throws IOException {
    		output.write(msg.getMsg(), 0, msg.getLen());
    		output.flush();  // 每一次写入都要刷新,防止阻塞。
    	}
    }

    3.4 Msg 类

    package org.dragon;
    public class Msg {
    	private int len;
    	private byte[] msg;
    	public Msg(int len, byte[] msg) {
    		this.len = len;
    		this.msg = msg;
    	}
    	public int getLen() {
    		return len;
    	}
    	public byte[] getMsg() {
    		return msg;
    	}
    	@Override
    	public String toString() {
    		return "msg: " + len + " --> " + new String(msg, 0, len);
    	}
    }

    3.5 ProxyConnection 类

    package org.dragon;
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.net.Socket;
    import java.net.UnknownHostException;
    /**
     * @author Alfred
     * 
     * 代理服务器和代理客户端是用于维持两者之间通信的一个长连接Socket,
     * 主要的目的是因为双方之间的通信方式是全双工的,它们的作用是为了传递报文。
     * */
    public class ProxyConnection {
    	private Socket proxySocket;
    	private DataInputStream input;
    	private DataOutputStream output;
    	public ProxyConnection(final Socket socket) throws UnknownHostException, IOException {
    		proxySocket = socket;
    		input = new DataInputStream(new BufferedInputStream(proxySocket.getInputStream()));
    		output = new DataOutputStream(new BufferedOutputStream(proxySocket.getOutputStream()));
    	}
    	/**
    	 * 接收报文
    	 * @throws IOException 
    	 * */
    	public Msg receiveMsg() throws IOException {
    		int len = input.readInt();
    		if (len <= 0) {
    			throw new IOException("异常接收数据,长度为:" + len);
    		}
    		byte[] msg = new byte[len];
    		int size = input.read(msg);  // 这里到底会不会读取到这么多,我也有点迷惑!
    		return new Msg(size, msg);   // 为了防止出错,还是使用一个记录实际读取值size
    	}
    	/**
    	 * 转发报文
    	 * @throws IOException 
    	 * */
    	public void sendMsg(Msg msg) throws IOException {
    		output.writeInt(msg.getLen());
    		output.write(msg.getMsg(), 0, msg.getLen());
    		output.flush();  // 每一次写入都需要手动刷新,防止阻塞。
    	}
    }

    3.6 Server 类

    package org.dragon;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    /**
     * 用于双向通信的服务器
     * */
    public class Server {
    	public static void main(String[] args) {
    		try (ServerSocket server = new ServerSocket(10000)) {
    			// 用于交换控制信息的Socket
    			Socket proxy = server.accept();
    			ProxyConnection proxySocket = new ProxyConnection(proxy);
    			// 用于正常通讯的socket
    			while (true) {
    				Socket client = server.accept();
    				Connection connection = new Connection(client);
    				Msg msg = connection.receiveMsg();  // 接收用户的请求报文
    				proxySocket.sendMsg(msg);           // 转发用户的请求报文给内网服务器
    				msg = proxySocket.receiveMsg();     // 接收内网服务器的响应报文
    				connection.sendMsg(msg);            // 转发内网服务器的响应报文给用户
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }

    4. 内网服务

    内网服务是一个web服务,这里我使用的是一个简单的SpringBoot项目,它只有三个请求方法。

    package org.dragon.controller;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    public class Controller {
    	@GetMapping("/loveEN")
    	public String testEN() {
    		return "I love you yesterday and today!";
    	}
    	@GetMapping("/loveZH") 
    	public String loveZH() {
    		return "有一美人兮,见之不忘。一日不见兮,思之如狂。凤飞翱翔兮,四海求凰。无奈佳人兮,不在东墙。";
    	}
    	@GetMapping("/loveJson")
    	public Map loveJson() {
    		HashMap map = new LinkedHashMap<>();
    		map.put("english", "I love you yesterday and today!");
    		map.put("chinese", "有一美人兮,见之不忘。一日不见兮,思之如狂。"
    				+ "凤飞翱翔兮,四海求凰。无奈佳人兮,不在东墙。");
    		return map;
    	}
    }

    5. 测试

    5.1 内网测试

    启动内网服务,在浏览器输入以下三条URL进行测试,功能正常。

      如何使用黑盒方式模拟实现内网穿透的Java实现。如何使用黑盒方式模拟实现内网穿透的Java实现。

    如何使用黑盒方式模拟实现内网穿透的Java实现。

    5.2 外网测试

    先后启动内网穿透服务端和内网穿透客户端,然后在浏览器访问一下三条URL即可。 注意: 1.如果你自己测试,切换成你运行内网穿透服务器的ip地址或者使用域名也行。 2.我这里外网机器和内网机器使用的是不同的端口(随便使用,只要不和自己机器上的服务端口冲突就行了),实际上可以在外网使用80端口,这样对普通用户比较友好。 3.第三条测试实际上是失败的,可以看到上面那个加载动画,一直在加载。按理说这个应该很快就停止了,但是似乎无法停下来。这是系统的bug了,但是由于我掌握的知识有限,就不去解决了。

    如何使用黑盒方式模拟实现内网穿透的Java实现。

    如何使用黑盒方式模拟实现内网穿透的Java实现。

    如何使用黑盒方式模拟实现内网穿透的Java实现。

    6. 注意事项

    这里的代码是一种模拟,它只能模拟这个功能,但是基本上不具备实际的作用,哈哈。因为我这里只有一个长连接,所以只能支持串行的通信,最好就是一个人简单的调用,似乎调用速度也不能太快了。我想了一种方式,在客户端和服务器之间维持一个连接池,这样就可以实现多线程访问了。这里没有处理TCP的粘包和分包(我理解了这个概念,但是我不太会处理它),所以我默认请求报文和响应报文都是2KB以内大小。如果超过这个长度会导致问题,尽管可以调大这个参数,但是如果多数报文的都是很小的话,也会导致效率低下。这个内网穿透是可以支持TCP之上的各种协议的,不一定是HTTP,至少理论上是可以的。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。