永久免费内网穿透,使用超简单的方式搭建,内含核心打洞代码

article/2025/9/20 11:59:39

 

内网穿透,即NAT穿透,是一个网络连接时术语,当计算机处于局域网内时,如家庭路由器、公司内部路由器等,出门在外就会受限于外网,不能直接访问,这时候通过内网穿透,就能让外网的电脑找到处于内网的电脑,将内网下应用映射到外网。

当下,内网穿透具有十分广泛的应用:对于个人用户来说,内网穿透可以让他们在外出时还能直接访问处于家庭路由器的电脑,进行相关操作;对于企业用户来说,内网穿透就具有更多的意义,如可以使出差在外的员工或其分支机构访问公司的OA、CRM、ERP系统,还能支持FTP、SVN、管家婆、金碟、用友,数据库软件如MYSQL与web应用服务以及视频监控等所有应用。

诸如此类的内网穿透解决方法其实很简单,那就是使用内网穿透软件或者自己手写一个,将内网下应用映射到外网,从而实现这一系列的简易操作。目前内网穿透软件有花生壳和神卓互联,花生壳限制流量而且收费,这里就介绍神卓互联内网穿透,重点是还很稳定,接下来就介绍和分析这款软件的用法和技术要点。

首先用法很简单,下载客户端一直点下一步安装即可,注册一个账号登录即可添加映射,全程自助一般人都会操作。

填写自己要穿透的应用名称和端口号,如果需要获取原访问者IP最好是选择Web应用。提交提交就可以了。

例如我需要发布一个Tomcat应用,访问端口号是7070,那么应用名称填写tomcat,内网主机填写127.0.0.1,内网端口填7070点提交就可以。

首先新建一个web项目

新建login.jsp登陆文件,内容如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登录系统</title>
<style type="text/css">
table td{font: 14px/1.5 'Microsoft YaHei',arial,tahoma,\5b8b\4f53,sans-serif;}
</style>
</head>
<body>
<table>
<tr><td>用户名</td><td><input type="text"></td></tr>
<tr><td>密码</td><td><input type="text"></td></tr>
<tr><td>&nbsp;</td><td><input type="submit" value="登录"></td></tr>
</table>
</body>
</html>

先在本地运行,看项目是否可以正常运行

本地运行没有问题,可以正常打开,接下来就试一下外网访问

打开神卓互联软件主界面,右键选择外网访问

永久免费内网穿透—神卓互联

如果需要绑定域名访问的话也很简单,这里不多说。

接下来就分析是如何做到将请求转发到内网因为又返回给访问客户端的。

接下来就是java版的TCP打洞核心代码

由于32位Ip地址的稀少,我们身边的设备,大部分运行在nat后面,无论是家庭还是单位,都会由一个路由器统一接入互联网,很多设备连上路由器组成一个内网。同一内网里的所有设备,拥有相同的外网ip地址,内网设备对外网进行访问,每次会使用不同的端口进行通信,不同内网里面的设备不能直接进行连接 ,因为不知道对方的公网地址和端口,这个时候就需要借助一台公网的设备进行牵线搭桥,也就是大家常说的穿透打洞。穿透的原理和NAT的运行原理,就不在此讨论,网上已有大量理论文章。

假设现在有以下3台机器:

外网机器,IP:121.56.21.85 , 以下简称“主机A”

处在内网1下的机器,外网IP:106.116.5.45 ,内网IP:192.168.1.10, 以下简称“主机1”

处在内网2下的机器,外网IP:104.128.52.6 ,内网IP:192.168.0.11,以下简称“主机2”

很显然内网的两台机器不能直接连接,我们现在要实现的是借助外网机器,让两台内网机器进行tcp直连通讯。

实现过程如下:

1、主机A启动服务端程序,监听端口8888,接受TCP请求。

2、启动主机1的客户端程序,连接主机A的8888端口,建立TCP连接。

3、启动主机2的客户端程序,连接主机A的8888端口,建立TCP连接。

4、主机2发送一个命令告诉主机A,我要求与其他设备进行连接,请求协助进行穿透。

5、主机A接收到主机2的命令之后,会返回主机1的外网地址和端口给主机2,同时把主机2的外网地址和端口发送给主机1。

6、主机1和主机2在收到主机A的信息之后,同时异步发起对对方的连接。

7、在与对方发起连接之后,监听本地与主机A连接的端口(也可以在发起连接之前),(由于不同的操作系统对tcp的实现不尽相同,有的操作系统会在连接发送之后,把对方的连接当作是回应,即发出SYN之后,把对方发来的SYN当作是本次SYN的ACK,这种情况就不需要监听也可建立连接,本文的代码所在测试环境就不需要监听,测试环境为:服务器centos 7.3, 内网1 win10,内网2 win10和centos7.2都测试过)。

8、主机1和主机2成功连上,可以关闭主机A的服务,主机1和主机2的连接依然会持续生效,不关闭就形成了一个3方直连的拓扑网状结构网络。

服务器端代码:

package org.inchain.p2p;import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;/*** 外网端服务,穿透中继* * @author ln**/
public class Server {public static List<ServerThread> connections = new ArrayList<ServerThread>();public static void main(String[] args) {try {// 1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口ServerSocket serverSocket = new ServerSocket(8888);Socket socket = null;// 记录客户端的数量int count = 0;System.out.println("***服务器即将启动,等待客户端的连接***");// 循环监听等待客户端的连接while (true) {// 调用accept()方法开始监听,等待客户端的连接socket = serverSocket.accept();// 创建一个新的线程ServerThread serverThread = new ServerThread(socket);// 启动线程serverThread.start();connections.add(serverThread);count++;// 统计客户端的数量System.out.println("客户端的数量:" + count);}} catch (IOException e) {e.printStackTrace();}}
}
package org.inchain.p2p;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;/*** 外网端服务多线程处理内网端连接* * @author ln**/
public class ServerThread extends Thread {// 和本线程相关的Socketprivate Socket socket = null;private BufferedReader br = null;private PrintWriter pw = null;public ServerThread(Socket socket) throws IOException {this.socket = socket;this.br = new BufferedReader(new InputStreamReader(socket.getInputStream()));this.pw = new PrintWriter(socket.getOutputStream());}// 线程执行的操作,响应客户端的请求public void run() {InetAddress address = socket.getInetAddress();System.out.println("新连接,客户端的IP:" + address.getHostAddress() + " ,端口:" + socket.getPort());try {pw.write("已有客户端列表:" + Server.connections + "\n");// 获取输入流,并读取客户端信息String info = null;while ((info = br.readLine()) != null) {// 循环读取客户端的信息System.out.println("我是服务器,客户端说:" + info);if (info.startsWith("newConn_")) {//接收到穿透消息,通知目标节点String[] infos = info.split("_");//目标节点的外网ip地址String ip = infos[1];//目标节点的外网端口String port = infos[2];System.out.println("打洞到 " + ip + ":" + port);for (ServerThread server : Server.connections) {if (server.socket.getInetAddress().getHostAddress().equals(ip)&& server.socket.getPort() == Integer.parseInt(port)) {//发送命令通知目标节点进行穿透连接server.pw.write("autoConn_" + socket.getInetAddress().getHostAddress() + "_" + socket.getPort()+ "\n");server.pw.flush();break;}}} else {// 获取输出流,响应客户端的请求pw.write("欢迎您!" + info + "\n");// 调用flush()方法将缓冲输出pw.flush();}}} catch (Exception e) {e.printStackTrace();} finally {System.out.println("客户端关闭:" + address.getHostAddress() + " ,端口:" + socket.getPort());Server.connections.remove(this);// 关闭资源try {if (pw != null) {pw.close();}if (br != null) {br.close();}if (socket != null) {socket.close();}} catch (IOException e) {e.printStackTrace();}}}@Overridepublic String toString() {return "ServerThread [socket=" + socket + "]";}
}

客户端代码:

package org.inchain.p2p;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;/*** 内网客户端,要进行穿透的内网服务**/
public class Client {//输入scannerprivate Scanner scanner = new Scanner(System.in);//是否等待输入private boolean isWaitInput = true;//首次与外网主机通信的连接private Socket socket;//首次与外网主机通信的本地端口private int localPort;private PrintWriter pw;private BufferedReader br;public static void main(String[] args) {new Client().start();}public void start() {try {// 新建一个socket通道socket = new Socket();// 设置reuseAddress为truesocket.setReuseAddress(true);//TODO在此输入外网地址和端口String ip = "xxx.xxxx.xxxx.xxxx";int port = 8888;socket.connect(new InetSocketAddress(ip, port));//首次与外网服务器通信的端口//这就意味着我们内网服务要与其他内网主机通信,就可以利用这个通道localPort = socket.getLocalPort();System.out.println("本地端口:" + localPort);System.out.println("请输入命令 notwait等待穿透,或者输入conn进行穿透");pw = new PrintWriter(socket.getOutputStream());br = new BufferedReader(new InputStreamReader(socket.getInputStream()));try {while (true) {if(process()) {break;}}} finally {// 关闭资源try {if(pw != null) {pw.close();}if (br != null) {br.close();}if (socket != null) {socket.close();}} catch (IOException e) {e.printStackTrace();}}} catch (Exception e) {e.printStackTrace();}}/** 处理与服务器连接的交互,返回是否退出*/private boolean process() throws IOException {String in = null;if (isWaitInput) {//把输入的命令发往服务端in = scanner.next();pw.write(in + "\n");//调用flush()方法将缓冲输出pw.flush();if ("notwait".equals(in)) {isWaitInput = false;}}//获取服务器的响应信息String info = br.readLine();if(info != null) {System.out.println("我是客户端,服务器说:" + info);}//处理本地命令processLocalCommand(in);//处理服务器命令processRemoteCommand(info);return "exit".equals(in);}private void processRemoteCommand(String info) throws IOException {if (info != null && info.startsWith("autoConn_")) {System.out.println("服务器端返回的打洞命令,自动连接目标");String[] infos = info.split("_");//目标外网地址String ip = infos[1];//目标外网端口String port = infos[2];doPenetration(ip, Integer.parseInt(port));}}private void processLocalCommand(String in) throws IOException {if ("conn".equals(in)) {System.out.println("请输入要连接的目标外网ip:");String ip = scanner.next();System.out.println("请输入要连接的目标外网端口:");int port = scanner.nextInt();pw.write("newConn_" + ip + "_" + port + "\n");pw.flush();doPenetration(ip, port);isWaitInput = false;}}/** 对目标服务器进行穿透*/private void doPenetration(String ip, int port) {try {//异步对目标发起连接new Thread() {public void run() {try {Socket newsocket = new Socket();newsocket.setReuseAddress(true);newsocket.bind(new InetSocketAddress(InetAddress.getLocalHost().getHostAddress(), localPort));System.out.println("connect to " + new InetSocketAddress(ip, port));newsocket.connect(new InetSocketAddress(ip, port));System.out.println("connect success");BufferedReader b = new BufferedReader(new InputStreamReader(newsocket.getInputStream()));PrintWriter p = new PrintWriter(newsocket.getOutputStream());while (true) {p.write("hello " + System.currentTimeMillis() + "\n");p.flush();String message = b.readLine();System.out.println(message);pw.write(message + "\n");pw.flush();if("exit".equals(message)) {break;}Thread.sleep(1000l);}b.close();p.close();newsocket.close();} catch (Exception e) {e.printStackTrace();}}}.start();//			//监听本地端口
//			ServerSocket serverSocket = new ServerSocket();
//			serverSocket.setReuseAddress(true);
//			serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), localPort));
//
//			// 记录客户端的数量
//			System.out.println("******开始监听端口:" + localPort);
//			// 循环监听等待客户端的连接
//			// 调用accept()方法开始监听,等待客户端的连接
//			Socket st = serverSocket.accept();
//			
//			System.out.println("成功了,哈哈,新的连接:" + st.getInetAddress().getHostAddress() + ":" + st.getPort());
//			
//			serverSocket.close();} catch (Exception e) {e.printStackTrace();System.out.println("监听端口 " + socket.getLocalPort() + " 出错");}}
}

服务端就是一个普通的socket服务,没什么特别的地方。
客户端需要注意的是:

1、最关键的地方,设置SO_REUSEADDR参数,41行的socket.setReuseAddress(true)和161行的newsocket.setReuseAddress(true)。

2、内网主机穿透时一定要异步发起连接。
3、在穿透时,新建的连接,需要先设置SO_REUSEADDR,再绑定端口,最后进行连接,顺序不能错。

 

如果改成上面这样就会连接超时。

最后附上测试方法和运行效果:

使用方法:
1、在服务器启动Server。
2、在客户端1启动Client,输入notwait命令,等待服务器通知打洞。
3、在客户端2启动Client,输入conn命令,然后输入服务器返回的客户端1的外网ip和端口,接下来就会自动完成连接。
运行效果:
客户端1运行结果 (穿透成功之后,客户端会把穿透对方返回的内容发送给服务器,服务器再返回)

客户端1使用netstat查看的网络连接

客户端2的运行结果

客户端2使用netstat查看的网络连接


可以看到客户端2对应的端口不同,那是因为电信NAT的问题,本地获取的Ip是电信10开头的内网地址,相当于在客户端2的上层还进行了一次中继。

ps:由于没有对称型的NAT设备,无法做深入研究,对称型设备的端口太难猜测,穿透成功概率很小。

一般真实的环境中都是用java nio,毕竟java socket是阻塞的,神卓互联应该是应nio技术

 

 

 

 

 

 

 

 

 

 

 

 

 

 


http://chatgpt.dhexx.cn/article/a6CVg3CE.shtml

相关文章

frp-免费内网穿透

环境准备 中文文档 公网IP服务器虚拟机&#xff08;内网&#xff09;内网穿透工具frp0.36.2版本地址 公网IP服务器(阿里云、腾讯云、华为云…) 上传frp_0.36.2_linux_amd64.tar.gz文件到服务器指定目录(如/home、/usr。。。。)解压文件tar xvf frp_0.36.2_linux_amd64.tar.g…

几款好用的免费内网穿透

【ngrok】 1、进入官网&#xff08;ngrok&#xff09;&#xff0c;下载适合你电脑系统的软件并安装。 2、我这里以Linux (ARM64)例&#xff0c;首先使用wget命令下载。 wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-arm64.zip 3、使用unzip命令解压。 un…

内网穿透(详细且免费)部署

内网穿透&#xff08;详细且免费&#xff09;100%成功 我们在做开发时&#xff0c;不想购买服务器&#xff0c;只想搭建我们本地的服务器&#xff0c;我们搭建的本地服务器只能供我们自己电脑的浏览器访问&#xff0c;或者处于同一个wifi下的手机访问平常我们访问的网站都是由…

五种永久免费 内网穿透傻瓜式使用

多种永久免费 内网穿透傻瓜式使用 教程方法一(使用qydev)方法二(使用飞鸽内网穿透)方法三(使用nps)方法四使用 (神卓互联内网穿透)方法五 使用 SAKURA FRP方法六 使用 网云穿方法七 使用 闪酷方法八 使用 蜻蜓映射方法九 使用 极点云 教程 方法一(使用qydev) 官网&#xff1a…

面积图-使用方法

文章来源&#xff1a;“面积图”就是折线图吗&#xff1f;https://www.sohu.com/a/199245953_416207 折线图&#xff1a;使用折线图&#xff0c;虽然也能很清晰第反映出两种产品的销量变化情况&#xff0c;但最想强调的——差距变化的部分&#xff0c;则欠缺可视化的突出表现 …

图像测量技术:面积测量

书山有路勤为径&#xff0c;学海无涯苦做舟 琴某人辛辛苦苦码的报告&#xff0c;当然不能让你们这么容易复制过去(๑• . •๑) 关注博主可以免费获得pdf版&#xff08;部分为word版&#xff09;报告 之前的源代码下载连接已失效&#xff0c;这是为了不扰乱课程秩序

cesium vue 天地图 WMTS图层数据 绘制点、线、面 测距、测面积

cesium vue 绘制点、线、面 测距、测面积 开箱即用 <template><div class"map-container"><div class"btn-container"><el-button type"primary" click"drawSelf(Point)">绘制点</el-button><el-b…

百度地图实现测量面积和测量距离功能

最近在公司的项目中&#xff0c;需要用到百度地图的测距和测面积功能&#xff0c;但是在网上只找到了测量距离的api&#xff0c;即BMapLib.DistanceTool。 但是测面积在网上没有找到很好的资料&#xff0c;百度提供的DrawingManager虽然也可以实现测面积&#xff0c;但是感觉太…

测量面积的手机软件有哪些?这两个不能错过

测量面积的手机软件有哪些&#xff1f;相信很多小伙伴&#xff0c;在日常生活中遇到了场地面积测量的问题总是犯难&#xff0c;如果身边有工具还是可以的&#xff0c;就怕身边没有测量的工具&#xff0c;这样一来不仅仅没办法去操作得到我们想要的数值&#xff0c;也极大的拖累…

【Cesium】距离量测和面积量测

1 、空间量测 空间量测是在三维空间中测量距离、角度、面积等内容。实现方法为在屏幕中拾取对应点的位置&#xff0c;然后将屏幕坐标转换为地理坐标&#xff0c;再根据地球椭球参数&#xff0c;进行几何解算&#xff0c;获取地理空间距离、面积等。 1.1、 距离量测 两点之间的…

百度地图API画多边型,测面积

效果&#xff1a; 脚本&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv"Content-Type" c…

cad面积计算机,AutoCAD如何测面积 AutoCAD面积计算方法

在AutoCAD运用的实例中&#xff0c;我们常常需要测量所画图形的尺寸面积&#xff0c;如果通过手算的方式总会觉得特别麻烦&#xff0c;还容易出错&#xff0c;为此小编特意为大家准备了最全面的CAD面积计算方法&#xff0c;教你如何巧妙的使用AutoCAD完成面积运算。 AutoCAD面积…

cesium工具-测距和测面-原理和代码

测距分为&#xff1a;空间距离和贴地距离&#xff1b; 测面积分为&#xff1a;水平面积和贴地面积。 下面讲解这4个工具的相关思路和代码&#xff1a; 1、空间距离 思路&#xff1a; Cesium.Cartesian3.distance()计算出两点之间的距离 一条线上的所有点&#xff0c;每2个点依次…

高德地图面积测量小工具

由于项目的需要&#xff0c;需要用到高德地图的测量面积功能&#xff0c;其实高德地图官方已经提供了测量面积的工具&#xff0c;但是感觉有点不太方便&#xff0c;于是在原来的测量面积工具的基础上进行一点补充&#xff0c;由于时间的原因&#xff0c;写的比较粗糙&#xff0…

国土面积测量问题

国土面积测量问题 如图所示一个国家的地图边界线&#xff0c;有关方面需确认国家的国土面积的数值。现在将地图放入平面直角坐标系中&#xff0c;以由西向东方向为 x 轴&#xff0c;由南向北方向为 y 轴&#xff0c;在边界上选取若干 点测出个点对应的坐标数据&#xff08;?,…

cesium面积测量

使用方法新建一个文件measureArea.js将下面代码贴上去 // DrawPolygon /* 绘制面(面积测量)*/class measureArea {constructor(arg) {this.objId Number(new Date().getTime() "" Number(Math.random() * 1000).toFixed(0));this.viewer arg.viewer;this.Cesium…

CAD快速测量面积与周长

这篇来教大家如何用CAD梦想画图快速测出规则图形与不规则图形的面积&#xff0c;首先这些图形都需要是封闭图形。 操作工具 操作系统&#xff1a;Windows10 CAD软件&#xff1a;CAD梦想画图 步骤 1.首先我们画一个规则的矩形与一个用多边形绘制的不规则封闭图形&#xff0…

openlayers测距和测面积

初次使用openlayers地图进行开发各种地图功能,测距和测面积使用的是官方例子并进行了一些简单的修改,官方示例在测量一次之后不能停止,修改后,每点击测量,只会测量一次。 而且需要注意的是,官方例子测距测面积使用的投影坐标系是3857。 js文件:measure.js import Draw…

手机测量面积的软件哪个好用?这些软件你值得收藏

相信大家在日常生活中总会遇到测量的问题&#xff0c;如果身边有测量工具还好&#xff0c;就怕恰好身边没有测量的工具&#xff0c;那就麻烦了。那有没有什么快捷的方法能够让我们迅速获得测量数据呢&#xff1f; 其实&#xff0c;现在市面上有很多面积测量软件&#xff0c;能…

cesium 实现测距离测面积 (划线画面 跟随鼠标位置 )

效果图 方法一&#xff1a;使用插件 cesium_measure.js 下载地址&#xff1a;https://github.com/zhangti0708/cesium-measure/blob/master/src/cesium-measure.js <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"&g…