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

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

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

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

(1)问题的由来:服务器

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

(2)动态ip具体分析:session

 

如图:并发

有一个私有网络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时会发生什么事情呢?socket

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

                                  (192.168.0.2:1234->129.208.12.38:2000)post

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

                                   (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不存在同样。
实现过程以下:
一、 S启动两个网络侦听,一个叫【主链接】侦听,一个叫【协助打洞】的侦听。
二、 A和B分别与S的【主链接】保持联系。
三、 当A须要和B创建直接的TCP链接时,首先链接S的【协助打洞】端口,并发送协助链接申请。同时在该端口号上启动侦听(保证net类型3也能成功)。注意因为要在相同的网络终端上绑定到不一样的套接字上,因此必须为这些套接字设置 SO_REUSEADDR 属性(即容许重用),不然侦听会失败。
四、 S的【协助打洞】链接收到A的申请后经过【主链接】通知B,并将A通过NAT-A转换后的公网IP地址和端口等信息告诉B。
五、 B收到S的链接通知后首先与S的【协助打洞】端口链接,随便发送一些数据后当即断开,这样作的目的是让S能知道B通过NAT-B转换后的公网IP和端口号。
六、 B尝试与A的通过NAT-A转换后的公网IP地址和端口进行connect(这就是所谓“打洞”),根据不一样的路由器会有不一样的结果,有些路由器在这个操做就能创建链接(例如我用的TPLink R402),大多数路由器对于不请自到的SYN请求包直接丢弃而致使connect失败,但NAT-A会纪录这次链接的源地址和端口号,为接下来真正的链接作好了准备,这就是所谓的打洞,即B向A打了一个洞,下次A就能直接链接到B刚才使用的端口号了。
七、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪之后经过与S的【主链接】回复消息“我已经准备好”,S在收到之后将B通过NAT-B转换后的公网IP和端口号告诉给A。
八、 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