内网穿透你真的了解吗?

article/2025/11/11 1:03:56

前言

内网穿透作为程序员常用的调试手段之一,我们可以通过在个人电脑上运行花生壳或者 frp 等方式,让他人访问我们本地启动的服务,而且这种访问可以不受局域网的限制,当我们使用ngrok,frp等开源框架时,你是否有好奇过它神奇的作用?明明没有将服务部署到服务器,程序员们究竟是怎么通过这种特殊方式让所有人访问自己的主机的?本文将以frp开源框架为例,介绍内网穿透的原理。

公网 IP 与内网 IP

能否在公网中访问服务器的决定性因素:公网 IP

IP 地址的作用

众所周知, IP 地址是每一位使用互联网的网民都会拥有的标识, IP 地址在互联网中起到的作用是定位,通过 IP 地址我们可以精确的定位到所需资源所在的服务器,这是对于一般用户来讲的,而对于程序员而言,我们需要的则是让用户通过 IP 地址定位到我们部署的资源,既然每个互联网用户都拥有 IP 地址,为什么用户无法直接访问部署在个人PC上的服务呢?

事实上, IP 地址分为两种:公网 IP 和内网 IP

内网 IP : 内网 IP 是用户在使用局域网时,由局域网的网关所分配的 IP 地址,每一个内网 IP 实际上都可以映射到当前所在局域网网关的某一端口( IPV4 地址通过 NAT 与端口映射方式实现,具体原理下文详解),拥有内网 IP 可以被同一局域网下的其他设备所访问到;

公网 IP : 内网的设备想要访问非同一局域网下的资源则必须通过公网 IP ,公网 IP 是没有经过 NAT 转换的由互联网供应商(ISP)提供的最原始的 IP 地址,每一个公网 IP 都可以直接在互联网中被直接定位到。

一个最简单的例子(以前端开发为例)

当我们使用 webpack-dev-server 来启动一个 node 项目时,我们除了通过localhost:[端口号]的方式以外,与我们的开发设备处于同一局域网下的设备可以通过内网 IP :[端口号]的方式对我们的项目进行访问,但当我们使用自己的流量或者连接其他非当前开发设备所在局域网的设备使用内网 IP :[端口号]的方式进行进行访问时,则无法访问。

原因:

内网 IP 地址仅在当前局域网下可以被定位并访问到,而当我们想要跨局域网访问时,我们的访问请求则需要先映射为公网 IP 然后访问到另一局域网的公网 IP ,最后由另一局域网的网关将其映射到相应的局域网设备,但我们访问的地址属于局域网中的内网 IP ,因此无法定位到其相应的公网 IP

综上所述,当我们想要让处于其他局域网下的设备访问到我们本地资源,必不可缺的就是公网 IP

公网 IP 的稀有程度

相较于内网 IP ,公网 IP 明显比内网 IP 更加有用,为什么不可以人手一个公网 IP 呢?

IPV4和 IPV6

尽管 IPV6 的概念在几年前已经被提出,但实际的普及程度并没有很高,现在大部分网络用户使用的依旧是 IPV4 的 IP 地址,这也是限制公网 IP 个数的最大原因。

** IPV4:** IPV4 由 32 位二进制数组成,一共有 2^32 个不同的 IPV4 地址

** IPV6**: IPV6 由 128 位二进制数组成,理论上共有 2^128 个不同的 IPV6 地址

由此可见, IPV4地址的个数并不足以满足当前全世界网络用户的人手一个 IP 地址的需求,那么当前的网络为什么可以让这么多用户同时在网络上冲浪呢?

NAT(网络地址转换)技术

网络地址转化技术的核心作用在于实现对公网 IP 地址的复用,即所有的内网主机共用同一个 IP 地址,NAT 的实现方式共有三种:

  • 静态转换:将内网 IP 直接转换为公网 IP 地址,形成一一对应的方式

  • 动态转换:将内网 IP 地址转换为公网 IP 地址,与静态转换不同的是动态转换会在 IP 池中选择空闲 IP 地址进行转换,即每次同一个内网 IP 对应的公网 IP 会发生改变

  • 端口多路复用(PAT 技术):将内网 IP 与公网 IP 的某一端口进行映射,通过公网 IP 的某一端口访问公网

可以看出以上三种形式中端口多路复用(PAT)技术可以最大程度上缓解 IPV4 地址紧张的现状,也是最为广泛使用的实现方式,三种 NAT 实现方式共同点在于:对于内网用户来说自己对应的公网 IP 是不可知的,就好像我们可以知道自己的门牌号但无法知道自己所在的小区,因此无法准确告诉别人我们的具体地址。

内网穿透

在已知了当前内外网工作方式后,我们再来看一看作为程序员常用的技术手段内网穿透

在此之前或许很多人都曾使用过如花生壳、ngrok、frp等方式在没有服务器的情况下将一些服务部署到网络上让别人使用

那么内网穿透的原理究竟是怎么样的呢?

内网穿透原理解析

目前市面上主流的内网穿透工具实现的原理如下:

可见,内网穿透的核心原理在于将外网 IP 地址与内网 IP 地址建立联系,市面上常用的如花生壳工具其核心原理就是依靠一台具有公网 IP 的服务器作为请求的中转站以此来达到从公网访问内网主机的目的。

当我们启动花生壳的服务时,花生壳会将本地配置好的端口和服务器上的端口进行映射,告知服务器请求转发的路径,花生壳的公网服务器则会监听相应端口的请求,当用户访问花生壳提供的 IP 地址时,花生壳的对应 IP 地址的公网主机将会根据访问的端口映射到相应的内网主机,并通过预先配置好的服务端口将请求转发,以达到访问内网主机相应服务的效果。

更多C++后台开发技术点知识内容包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,音视频开发,Linux内核,TCP/IP,协程,DPDK多个高级知识点。

C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击领取

实现内网穿透

花生壳作为一款商业产品,对于配置端口等一系列工作进行了封装,使得用户可以更快捷的使用内网穿透,但我们在了解原理后完全可以通过一些开源的框架以及一台公网服务器实现对应的内网穿透功能,我们以 frp 为例。

如何搭建最简单的 frp 服务

服务端设置(frps.ini):
[common]
bind_port = 7000       //此处填写客户端监听的服务端端口号
vhost_http_port = 8080 //此处填写用户访问的端口号客户端配置(frpc.ini):
[common]
server_addr = x.x.x.x //此处填写服务端 IP 地址
server_port = 7000    //此处填写服务端配置的bind_port[web]
type = http         //此处规定转发请求的协议类型
local_port = 80     //此处规定本地服务启动的地址
custom_domains = www.example.com   //此处可以填写自定义域名(需要在 IP 地址下配置域名解析)

当我们配置完上述的文件后,用户的访问请求将会经过如下的步骤:

用户的请求将会经过域名解析,公网端口的转发以及内网主机的监听三个步骤成功将请求发送到对应的内网服务,当然 frp 相较于花生壳提供了更多的自定义配置项,此处不做详细讲解,有兴趣的读者可以访问:frp中文文档

当我们使用 frp 去配置我们自己的内网穿透服务时,我们可以使用一台服务器为大量的内网主机提供公网访问的功能,以此来实现公网 IP 的复用,其原理与上文提到的 PAT 端口多路复用技术相类似,当我们临时需要使用服务器时,只需要向拥有公网服务器的朋友申请两个闲置端口即可。

frp 核心代码解析

本文以 http 请求为例解析当一个公网请求发送到frp服务器后究竟会经过哪些步骤

frps 初始化

func runServer(cfg config.ServerCommonConf) (err error) {log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)if cfgFile != "" {log.Info("frps uses config file: %s", cfgFile)} else {log.Info("frps uses command line arguments for config")}// !important 核心代码1svr, err := server.NewService(cfg)if err != nil {return err}log.Info("frps started successfully")// !important 核心代码2svr.Run()return
}

在frp/cmd/frps/root.go中

  • 核心代码1: server.NewService() 方法对我们在frps中的配置进行解析,初始化frp服务端
  • 核心代码2: serever.Run() 方法启动frp服务

frpc 初始化

for{    // !important 核心代码3
conn, session, err := svr.login()if err != nil {xl.Warn("login to server failed: %v", err)// if login_fail_exit is true, just exit this program// otherwise sleep a while and try again to connect to serverif svr.cfg.LoginFailExit {return err}util.RandomSleep(10*time.Second, 0.9, 1.1)} else {// login success// !important 核心代码4ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)ctl.Run()svr.ctlMu.Lock()svr.ctl = ctlsvr.ctlMu.Unlock()break}
}

在frp/cmd/client/service.go中

  • 核心代码3: for 循环不断去发起和服务端的连接,失败后会再次发起
  • 核心代码4: 连接成功后,客户端会使用连接的信息调用 NewControl()

frpc 和 frps 通信

frps 发起连接

func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error) {xl := xlog.FromContextSafe(pxy.ctx)// try all connections from the poolfor i := 0; i < pxy.poolCount+1; i++ {// !important 核心代码5if workConn, err = pxy.getWorkConnFn(); err != nil {xl.Warn("failed to get work connection: %v", err)return}xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String())xl.Spawn().AppendPrefix(pxy.GetName())workConn = frpNet.NewContextConn(pxy.ctx, workConn)......// !important 核心代码6err := msg.WriteMsg(workConn, &msg.StartWorkConn{ProxyName: pxy.GetName(),SrcAddr:   srcAddr,SrcPort:   uint16(srcPort),DstAddr:   dstAddr,DstPort:   uint16(dstPort),Error:     "",})}
}

在frp/server/proxy.go中

  • 核心代码5: frps从多个连接中通过依次遍历的方式来获取第一个成功获取到的连接
  • 核心代码6:frps通过获取到的连接向 frpc 发出 &msg.StartWorkConn 的消息,告诉frpc建立连接的相应信息

frpc 响应连接

func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {// !important 核心代码7HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,conn, []byte(pxy.clientCfg.Token), m)
}

在frp/client/proxy/proxy.go中

  • 核心代码7:frpc接收到frps的信息后发起 TCP 连接

frps发送消息

func (ctl *Control) writer() {xl := ctl.xldefer func() {if err := recover(); err != nil {xl.Error("panic error: %v", err)xl.Error(string(debug.Stack()))}}()defer ctl.allShutdown.Start()defer ctl.writerShutdown.Done()encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))if err != nil {xl.Error("crypto new writer error: %v", err)ctl.allShutdown.Start()return}for {m, ok := <-ctl.sendChif !ok {xl.Info("control writer is closing")return}// !important 核心代码8if err := msg.WriteMsg(encWriter, m); err != nil {xl.Warn("write message to control connection error: %v", err)return}}
}

在frp/server/control.go中

  • 核心代码8: frps发送信息到 crypto.NewWriter() 创建的 writer 中

frpc 接收和响应

// !important 核心代码9
func (ctl *Control) reader() {xl := ctl.xldefer func() {if err := recover(); err != nil {xl.Error("panic error: %v", err)xl.Error(string(debug.Stack()))}}()defer ctl.readerShutdown.Done()defer close(ctl.closedCh)encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))for {m, err := msg.ReadMsg(encReader)if err != nil {if err == io.EOF {xl.Debug("read from control connection EOF")return}xl.Warn("read error: %v", err)ctl.conn.Close()return}ctl.readCh <- m}
}
  • 核心代码9: frpc 读取 frps 转发的信息

到这里,我们的 frps 已经成功将公网中接收到的请求转发到 frpc 相应的端口了,这就是一个最简单的请求通过 frp 进行代理转发的流程。

总结

本文所介绍的内网穿透技术相关的实现方式其实在我们的日常开发生活中有更多的使用场景,当我们深入了解了当前 IP 地址以及内外网的实现方式后,我们不难发现,当我们将内网穿透的图片稍加修改后就成为了我们常用的另一种功能的实现方式(VPN实现原理):

原文作者:内网穿透你真的了解吗? - 掘金


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

相关文章

Ngrok内网穿透

Ngrok一款内网穿透工具&#xff0c;它可以将本地部署的Web应用能够让公网环境直接访问到。ngrok是一个反向代理&#xff0c;通过在公共端点和本地运行的Web服务器之间建立一个安全的通道&#xff0c;实现内网主机的服务可以暴露给外网。所以它提供了一个能够在公网安全访问内网…

大白话告诉你内网穿透原理和验证

内网穿透到底干啥的这里不做详述&#xff0c;这篇文章主要是讲这玩意怎么做的 分析 网上有很多原理图&#xff0c;但是说实话鄙人天资愚钝没咋看懂&#xff0c;在网上疯狂百度和个人的验证之后&#xff0c;得出如下简图 其实很简单 想让我们内网的服务能被外网访问到&#x…

frp内网穿透原理及配置应用

1 Frp介绍 frp 是一个专注于内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 frp 项目官网 https://github.com/fatedier/frp 中文文档 https://github.com/fat…

内网穿透原理,以及用花生壳实现内网穿透

在做自学项目部署nginx的时候&#xff0c;nginx部署在阿里云服务器上&#xff0c;项目在本地&#xff0c;阿里云的nginx无法访问到本地的ip&#xff0c;所以需要做一下内网穿透。记录一下 什么是内网穿透&#xff1f; 内网和外网的概念&#xff1a; 内网&#xff1a;即所说的局…

内网穿透原理和实现

什么是内网穿透 作者&#xff1a;匿名用户 链接&#xff1a;https://www.zhihu.com/question/63098230/answer/1929852198 希望对你们更易懂要知道这个的意思&#xff0c;需要先来点前菜 1、地球上的电脑设备等要实现通信&#xff0c;都需要有个IP地址才可以实现。 2、IP地址…

内网穿透原理和实现思路介绍

内网穿透技术&#xff0c;简单理解就是将内网的服务映射到公网中&#xff0c;这样可以随时随地访问。 那什么是内网&#xff1f;什么是公网&#xff1f;先从这个基本概念来简单介绍吧 1. 公网、内网和外网 首先声明&#xff0c;公网、内网和外网这几个概念&#xff0c;不是专…

内网穿透工具frp原理和使用教程

内网穿透&#xff08;Port Forwarding&#xff09;是将公网上的IP地址映射到内部网络中的一台计算机的某个端口上&#xff0c;以便外部网络可以访问该计算机中运行的应用程序。内网穿透技术可以通过一些开源工具来实现&#xff0c;其中比较常用的是frp。在本文中&#xff0c;我…

内网穿透原理

前言 内网穿透一般在家庭用户下NAS设备在经常折腾&#xff0c;实际上企业云服务就是典型的内网穿透&#xff0c;只不过方式原理有点不同&#xff0c;家庭网络因为宽带的上传限制和没有公网IP&#xff0c;难度大于企业宽带。实际上内网穿透就是反向代理&#xff0c;域名解析&am…

内网穿透的实现和原理解析

需求场景: 基于微信平台开发服务号&#xff0c;本地移动端测试时&#xff0c;需要在微信平台注册测试号&#xff0c;然后填写接口配置信息&#xff0c;此信息需要你有自己的服务器资源&#xff0c;填写的URL需要正确响应微信发送的Token验证。如何能让外网访问到本地服务器呢&…

MacBook M1 Idea集成SVN

1.检查是否安装SVN svn —version2.检查是否安装了brew brew -v3.安装 brew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)” speed4.记住安装路径 5.配置国内镜像源HOMEBREW BOTTLE 6.svn安装前需要执行 按照/usr/local路径…

IntelliJ IDEA上svn分支管理和使用

IntelliJ IDEA上svn分支管理和使用 从Subversion下载trunk下的代码 选择项目创建分支 右键 Subversion --> branch or Tag … 选择Repository Location:需要创建的项目 选择Any Location 分支的位置和名字 详细查看截图 切换到分支 选择项目右键Subversion --> Update …

vscode / idea 使用SVN及查看历史记录

一、vscode使用 SVN 1、在vscode插件中心搜索svn 进行安装&#xff08;我这里已经安装完毕了&#xff09; 注意&#xff1a;插件安装成功&#xff0c;需要重新启动软件。 安装成功后会出现如下的图标&#xff1a; 2、桌面右键使用SVN Checkout… 来检出远程库的代码&#xf…

IDEA之配置SVN

最近在公司的开发项目过程中&#xff0c;发现有关idea2020的svn配置方法都参差不齐&#xff0c;所以整理了有关idea2020整合svn的步骤&#xff1b; 目录 一、SVN配置1.1 下载SVN1.2 安装SVN1.3 配置SVN 二、常见问题2.1 Warning:java: 源值1.5已过时 一、SVN配置 1.1 下载SVN…

IDEA使用svn拉取多模块项目

如果没有安装过svn客户端,安装的时候需要选择安装第二个工具,如下图所示 安装小乌龟, 自行搜索, 注意点是需要选择安装第二个工具 因为默认是不安装的, 而这个组件是集成到IDEA ”必须的” . 如果是已经安装好的, 但是没有选这个的也是有办法的 办法一: 更改小乌龟 在卸载小乌龟…

idea 使用svn出现path to certificate

百度了一会&#xff0c;有说清缓存的&#xff0c;不能使用。 还有配置这个的&#xff0c;也不行。 我记得以前可以&#xff0c;后来发现你拉去代码不用在idea拉&#xff0c;能提交。只要你不选最外层项目。可能是我打开了两个idea&#xff0c;都用的这个链接&#xff0c;只是文…

IDEA SVN 代码合并

IDEA SVN 代码合并 一&#xff1a;安装Subversion 插件 打开settings 设置&#xff0c;搜索svn&#xff0c;点击install,安装成功后重启 二&#xff1a;导入svn项目 打开idea&#xff0c;左下角点击 Subversion, 选择第四个选项卡或者从 View -> Tool Windows -> Sub…

IDEA上使用svn插件

安装后会在安装目录下出现svn.exe字样有用&#xff01;&#xff01;&#xff01; 如果没有svn.exe 原因: 产生这个问题的原因是由于我们在安装svn时,默认缺少一项导致的,我们只需要再重新安装一下即可(不需要卸载) 双击安装文件后,选择Modify 2.勾选command line client tool…

Idea使用SVN常用操作

贴一个有弧度的代码 目录 Idea中配置 检出代码 subversion有许多选项 拉取(更新)/提交代码 ​ Show Diff对比代码, 提交代码注释项必填 查看历史提交 设置文件忽略 拉出新分支 将分支合并到主干 删除分支 更换SVN账号信息 svn结构: 一般习惯性在创建repository仓…

macOS系统升级后idea使用svn报错

昨天升级了 macOS Monterey 更新完后IntelliJ IDEA无法正常使用SVN 报Cannot run program “svn” (in directory “/XXXX/XXXX/XXXX/XXX”): error2&#xff01;,NO such file or directory 看了一下网上的解决方案 1.command line client中的参数&#xff0c;在代码的svn…

idea使用svn提交代码失败,报错E230001: Server SSL certificate verification failed: certificate issued

IDEA踩坑记一 问题描述 使用SVN提交代码失败&#xff0c;报错E230001: Server SSL certificate verification failed: certificate issued。 在网上搜索了一下&#xff0c;全是下面这样的回答&#xff1a; 实际上这个 ls 是查看目录内容的命令&#xff0c;执行之后会展示目录…