p2p(peer-to-peer)即对等网络。p2p(peer to peer)网络结构区别于client/server结构或browser/server结构最显著的特点是整个网络不存在中心节点(或中心服务器),其中的每一个节点(peer)大都同时具有信息消费者、信息提供者和信息通讯等三方面的功能。
p2p(peer-to-peer)即对等网络。p2p(peer to peer)网络结构区别于client/server结构或browser/server结构最显著的特点是整个网络不存在中心节点(或中心服务器),其中的每一个节点(peer)大都同时具有信息消费者、信息提供者和信息通讯等三方面的功能。 nat(net address translation):网络地址转换,即局域网通常通过一个具有公网ip的代理网关服务器连到internet共享上网。局域往内的机器并不具有公网ip地址,只有内网ip地址,若要和internet上的http服务器通信,代理网关便会创建一个端口来和这个网内机器通信,并通过该端口和http服务器交换数据。最终,网内机器-->代理网关-->http服务器,在一个会话期间,各自的端口保持了映射关系,特别是代理网关和网内机器的端口映射,使得代理网关不会把接收到的数据包发错对象。局域网内的机器在网关处,就是靠nat来映射端口实现internet连接。因此,nat也称为端口映射。端口映射之后,在一个会话期间保持,对于tcp连接是直到连接断开才销毁,而对于udp,却存在一个不定的生存期。
如果两台机器a和b,分别处于两个局域网内,要通过internet通信,即为p2p连接通信。目前的internet使用ipv4,采用32位ip地址,主要被用来进行c/s形式的通信,需要共享的资源集中放于internet服务器上,ipv4对于p2p分布式资源共享的支持,极不友好。首先,32位ip地址已经不敷使用,公网ip地址日趋紧张,只能使用局域网共享公网ip的方式,局域网正是为了临时应对ip耗尽而出现的,长远的解决办法是研究ipv6。其次,分别处于两个局域网内的机器要通信,由于对方没有公网ip,直接呼叫对方是不可能的,必须借助第三方中介间接地通信,解决方法有如下几种:
1、实现局域网内的数据链路层协议,就是设计一个类时tcp/ip的协议,由它来代替tcp/ip协议,由它直接基于网卡硬件获取数据。这是十分复杂的。
2、依靠internet上的公网服务器中转数据,但对于大数据量的中转,显然受到服务器和网络的负载极限的限制。
3、依靠internet上的公网服务器做媒介,将这两台分别处于不同局域网的机器相互通知给对方,在它们建立连接之后,服务器即脱离关系。这种方式下,服务器把a的nat端口映射关系告诉b,又把b的nat端口映射关系告诉a,这样ab相互知道对方的端口映射关系之后,就能建立连接。因为a和b各自的端口映射关系是靠各自的代理网关动态建立的,动态建立的映射端口不得告知对方。
4、上面的第三种办法,也可以采用静态端口映射方式,这样就无需中介服务器对a和b做介绍。在各方自的代理网关上,可以在代理工具里将某个端口(如1002)和局域网内的某台机器(如内网ip为192.168.18.23,端口1003)做好静态映射,这样,代理网关会自动地将出入于1002端口的数据发往192.168.18.23的1003端口。当然,通信之前,必须对对方的端口映射关系做配置。有多少台网内机器要通信,就得映射多少个不同的端口,同时在另一个局域网内的机器就要做多少个配置。在局域网内搭建http、ftp等服务器就是通过静态映射端口来实现的,这个端口一般不是http、ftp的默认80和23,所以对这类站点的访问往往会在url里加上端口号。
由此所知,上述前两种办法在简单应用中是不可取的,只有后两种可行。它们又各有缺点,第三种动态映射端口,需要增加中介服务器,第四种静态映射端口,在需要通信的各方机器很多的情况下,做手工端口映射和配置都是很繁琐的,并且一方添加一台机器,就需要在其余对方增加配置。
采用动态和静态相结合的办法是可以推想的,然而其可行性还必须经过测试。可以这样设计,为了让所有通信机器彼此知晓并定位。我们可以在局域网里,只对一台机器在代理网关处做静态端口映射,本局域网内的机器都向它登记。而两个局域网各自只做一项对对方的映射配置。两个局域网之间,没有静态映射端口的机器要通信,就靠有映射的机器来担当“介绍”。
就局域网和nat的问题实际上还很多,比如各自的局域网的结构不同,局域网里可能又有子局域网,局域网可能是nat代理结构,但也可能是http代理,sock4、sock5代理等结构,nat又分严格的和非严格nat,严格nat限制很多,更不便于p2p。不过,软件不能实现的地方,可以考虑改变硬件结构,例如将严格nat变为非严格nat。如果硬件改变不得,那么internet整体上就有10%的系统不能实现p2p,除非等到正处于研发的ipv6协议出来。p2p要解决的唯一技术难题是如何发现、定位和寻址对方,就是如何穿透nat、http、sock等代理和如何穿透防火墙找到对方并建立起通信的问题。由于绝大多数局域网是nat代理结构,所以前面对nat论述比较详细,也是网上讨论最多的话题,相比之下,穿透http、sock代理就简单一些。此外,穿透nat发现对等点的办法还有一些,例如多播,但由于现有internet对多播并不友好,同时多播是无连接和不可靠的,其实现有难度。许多软件都是按照上述一些技术实现了p2p通信,著名的有msn、qq和bittorrent下载软件等。
我们不希望在ip层实现我们的p2p,而是希望在应用层,利用windows提供的socket建立p2p,至多下到用原始raw socket来写p2p。首先看,我们对于公网有服务器做“中介(非中转)”的p2p怎么实现。
原理讲述:
例如ab两台机器分别处于两个不同的局域网后,由server做中介,先看连接过程。
a首先连接服务器,采用udp发包给server,这个包包括了a的用户信息,类似于qq的qq号、呢称等。server方,可以用csocket::getpeername()得到a的ip及端口,但得到的ip和端口应该是a的代理网关的公网publicip及其映射端口natport,该映射端口就是a的代理网关为a的本次udp通信临时分配的nat端口。可以断言,得到的端口一定不是a的内网ip和内网udp端口。
服务器然后将a的公网ip、映射端口、用户信息等保存到(内存列表或者数据库),这样标志着a已经上线。服务器马上将其它在线的用户信息发回给a,包括其它用户的代理网关的公网ip及nat端口。a同样将在线用户的这些信息保存并显示为列表,期待a用户做出选择。
对于b,同样有上述的上线过程。
当a用户做出选择,要和在线的b用户通信时,a首先发udp包给b的公网ip及nat端口,并立即发一个udp包给服务器,让服务器去通知b,叫b给a也发一个udp包。换句话说,1、a发包给publicb, 2、a发包给server,3、server发包给publicb,4、b发包给publica。
上面的叙述用到了public字样,它代表代理网关的公网ip及其映射端口。
由于a和b各自的网关都保存了各自的端口映射关系,发到网关的数据,网关会按照这个映射关系转发给a和b。当a和b都分别收到对方发来的udp包以后,连接宣告成功,服务器即可以脱离,ab即可以用udp通信。
何以如此麻烦?
a在发udp包给server上线时,a的网关(a.gate)就分配一个nat端口(a.natport)给a,用于a和server间的本次udp会话,但a的网关明确标记,这个nat端口,仅能用于a和server之间的udp通信,不能挪着它用。并且,这个临时分配的端口,只能保持一个很短的时效,也许是一两秒吧。这个时间内,如果a与server没有任何通信,那么这个映射端口就宣告无效。下次,a和server又要通信时,a的网关又会重新分配一个新的端口。这段表明三点:
1、a与server的通信,需要a网关分配nat端口来中转。
2、nat端口只能用于a和server间的通信。
3、nat端口存在生存期,长时间a和server无通信,该端口即宣告无效。
就是这些麻烦,使得我们的连接过程必须绕很多弯。
a和b的通信,就是借助事先ab分别与server连接时,在各自的网关处建立的端口映射来通信。为避免上面的2、3点麻烦,a和b在初次连接时,必须几乎同时向对方发包。
如果a、b不同时发包给对方,它们各自的网关就会虑掉对方的包,因为该包不是server发来的包,叫做不请自来的包。
并且,即便ab各自的网关不虑掉非server发来的包,它们各自的nat端口也有一个时效。那么a与server,b与server就不得不发心跳包,以维持各自的映射端口,保证其不失效。
上面的过程中,如果a和b建立连接失败,可以循环这个过程,直到一个有限的次数之后,仍不能连接则宣告失败。