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

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.