P2P 穿透内网,连接动态ip,内网ip打洞,p2p实现原理

2024-01-14 20:58

本文主要是介绍P2P 穿透内网,连接动态ip,内网ip打洞,p2p实现原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

网上找了很多,代码大堆,原理讲清楚透彻的不多。

本人找几篇讲得好的来整理一下。

一片技术文章,最主要的讲清楚原理,如果再有完整的能运行的源代码也可,关键是要把核心部分代码分析清楚。

(1)问题的由来:

大部分的电脑上网都是用动态ip地址。内网的ip是由net(路由、网关)分配的,net发出去的时候,映射到一个公网地址,这是一个动态计算的过程(特别是端口号),因此称为动态ip地址。外部网是无法直接访问内网计算机的,但在大部分情况下,借助一台有公网ip地址电脑(这里叫服务器),通过某种方式,可以实现连接,这种技术较“打洞”。

(2)动态ip具体分析:

 

如图:

有一个私有网络192.168.0.2,client a是其中的一台计算机,这个网络的网关natA(一个nat设备)的外网ip是202.103.142.29(应该还有一个内网的ip地址,比如10.0.0.10)。如果client a中的某个进程(这个进程创建了一个socket,这个socket绑定1234端口)想访问外网主机129.208.12.38的2000端口,那么当数据包通过nat时会发生什么事情呢?

先nat会改变这个数据包的原ip地址,改为202.103.142.29。接着nat会为这个传输创建一个session(session是一个抽象的概念,如果是tcp,也许session是由一个syn包开始,以一个fin包结束。而udp呢,以这个ip的这个端口的第一个udp开始,结束不确定,也许是几分钟,也许是几小时,这要看具体的实现了)并且给这个session分配一个端口,比如62000,然后改变这个数据包的源端口为62000。所以本来是

                                  (192.168.0.2:1234->129.208.12.38:2000)

的数据包到了互联网上变为了

                                   (202.103.142.29:62000->129.208.12.38:2000)。

一旦nat创建了一个session后,nat会记住62000端口对应的是192.168.0.2的1234端口,以后从129.208.12.38发送到62000端口的数据会被nat自动的转发到192.168.0.2上。(注意:这里是说129.208.12.38发送到62000端口的数据会被转发,其他的ip发送到这个端口的数据将被nat抛弃,这就是真正头痛的问题)这样client a就与server s1建立以了一个连接。

如果client a的原来那个socket(绑定了1234端口的那个udp socket)又接着向另外一个服务器server s2发送了一个udp包,那么这个udp包在通过nat时会怎么样呢?

答案是net决定的,不同的net有不同的答案。

这里需要介绍一下NAT的类型:
NAT设备的类型对于TCP穿越NAT,有着十分重要的影响,根据端口映射方式,NAT可分为如下4类,前3种NAT类型可统称为cone类型。
(1)全克隆( Full Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。任何一个外部主机均可通过该映射发送IP包到该内部主机。
(2)限制性克隆(Restricted Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。但是,只有当内部主机先给IP地址为X的外部主机发送IP包,该外部主机才能向该内部主机发送IP包。
(3)端口限制性克隆( Port Restricted Cone) :端口限制性克隆与限制性克隆类似,只是多了端口号的限制,即只有内部主机先向IP地址为X,端口号为P的外部主机发送1个IP包,该外部主机才能够把源端口号为P的IP包发送给该内部主机。
(4)对称式NAT ( Symmetric NAT) :这种类型的NAT与上述3种类型的不同,在于当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, NAT对该内部主机的映射会有所不同。对称式NAT不保证所有会话中的私有地址和公开IP之间绑定的一致性。相反,它为每个新的会话分配一个新的端口号。

第一种最为理想,基本就是无需打洞;

第四种最糟糕,根本就不能打洞。好消息就是这种net基本没有,很少。

所以关键是第二和第三种net类型。

因此打洞的本质就是利用net的特性“只有当内部主机先给IP地址为X的外部主机发送IP包,该外部主机才能向该内部主机发送IP包

(3)实现步骤(各个实现可能都不一样)

我们先假设一下:有一个服务器S在公网上有一个IP,两个私网分别由NAT-A和NAT-B连接到公网,NAT-A后面有一台客户端A,NAT-B 后面有一台客户端B,现在,我们需要借助S将A和B建立直接的TCP连接,即由B向A打一个洞,让A可以沿这个洞直接连接到B主机,就好像NAT-B不存在一样。
实现过程如下:
1、 S启动两个网络侦听,一个叫【主连接】侦听,一个叫【协助打洞】的侦听。
2、 A和B分别与S的【主连接】保持联系。
3、 当A需要和B建立直接的TCP连接时,首先连接S的【协助打洞】端口,并发送协助连接申请。同时在该端口号上启动侦听(保证net类型3也能成功)。注意由于要在相同的网络终端上绑定到不同的套接字上,所以必须为这些套接字设置 SO_REUSEADDR 属性(即允许重用),否则侦听会失败。
4、 S的【协助打洞】连接收到A的申请后通过【主连接】通知B,并将A经过NAT-A转换后的公网IP地址和端口等信息告诉B。
5、 B收到S的连接通知后首先与S的【协助打洞】端口连接,随便发送一些数据后立即断开,这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。
6、 B尝试与A的经过NAT-A转换后的公网IP地址和端口进行connect(这就是所谓“打洞”),根据不同的路由器会有不同的结果,有些路由器在这个操作就能建立连接(例如我用的TPLink R402),大多数路由器对于不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-A会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即B向A打了一个洞,下次A就能直接连接到B刚才使用的端口号了。
7、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪以后通过与S的【主连接】回复消息“我已经准备好”,S在收到以后将B经过NAT-B转换后的公网IP和端口号告诉给A。
8、 A收到S回复的B的公网IP和端口号等信息以后,开始连接到B公网IP和端口号,由于在步骤6中B曾经尝试连接过A的公网IP地址和端口,NAT-A纪录 了此次连接的信息,所以当A主动连接B时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的TCP连接建立起来了。

(4)让内网主机做服务器

以上的应用主要在于做p2p软件,如果我们想用家里的电脑,做服务器,是不需要这项技术(“打洞”)的,应为我们可以把路由器设置为net1型,可以直接设置端口映射:

但还是需要一个外网固定ip的服务器来负责通知其他客服端,因为拨号时得到的ip(这其实也是外网ip,就是其他的计算机可以直接访问你的)每次还是变化的。

(5)花生壳是什么

花生壳是可以把这个变化的ip映射为固定域名的域名服务商,如果不需要域名服务,只要用ip访问即可的应用(比如数据库),是用不上这个软件的。

但是还有一种办法,如果你已经有一台有静态ip的服务器,在上面部署一个代理服务器(比如nginx),然后把这个动态的ip通知它就可以了。


原文链接:http://www.cnblogs.com/eyye/archive/2012/10/23/2734807.html

这篇关于P2P 穿透内网,连接动态ip,内网ip打洞,p2p实现原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/606492

相关文章

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

Spring @Scheduled注解及工作原理

《Spring@Scheduled注解及工作原理》Spring的@Scheduled注解用于标记定时任务,无需额外库,需配置@EnableScheduling,设置fixedRate、fixedDe... 目录1.@Scheduled注解定义2.配置 @Scheduled2.1 开启定时任务支持2.2 创建

SpringBoot中使用Flux实现流式返回的方法小结

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连... 目录背景流式返回的核心概念与优势1. 提升用户体验2. 降低内存消耗3. 支持长连接与实时通信在Sp

Conda虚拟环境的复制和迁移的四种方法实现

《Conda虚拟环境的复制和迁移的四种方法实现》本文主要介绍了Conda虚拟环境的复制和迁移的四种方法实现,包括requirements.txt,environment.yml,conda-pack,... 目录在本机复制Conda虚拟环境相同操作系统之间复制环境方法一:requirements.txt方法

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3