我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用Nodejs怎么实现内网穿透服务

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用Nodejs怎么实现内网穿透服务

这篇文章给大家介绍使用Nodejs怎么实现内网穿透服务,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

1. 局域网内代理

我们先来回顾上篇,如何实现一个局域网内的服务代理?因为这个非常简单,所以,直接上代码。

const net = require('net')const proxy = net.createServer(socket => {  const localServe = new net.Socket()  localServe.connect(5502, '192.168.31.130') // 局域网内的服务端口及ip。  socket.pipe(localServe).pipe(socket)})proxy.listen(80)

这就是一个非常简单的服务端代理,代码简单清晰明了,如果有疑问的话,估计就是管道(pipe)这里,简单说下。socket是一个全双工流,也就是既可读又可写的数据流。代码中,当socket接收到客户端数据的时候,它会把数据写入localSever,当localSever有数据的时候,它会把数据写入socket,socket再把数据发送给客户端。

2. 内网穿透

局域网代理简单,内网穿透就没这么简单了,但是,它却是核心的代码,需要在其上做相当的逻辑处理。具体实现之前,我们先梳理一下内网穿透。

什么是内网穿透?

简单来说,就是公网客户端,可以访问局域网内的服务。比如,本地启动的服务。公网客户端怎么会知道本地启的serve呢?这里必然要借助公网服务端。那么公网服务端又怎么知道本地服务呢?这就需要本地和服务端建立socket链接了。

四个角色

通过上面的描述,我们引出四个角色。

  1. 公网客户端,我们取名叫client。

  2. 公网服务端,因为有代理的作用,我们取名叫proxyServe。

  3. 本地服务,取名localServe。

  4. 本地与服务端的socket长连接,它是proxyServe与localServe之前的桥梁,负责数据的中转,我们取名叫bridge。

其中,client和localServe不需要我们关心,因为client可以是浏览器或者其它,localServe就是一个普通的本地服务。我们只需要关心proxyServe和bridge就可以了。我们这里介绍的依然是最简单的实现方式,提供一种思路与思考,那我们先从最简单的开始。

bridge

我们从四个角色一节知道, bridge是一个与proxyServe之间socket连接,且是数据的中转,上代码捋捋思路。

const net = require('net')const proxyServe = '10.253.107.245'const bridge = new net.Socket()bridge.connect(80, proxyServe, _ => {  bridge.write('GET /regester?key=sq HTTP/1.1\r\n\r\n')})bridge.on('data', data => {  const localServer = new net.Socket()  localServer.connect(8088, 'localhost', _ => {    localServer.write(data)    localServer.on('data', res => bridge.write(res))  })})

代码清晰可读,甚至朗朗上口。引入net库,声明公网地址,创建bridge,使bridge连接proxyServe,成功之后,向proxyServe注册本地服务,接着,bridge监听数据,有请求到达时,创建与本地服务的连接,成功之后,把请求数据发送给localServe,同时监听响应数据,把响应流写入到bridge。

其余没什么好解释的了,毕竟这只是示例代码。不过示例代码中有段/regester?key=sq,这个key可是有大作用的,在这里key=sq。那么角色client通过代理服务访问本地服务的是,需要在路径上加上这个key,proxyServe才能对应的上bridge,从而对应上localServe。

例如:lcoalServe是:http://localhost:8088 ,rpoxyServe是example.com ,注册的key是sq。那么要想通过prxoyServe访问到localServe,需要如下写法:example.com/sq 。为什么要这样写?当然只是一个定义而已,你读懂这篇文章的代码之后,可以修改这样的约定。

那么,且看以下关键代码:

proxyServe

这里的proxyServe虽然是一个简化后的示例代码,讲起来依然有些复杂,要想彻底弄懂,并结合自己的业务做成可用代码,是要下一番功夫的。这里我把代码拆分成一块一块,试着把它讲明白,我们给代码块取个名字,方便讲解。
代码块一:createServe

该块的主要功能是创建代理服务,与client和bridge建立socket链接,socket监听数据请求,在回调函数里做逻辑处理,具体代码如下:

const net = require('net')const bridges = {} // 当有bridge建立socket连接时,缓存在这里const clients = {} // 当有client建立socket连接时,缓存在这里,具体数据结构看源代码net.createServer(socket => {  socket.on('data', data => {    const request = data.toString()    const url = request.match(/.+ (?<url>.+) /)?.groups?.url        if (!url) return    if (isBridge(url)) {      regesterBridge(socket, url)      return    }    const { bridge, key } = findBridge(request, url)    if (!bridge) return    cacheClientRequest(bridge, key, socket, request, url)    sendRequestToBridgeByKey(key)  })}).listen(80)

看一下数据监听里的代码逻辑:

  1. 把请求数据转换成字符串。

  2. 从请求里查找URL,找不到URL直接结束本次请求。

  3. 通过URL判断是不是bridge,如果是,注册这个bridge,否者,认为是一个client请求。

  4. 查看client请求有没有已经注册过的bridge -- 记住,这是一个代理服务,没有已经注册的bridge,就认为请求无效。

  5. 缓存这次请求。

  6. 接着再把请求发送给bridge。

结合代码及逻辑梳理,应该能看得懂,但是,对5或许有疑问,接下来一一梳理。

代码块二:isBridge

判断是不是一个bridge的注册请求,这里写的很简单,不过,真实业务,或许可以定义更加确切的数据。

function isBridge (url) {  return url.startsWith('/regester?')}

代码块三:regesterBridge
简单,看代码再说明:

function regesterBridge (socket, url) {  const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key  bridges[key] = socket  socket.removeAllListeners('data')}
  1. 通过URL查找要注册的bridge的key。

  2. 把改socket连接缓存起来。

  3. 移除bridge的数据监听 -- 代码块一里每个socket都有默认的数据监听回调函说,如果不移除,会导致后续数据混乱。

代码块四:findBridge

逻辑走到代码块4的时候,说明这已经是一个client请求了,那么,需要先找到它对应的bridge,没有bridge,就需要先注册bridge,然后需要用户稍后再发起client请求。代码如下:

function findBridge (request, url) {  let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key  let bridge = bridges[key]  if (bridge) return { bridge, key }  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer  if (!referer) return {}  key = referer.split('//')[1].split('/')[1]  bridge = bridges[key]  if (bridge) return { bridge, key }  return {}}
  • 从URL中匹配出要代理的bridge的key,找到就返回对应的bridge及key。

  • 找不到再从请求头里的referer里找,找到就返回bridge及key。

  • 都找不到,我们知道在代码块一里会结束掉本次请求。

代码块五:cacheClientRequest

代码执行到这里,说明已经是一个client请求了,我们先把这个请求缓存起来,缓存的时候,我们一并把请求对应的bridge、key绑定一起缓存,方便后续操作。

为什么要缓存client请求?

在目前的方案里,我们希望请求和响应都是成对有序的。我们知道网络传输都是分片传输的,目前来看,如果我们不在应用层控制请求和响应成对且有序,会导致数据包之间的混乱现象。暂且这样,后续如果有更好方案,可以不在应用层强制控制数据的请求响应有序,可以信赖tcp/ip层。
讲完原因,我们先来看缓存代码,这里比较简单,复杂的在于逐个取出请求并有序返回整个响应。

function cacheClientRequest (bridge, key, socket, request, url) {  if (clients[key]) {    clients[key].requests.push({bridge, key, socket, request, url})  } else {    clients[key] = {}    clients[key].requests = [{bridge, key, socket, request, url}]  }}

我们先判断该bridge对应的key下是不是已经有client的请求缓存了,如果有,就push进去。

如果没有,我们就创建一个对象,把本次请求初始化进去。

接下来就是最复杂的,取出请求缓存,发送给bridge,监听bridge的响应,直到本次响应结束,在删除bridge的数据监听,再试着取出下一个请求,重复上面的动作,直到处理完client的所有请求。

代码块六:sendRequestToBridgeByKey

在代码块五的最后,对该块做了概括性的说明。可以先稍作理解,在看下面代码,因为代码里会有一些响应完整性的判断,去除这一些,代码就好理解一些。整个方案,我们没有对请求完整性进行处理,原因是,一个请求的基本都在一份数据包大小内,除非是文件上传接口,我们暂不处理,不然,代码又会复杂一些。

function sendRequestToBridgeByKey (key) {  const client = clients[key]  if (client.isSending) return  const requests = client.requests  if (requests.length <= 0) return  client.isSending = true  client.contentLength = 0  client.received = 0  const {bridge, socket, request, url} = requests.shift()  const newUrl = url.replace(key, '')  const newRequest = request.replace(url, newUrl)  bridge.write(newRequest)  bridge.on('data', data => {    const response = data.toString()    let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code    if (code) {      code = parseInt(code)      if (code === 200) {        let contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength        if (contentLength) {          contentLength = parseInt(contentLength)          client.contentLength = contentLength          client.received = Buffer.from(response.split('\r\n\r\n')[1]).length        }      } else {        socket.write(data)        client.isSending = false        bridge.removeAllListeners('data')        sendRequestToBridgeByKey(key)        return      }    } else {      client.received += data.length    }    socket.write(data)    if (client.contentLength <= client.received) {      client.isSending = false      bridge.removeAllListeners('data')      sendRequestToBridgeByKey(key)    }  })}

从clients里取出bridge key对应的client。
判断该client是不是有请求正在发送,如果有,结束执行。如果没有,继续。
判断该client下是否有请求,如果有,继续,没有,结束执行。
从队列中取出第一个,它包含请求的socket及缓存的bridge。
替换掉约定的数据,把最终的请求数据发送给bridge。
监听bridge的数据响应。

  • 获取响应code

    • 如果响应是200,我们从中获取content length,如果有,我们对本次请求做一些初始化的操作。设置请求长度,设置已经发送的请求长度。

    • 如果不是200,我们把数据发送给client,并且结束本次请求,移除本次数据监听,递归调用sendRequestToBridgeByKey

  • 如果没有获取的code,我们认为本次响应非第一次,于是,把其长度累加到已发送字段上。

  • 我们接着发送该数据到client。

  • 再判断响应的长度是否和已经发送的过的数据长度一致,如果一致,设置client的数据发送状态为false,移除数据监听,递归调用递归调用sendRequestToBridgeByKey。

至此,核心代码逻辑已经全部结束。

总结

理解这套代码之后,就可以在其上做扩展,丰富代码,为你所用。理解完这套代码,你能想到,它还有哪些使用场景吗?是不是这个思路也可以用在远程控制上,如果你要控制客户端时,从这段代码找找,是不是会有灵感。
这套代码或许会有难点,可能要对tcp/ip所有了解,也需要对http有所了解,并且知道一些关键的请求头,知道一些关键的响应信息,当然,对于http了解的越多越好。
如果有什么需要交流,欢迎留言。

proxyServe源码

const net = require('net')const bridges = {}const clients = {}net.createServer(socket => {  socket.on('data', data => {    const request = data.toString()    const url = request.match(/.+ (?<url>.+) /)?.groups?.url        if (!url) return    if (isBridge(url)) {      regesterBridge(socket, url)      return    }    const { bridge, key } = findBridge(request, url)    if (!bridge) return    cacheClientRequest(bridge, key, socket, request, url)    sendRequestToBridgeByKey(key)  })}).listen(80)function isBridge (url) {  return url.startsWith('/regester?')}function regesterBridge (socket, url) {  const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key  bridges[key] = socket  socket.removeAllListeners('data')}function findBridge (request, url) {  let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key  let bridge = bridges[key]  if (bridge) return { bridge, key }  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer  if (!referer) return {}  key = referer.split('//')[1].split('/')[1]  bridge = bridges[key]  if (bridge) return { bridge, key }  return {}}function cacheClientRequest (bridge, key, socket, request, url) {  if (clients[key]) {    clients[key].requests.push({bridge, key, socket, request, url})  } else {    clients[key] = {}    clients[key].requests = [{bridge, key, socket, request, url}]  }}function sendRequestToBridgeByKey (key) {  const client = clients[key]  if (client.isSending) return  const requests = client.requests  if (requests.length <= 0) return  client.isSending = true  client.contentLength = 0  client.received = 0  const {bridge, socket, request, url} = requests.shift()  const newUrl = url.replace(key, '')  const newRequest = request.replace(url, newUrl)  bridge.write(newRequest)  bridge.on('data', data => {    const response = data.toString()    let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code    if (code) {      code = parseInt(code)      if (code === 200) {        let contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength        if (contentLength) {          contentLength = parseInt(contentLength)          client.contentLength = contentLength          client.received = Buffer.from(response.split('\r\n\r\n')[1]).length        }      } else {        socket.write(data)        client.isSending = false        bridge.removeAllListeners('data')        sendRequestToBridgeByKey(key)        return      }    } else {      client.received += data.length    }    socket.write(data)    if (client.contentLength <= client.received) {      client.isSending = false      bridge.removeAllListeners('data')      sendRequestToBridgeByKey(key)    }  })}

关于使用Nodejs怎么实现内网穿透服务就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用Nodejs怎么实现内网穿透服务

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用Nodejs怎么实现内网穿透服务

这篇文章给大家介绍使用Nodejs怎么实现内网穿透服务,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1. 局域网内代理我们先来回顾上篇,如何实现一个局域网内的服务代理?因为这个非常简单,所以,直接上代码。const n
2023-06-15

怎么使用云服务器实现内网穿透

使用云服务器实现内网穿透可能需要您提供内网IP地址和端口号,以便管理员可以在云主机上创建私有网络。内网穿透的过程可以大致如下:确定需要穿透的内网地址和端口号:在云服务器上安装相应的客户端程序和脚本,例如:https://ycloud.cloudflare.com/your-host.aspx等,这些脚本将用于创建私有网络。在云服务器上创建私有网络:使用以下命令创建一个私有网络,并配置IP地
2023-10-26

怎么使用云服务器实现内网穿透服务

使用云服务器实现内网穿透服务的具体步骤如下:选择适当的云服务器:选择一款适合您需要服务的云服务器。有多种云服务器供您选择,包括AmazonWebServices(AMS)、AWS、GoogleCloud等。注册并获取许可证:按照许可证要求在云服务器上注册ID。使用服务:使用云服务器提供的服务。配置服务:配置云服务器的功能和配置。可以使用命令行工具(如mkdir命令、cd命令、pubco
2023-10-26

怎么使用云服务器实现内网穿透功能

1.什么是内网穿透内网穿透是指通过互联网将内网中的服务映射到公网上,使得外网用户可以访问内网中的服务。在实际应用中,内网穿透可以用于远程控制、文件共享、Web服务器等场景。2.云服务器实现内网穿透的原理云服务器可以作为内网穿透的中转站,将公网请求转发到内网中的服务。具体实现方式如下:在云服务器上安装内网穿透工具,如Ngrok、Frp等。在内网中的服务所在的机器上安装客户端,并将客户端
2023-10-26

怎么使用云服务器实现内网穿透连接

使用云服务器实现内网穿透连接的方式有很多种,以下是其中一种常用的方法:内网IP地址切换:可以使用系统虚拟IP或本地网络地址(LAN地址)来实现内网穿透连接。切换后,可以使用云服务器提供的远程管理功能或其他方式进行管理。使用Ping功能:可以在云服务器中配置一个PingID,可以帮助管理员实时检测内网连接。管理员可以配置Ping命令,例如使用"ping123",来测试内网连接是否正常。测试过
2023-10-26

怎么使用云服务器实现内网穿透连接服务

使用云服务器实现内网穿透连接服务是一种常见的网络连接方式,可以帮助实现内网访问外网。以下是使用云服务器实现内网穿透连接服务的步骤和方法:步骤:配置服务器端的虚拟防火墙:在客户端和云服务器之间安装云服务器提供的虚拟防火墙,可以根据虚拟防火墙配置的规则进行访问限制。在虚拟防火墙上添加虚拟访问控制:在客户端和云服务器之间安装云服务器提供的虚拟访问控制,可以根据虚拟访问控制规则,只允许内网客户端
2023-10-26

怎么使用云服务器实现内网穿透服务功能

使用云服务器实现内网穿透服务功能是一种常见的网络攻击手段,它可以通过对内网终端电脑进行远程操控,绕过防火墙、网络防护等安全保护措施,进入目标内网计算机系统并获取用户敏感信息或者执行恶意操作。以下是具体的步骤和建议:步骤:选择云服务器供应商:首先,应选择信誉较好、可靠的云服务器供应商,确保其提供的服务稳定、安全、可靠。注册账号并连接服务器:在云服务器供应商的网站或联系他们的客服获取注册账号
2023-10-26

怎么使用云服务器实现内网穿透功能呢

使用云服务器实现内网穿透功能是一个非常流行的技术,它可以帮助企业用户轻松地访问他们的内部网络,并对其数据进行加密。具体来说,使用云服务器实现内网穿透功能的方法如下:安装云服务器:首先需要将云服务器的管理界面安装到您的计算机上。可以将该操作系统路径设置为您的Windows操作系统或macOS系统的本地计算机,并将“云服务”选项更改为“已启用”。创建内网连接:接下来,您可以为云服务器添加内网
2023-10-26

利用云服务器实现内网穿透

利用云服务器实现内网穿透有许多潜在的好处,例如:保护敏感数据:当你的数据通过云服务器传输到公共云或其他第三方应用程序,你的敏感信息就会被加密或匿名化处理,以确保只有授权用户能够访问和操作。这样就可以确保数据不被未经授权的人访问,从而保护你的数据。减少延迟:通过使用云服务器,你可以减少你的客户端和云服务器之间的交互,从而减少云服务器的延迟。当你在云服务器的托管端运行客户端时,你可以将它们在客
2023-10-26

怎么使用云服务器实现内网穿透连接功能

使用云服务器实现内网穿透连接功能是一种常见的网络攻击手段,它可以通过对内网计算机系统进行远程操控,绕过防火墙、入侵检测系统或其他安全措施,使内部网络变成不安全网络。下面我们来看一下具体的操作步骤。打开云服务器管理界面。在界面上,我们可以看到“内网穿透连接”选项,点击它,进入内网穿透连接设置界面。在这个界面中,我们可以选择“连接内网服务器或外网服务器”,设置好服务器的IP、端口和其他选项
2023-10-26

怎么使用云服务器实现内网穿透功能设置

使用云服务器实现内网穿透功能,可以通过以下步骤来实现:安装云服务器:如果您使用的是公有云提供商(如AWS、GCP或其他)的云服务器,可以通过在控制台中进行“添加”和“更改设置”来完成内网穿透功能的安装。例如,您可以在AWS中,将“内网访问”选项添加为您的公共云提供商提供的选项,然后在GCP中进行“添加”或“更改设置”。在GCP中,您还可以添加“内网访问保护”选项作
2023-10-26

怎么配置frp服务器实现内网穿透

本篇内容主要讲解“怎么配置frp服务器实现内网穿透”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么配置frp服务器实现内网穿透”吧!  内网服务器ip地址:192.168.2.100 ,作为代
2022-12-16

怎么在Mac中使用网云穿内网穿透

这期内容当中小编将会给大家带来有关怎么在Mac中使用网云穿内网穿透,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。网云穿内网穿透Mac版本使用演示环境:Mac 10.13.6网云穿 1.01. 下载网云穿到
2023-06-03

怎么使用云服务器实现内网穿透连接服务功能

使用云服务器实现内网穿透连接服务功能需要具备以下条件:确保服务器已正确安装并且处于运行状态;网络环境已正确建设,且网络带宽足够;客户端和服务器操作系统和软件已正确安装,并正确配置;服务器操作系统和软件已正确设置,包括服务器账户、网络设置和操作系统设置;确保客户端和服务器操作系统和软件已正确运行,并已连接到服务器的网络连接;确保客户端和服务器操作系统和软件已正确安装和配置,包括客户端
2023-10-26

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录