[C# 网络编程系列]专题八:P2P编程
引言:
前面的介绍专题中有朋友向我留言说介绍下关于P2P相关的内容的,首先本人对于C#网络编程也不是什么大牛,因为能力的关系,也只能把自己的一些学习过程和自己的一些学习过程中的理解和大家分享下的,下面就进入正题——P2P(Peer to Peer)编程
一、P2P的介绍
首先,现在大家熟知的BT、电驴、迅雷、QQ、MSN和PPlive等都是基于P2P方式实现的软件,并且对等联网(Peer to Peer,P2P)将是互联网的发展方向,因此对于P2P技术的了解显得非常的重要,下面就来介绍下P2P架构:
在P2P技术之前,我们所有的网络应用都采用C/S或者B/S架构来实现的,然而在之前C/S架构的应用程序中,客户端软件向服务器发出请求,服务器然后对客户端请求做出响应,在这种情况下,如果客户端越多,此时服务器的压力就越大。然而采用P2P技术实现的每台计算机既是客户端,也是服务器,他们的功能都是对等的。对于安装了P2P软件(如迅雷,QQ等)的计算机加入一个共同的P2P网络,网络中的节点之间可以直接进行数据传输和通信。
1.1 P2P架构和C/S架构的比较
C/S架构有下面的缺点(其实上面的简单介绍中也讲到过):
1. 服务器负担过重。当大量用户访问C/S系统的服务器时,服务器常常会出现网络堵塞等现象,这时候,我们可能会通过增加投资提高服务器的硬件性能
2. 系统稳健性和服务器关联密切。指的是——如果服务器出现了问题时,整个系统的运行将会瘫痪(感觉是面向对象中经常强调的原则——低耦合原则)
然而P2P具有下面的特点:
1.对等模式
P2P系统中的客户端能够同时扮演客户端和服务器的角色,使两台计算机之间能够不通过服务器直接进行信息分享(QQ中当好友在线的时候发信息时,相信此时是不需要经过服务器转发的,只有当给离线好友发送消息时,此时应该会先把消息发送到服务器端存储起来,当好友再次登录的时候,会和服务器进行连接,服务器会进行判断是不是给这个用户的信息来决定是否转发,QQ软件的实现属于混合型P2P结构的, 这个会在后面的P2P系统分类中介绍。)
注:括号中都是我个人的一些理解,如果有什么说错的地方请大家及时更正我,这样我会及时的更新以免误导大家,谢谢大家监督。
2. 网络资源的分布式存储
在C/S架构中,所有客户端都直接从服务器下载所有数据资源,这样势必会加重服务器的负担,而P2P则改变了以服务器为中心的状态,使每个节点可以先从服务器上个下载一部分,然后再相互从对方或者其他节点下载其余部分。采用这种方式,当大量客户端同时下载时,就不会形成网络堵塞现象了。
1.2 P2P系统的分类
使用P2P技术的系统分为两类:(1)单纯型P2P——没有专用的服务器。安装了P2P软件的各个计算机可以直接通信
(2)混合型P2P——有专用的服务器,此时的服务器一般叫索引服务器,此服务器与C/S架构下的服务器不同,在C/S架构下所有资源都存储在服务器中,所有传递的信息都要经过服务器,而在混合型P2P系统中的索引服务器仅仅起到协调和扩展的功能,资源不是全部存储在服务器上,而是分布在各个电脑上,安装了P2P软件的电脑开始全部和索引服务器连接,以便告知自己监听的IP地址和端口号,然后再通过索引服务器告诉其他与自己连接的电脑,每台计算机的连接和断开都通过服务器通知网络上有联系的计算机,这样就减轻了每台计算机搜索其他计算机的负担,但是信息的传递还是通过点对点的方式来完成(这里可以以QQ为例,当我们电脑上安装了QQ这类P2P软件时,安装了QQ这类软件的计算机就会加入一个P2P网络,并且登陆的时候都会与索引服务器建立连接,通过连接来告诉服务器自己的IP地址和端口号,当我们找一个好友聊天时,此时自己的计算机和好友的计算机都会与服务器端口连接,但是要互相发送消息,自己的计算机必须知道好友计算机的IP地址和端口号才可以通信,这样的工作正是通过索引服务器来告知对方的--指的是告诉自己的计算机好友的计算机的IP地址和端口号,告诉好友的计算机自己的IP地址和端口号,这样双方就可以不通过服务器直接通信了。)
1.3 主流P2P应用分类
P2P 网络应用大致可以分为三类—— 1. 文件共享类,例如迅雷,BT等软件都是文件共享类的应用
2. 即时通信类,例如QQ,MSN等软件都是属于即时通信类
3. 多媒体传输类,例如在线视频直播软件,PPlive等软件
从上面的分类可以看出,现在网络上流行的软件都采用了P2P技术来实现的, 但是它们的实现肯定不是单纯的只采用P2P技术来实现的, 而是采用多种技术来实现的, 在下一专题中将介绍利用TCP,UDP和P2P等技术来实现类似QQ的一个即时通信程序,希望通过此程序可以综合前面专题介绍的内容以及帮助大家对QQ等软件的实现原理有了解。
二、P2P的基本原理
在前面我们对P2P的一些知识进行的简单的介绍, 通过前面的介绍相信大家对P2P的技术有了一定的了解,但是要自己开发一个P2P的应用当然必须了解P2P技术的实现原理的,下面就介绍下P2P实施的基本原理。
安装了P2P软件后,首先双方要进行通信,必须能够发现对方(指的就是知道对方的IP地址和端口号),一旦发现了对方后才可以进行通信,所以P2P应用程序一般分为发现、连接和通信3个阶段。 发现阶段负责动态定位通信方的网络位置;连接阶段负责在双方建立网络连接,通信阶段负责在双方之间传输数据。
2.1 发现阶段
一台计算机要和另外一台计算机通信,必须知道对方的IP地址和监听端口,否则就无法向对方发送消息。在之前的C/S架构中,服务器的IP地址一般固定不变的,并且提供服务的计算机域名也一般不会改变,所以为了方便客户端访问,一些Web服务器在DNS(DNS其实就是域名和IP地址的一个映射)中进行了注册,客户机可以利用域名解析机制将服务器域名解析为IP地址,然后在P2P应用中,各个对等节点(计算机或资源)可以随时加入和随时离开,并且对等节点的IP地址也不是固定的,所以不能采用DNS的机制来获取P2P架构中的对等节点的信息。
目前,在单纯型P2P中,针对如何发现对等节点,各种P2P技术采用的协议和标准都不一样,微软在.net 支持对等名称解析协议(Peer name Resolution Protocol, PNRP),该协议可以发现对等节点的信息,通过无服务器的解析功能将任何资源解析为一组IP地址和端口号,在后面的实现的简单程序用的就是这个协议来完成发现阶段的。
2.2 连接和通信阶段
完成对等节点的发现后,接下来就可以根据需要,选择TCP、UDP或者其他协议完成数据传输。如果选择TCP,则需要先建立连接,再利用该连接传输数据,关于TCP的内容可以查看我之前的专题;如果选择UDP,则无须建立连接,直接在对等节点之间通信就可以了。
三、.net平台对P2P编程的支持
之前在发现阶段也介绍了.Net平台对P2P编程的支持的,然后微软帮我们已经封装好了对PNRP协议的实现,这些类在System.NET.PeerToPeer命名空间里(微软现在很多东西都帮我们封装了,这样可以方便我们开发应用程序,感觉微软的做的东西都是这样,把程序的实现都帮我们做好了,我们开发程序的时候只要关注业务逻辑就好了的, 这样做当然有好处也有坏处的, 我觉得好处就是缩短软件的开发周期,让花更多的时间去实现软件真真的业务逻辑方面的东西,不好的地方就是现在的程序员就不能叫程序员,所以园子里面有很多人都称码农,这些我自己的观点了)
3.1 对等名称解析协议(PNRP)
PNRP可以完成对等名称的注册和解析(可以和DNS对比来理解)。
3.1.1 基本概念
第一个介绍的是对等名称的概念,我们将每一个网络资源(包括计算机,P2P应用程序、视频、Mp3或其他文档等资源)抽象为对等节点,对等节点名称当然就是对等节点的名称。对等节点名称简称为对等名,分为安全的和不安全的两种形式,不安全的名称仅由文本字符串组成,任何人都可以注册一个相同的不安全对等名称;安全的由一个公钥/私钥(代表唯一)对支持,所以使用PNRP注册时,不会受到欺骗,对等名称的格式如下:
Authority.Classifier
Authority的值取决于该名称的安全类型。对于不安全的类型,Authority为单字符“0”,而对于安全的对等名称,Authority由40个十六机制字符组成
Classifier是用户定义的用于标志对等节点的字符串,最大长度为150个Unicode字符。例如,对等名称0.PeerNametest1就是一个不安全的对等名称。
第二个概念就是云(Cloud)的概念,安装了相同P2P软件的计算机会加入一个共同的P2P网络中,才能相互识别各自拥有的资源并顺利进行P2P通信。微软PNPR协议将这个P2P网络称为“云”。云是指一组可以通过P2P网络相互识别的对等节点及其上资源的集合。云中的所有对等节点都可以解析注册到该云中的其他任何资源所在的位置(IP+Port),一个对等节点上的某个资源可以同时注册到多个云中。
PNPR目前使用了两种云——本地云和全局云。一个对等名称若注册到链接一本地云,就意味着只有同一本地网络上的其他对等节点可以解析该名称。而注册到全局云上的对等名称则允许IPv6互联网的任何对等节点解析。
注:全局云是基于IPv6协议的,并不支持IPv4,如果不存在IPv6地址,则不会出现全局云,也无法加入全局云。由于现在网上绝大多数应用使用的仍然是IPv4的地址,所以我们通常的P2P编程还用不到全局云,而只能使用默认的本地云。
3.1.2 名称注册
任何资源要被网络上的其他计算机识别到,首先必须注册进P2P网络,名称注册就是将包含对等节点信息的对等名称发布到云中,以便其他对等节点解析。一个资源如果注册到云中后,就可以被云中的其他对等节点解析和访问。
关于名称注册的具体内容,在后面的P2P的程序中也会使用的, 相信大家可以通过代码来进一步理解名称的注册,这里就先介绍到这里的
3.1.3 名称解析
名称解析是指利用对等名称获取到云中资源所在对等节点的IP地址和端口号的过程(和DNS解析原理一样)。PNPR名称解析仅能够注册到云中的其他对等节点资源,而不能发现自身注册的资源
PNPR协议没有使用索引服务器,所以为了完成解析,云中的每个对等节点都存储一些PNRP ID的缓存记录。PNPR缓存中的都含有PNPR ID和应用程序的IP地址和端口号。名称解析的步骤为——首先在本地计算机对等节点的缓存中查找目标资源,如果没有,则在缓存中的临近节点查看,这样循环下去,直到找到目标资源所在的对等节点位置。
3.2 PeerToPeer命名空间
上面主要介绍了PNPR协议的工作过程(相当于是理论部分了),下面就介绍下.net 为我们封装好PNPR的类的使用。这里就简单指明几个常用类的使用,并附上MSDN的链接,大家可以直接点链接进行查看详细内容,因为后面的P2P程序中也有具体的使用,所以这里就不一一列出来了。
这些类基本上从类名都可以大致知道他们的用途的,所以在这里就没有一一介绍的,只是附上了MSDN的链接。
四、实现P2P应用程序
以上介绍了那么多P2P的相关的知识,主要是为了实现一个自定义的P2P应用程序做准备的,这里就简单实现了资源发现的一个程序。
核心代码:
对等名称的注册代码:
- // 注册资源
- private void btnRegister_Click(object sender, EventArgs e)
- {
- if (tbxResourceName.Text == "")
- {
- MessageBox.Show("请输入发布的资源名!", "提示");
- return;
- }
- // 将资源名注册到云中
- // 具体资源名的结构在博客有介绍
- PeerName resourceName = new PeerName(tbxResourceName.Text, PeerNameType.Unsecured);
- // 用指定的名称和端口号初始化PeerNameRegistration类的实例
- resourceNameReg[seedCount] = new PeerNameRegistration(resourceName, int.Parse(tbxlocalport.Text));
- // 设置在云中注册的对等名对象的其他信息的注释
- resourceNameReg[seedCount].Comment =resourceName.ToString();
- // 设置PeerNameRegistration对象的应用程序定义的二进制数据
- resourceNameReg[seedCount].Data = Encoding.UTF8.GetBytes(string.Format("{0}", DateTime.Now.ToString()));
- // 在云中注册PeerName(对等名)
- resourceNameReg[seedCount].Start();
- seedCount++;
- comboxSharelist.Items.Add(resourceName.ToString());
- tbxResourceName.Text = "";
- }
// 注册资源
private void btnRegister_Click(object sender, EventArgs e)
{
if (tbxResourceName.Text == "")
{
MessageBox.Show("请输入发布的资源名!", "提示");
return;
} // 将资源名注册到云中
// 具体资源名的结构在博客有介绍
PeerName resourceName = new PeerName(tbxResourceName.Text, PeerNameType.Unsecured);
// 用指定的名称和端口号初始化PeerNameRegistration类的实例
resourceNameReg[seedCount] = new PeerNameRegistration(resourceName, int.Parse(tbxlocalport.Text));
// 设置在云中注册的对等名对象的其他信息的注释
resourceNameReg[seedCount].Comment =resourceName.ToString();
// 设置PeerNameRegistration对象的应用程序定义的二进制数据
resourceNameReg[seedCount].Data = Encoding.UTF8.GetBytes(string.Format("{0}", DateTime.Now.ToString()));
// 在云中注册PeerName(对等名)
resourceNameReg[seedCount].Start();
seedCount++;
comboxSharelist.Items.Add(resourceName.ToString());
tbxResourceName.Text = "";
}
名称解析代码(搜索资源):
- // 搜索资源
- private void btnSearch_Click(object sender, EventArgs e)
- {
- if (tbxSeed.Text == "")
- {
- MessageBox.Show("请先输入要寻找的种子资源名", "提示");
- return;
- }
- lstViewOnlinePeer.Items.Clear();
- // 初始化要搜索的资源名
- PeerName searchSeed = new PeerName("0." + tbxSeed.Text);
- // PeerNameResolver类是将节点名解析为PeerNameRecord的值(即将通过资源名来查找资源名所在的地址,包括IP地址和端口号)
- // PeerNameRecord用来定于云中的各个节点
- PeerNameResolver myresolver = new PeerNameResolver();
- // PeerNameRecordCollection表示PeerNameRecord元素的容器
- // Resolve方法是同步的完成解析
- // 使用同步方法可能会出现界面“假死”现象
- // 解决界面假死现象可以采用多线程或异步的方式
- // 关于多线程的知识可以参考本人博客中多线程系列我前面UDP编程中有所使用
- // 在这里就不列出多线程的使用了,朋友可以自己实现,如果有问题可以留言给我一起讨论
- PeerNameRecordCollection recordCollection = myresolver.Resolve(searchSeed);
- foreach (PeerNameRecord record in recordCollection)
- {
- foreach(IPEndPoint endpoint in record.EndPointCollection)
- {
- if (endpoint.AddressFamily.Equals(AddressFamily.InterNetwork))
- {
- ListViewItem item = new ListViewItem();
- item.SubItems.Add(endpoint.ToString());
- item.SubItems.Add(Encoding.UTF8.GetString(record.Data));
- lstViewOnlinePeer.Items.Add(item);
- }
- }
- }
- }
// 搜索资源
private void btnSearch_Click(object sender, EventArgs e)
{
if (tbxSeed.Text == "")
{
MessageBox.Show("请先输入要寻找的种子资源名", "提示");
return;
} lstViewOnlinePeer.Items.Clear();
// 初始化要搜索的资源名
PeerName searchSeed = new PeerName("0." + tbxSeed.Text);
// PeerNameResolver类是将节点名解析为PeerNameRecord的值(即将通过资源名来查找资源名所在的地址,包括IP地址和端口号)
// PeerNameRecord用来定于云中的各个节点
PeerNameResolver myresolver = new PeerNameResolver(); // PeerNameRecordCollection表示PeerNameRecord元素的容器
// Resolve方法是同步的完成解析
// 使用同步方法可能会出现界面“假死”现象
// 解决界面假死现象可以采用多线程或异步的方式
// 关于多线程的知识可以参考本人博客中多线程系列我前面UDP编程中有所使用
// 在这里就不列出多线程的使用了,朋友可以自己实现,如果有问题可以留言给我一起讨论
PeerNameRecordCollection recordCollection = myresolver.Resolve(searchSeed);
foreach (PeerNameRecord record in recordCollection)
{
foreach(IPEndPoint endpoint in record.EndPointCollection)
{
if (endpoint.AddressFamily.Equals(AddressFamily.InterNetwork))
{
ListViewItem item = new ListViewItem();
item.SubItems.Add(endpoint.ToString());
item.SubItems.Add(Encoding.UTF8.GetString(record.Data));
lstViewOnlinePeer.Items.Add(item);
}
}
}
}
运行结果截图:
为了演示资源发现的效果,所以同时开启了本程序的3个进程来模拟网络上对等的3个计算机节点,当在资源名中输入资源后会在分享下列表中显示出本地分享的资源,同时在P2P网络上的其他计算机可以通过资源名称搜索该资源,将得到的资源名称和发布时间显示在ListView控件中,下面是程序的运行结果:
五、总结
到这里P2P编程的介绍就结束了, 本专题只是简单演示了一个资源发现的程序,资源发现是P2P的核心技术,正是因为P2P技术实现了互联网范围的资源发现,才使得它被广泛应用,像我们经常用的下载工具——迅雷,迅雷就是典型采用P2P技术的应用程序,当我们在迅雷页面中输入“爱情公寓3”(相当于本专题中种子文本框填的资源名)然后点击搜索后迅雷会自动启动“狗狗搜索”并显示资源链接列表,当我们点击连接就可以进行下载了。不过迅雷肯定不是使用微软的PNPR,而是迅雷自主研发的与PNPR作用一样的协议——都是完成解析网络资源的地址的作用,当然,迅雷软件中也采用了其他的一些技术,如搜索引擎等。
希望本专题可以帮助大家对P2P技术有所了解,如果有任何的问题都可以通过留言的方式来一起讨论,在下一个专题中将介绍实现一个类似QQ的程序。
源码附上:资源发现程序
[C# 网络编程系列]专题八:P2P编程的更多相关文章
- [C# 网络编程系列]专题九:实现类似QQ的即时通信程序
转自:http://www.cnblogs.com/zhili/archive/2012/09/23/2666987.html 引言: 前面专题中介绍了UDP.TCP和P2P编程,并且通过一些小的示例 ...
- [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现
转自:http://www.cnblogs.com/zhili/archive/2012/09/03/2666974.html 上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享 ...
- [C# 网络编程系列]专题五:TCP编程
转自:http://www.cnblogs.com/zhili/archive/2012/08/25/2656840.html 前言 前面专题的例子都是基于应用层上的HTTP协议的介绍, 现在本专题来 ...
- 学习ASP.NET Core Razor 编程系列十八——并发解决方案
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- [C# 网络编程系列]专题三:自定义Web服务器
转自:http://www.cnblogs.com/zhili/archive/2012/08/23/2652460.html 前言: 经过前面的专题中对网络层协议和HTTP协议的简单介绍相信大家对网 ...
- [C#网络编程系列]专题一:网络协议简介
转自:http://www.cnblogs.com/zhili/archive/2012/08/11/NetWorkProgramming.html 因为这段时间都在研究C#网络编程的一些知识, 所以 ...
- [C# 网络编程系列]专题十:实现简单的邮件收发器
转自:http://www.cnblogs.com/zhili/archive/2012/09/24/2689892.html 引言: 在我们的平常工作中,邮件的发送和接收应该是我们经常要使用到的功能 ...
- [C# 网络编程系列]专题六:UDP编程
转自:http://www.cnblogs.com/zhili/archive/2012/09/01/2659167.html 引用: 前一个专题简单介绍了TCP编程的一些知识,UDP与TCP地位相当 ...
- [C# 网络编程系列]专题四:自定义Web浏览器
转自:http://www.cnblogs.com/zhili/archive/2012/08/24/WebBrowser.html 前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发 ...
随机推荐
- bzoj4548: 小奇的糖果 题解
题目链接 题解 不包含所有颜色 就强制不选一个颜色 图中圆点颜色相同 矩形越大,包括的点一定不比其一小部分少 如图所示,最大矩形只有3种 离散化\(x\)坐标 然后按\(y\)排序 每次取出颜色的前驱 ...
- Codeforces - 914F bitset 维护字符串匹配个数
题意:给你一个串,支持两种操作,1修改某个点的字符,2询问[l,r]内模式串P与原串的匹配个数 bitset的写法是真的6啊,简直是优雅暴力的典范 bs[i]表示\(T_i\)与\(P\)匹配与否, ...
- UVA - 11825 状压DP
该题目是EMAXX推荐的练习题,刘汝佳的书也有解说 如果S0属于全集,那S0就可以作为一个分组,那么S分组数可以是best{当前S中S0的补集+1} 对于集合类的题目我觉得有点抽象,希望多做多理解把 ...
- 自动判断文本文件编码来读取文本文件内容(.net版本和java版本)
.net版本 using System; using System.IO; using System.Text; namespace G2.Common { /// <summary> / ...
- layer.open中content里面的元素追加click事件,触发不了
[注意] 事件要追加在触发弹出事件的点击事件里面 $('#feedback').click(function(){ layer.open({ content:'<div><div c ...
- [转] docker基础知识之挂载本地目录
[From] https://blog.csdn.net/huludan/article/details/52641090 https://my.oschina.net/piorcn/blog/324 ...
- [转] Java8 日期/时间(Date Time)API指南
[From] http://www.importnew.com/14140.html Java 8日期/时间( Date/Time)API是开发人员最受追捧的变化之一,Java从一开始就没有对日期时间 ...
- 用leiningen来运行和打包clojure项目
今天是2017年5月24日.隔壁team用clojure写了个工具,我们要拿来用,于是弄了个leiningen来尝试.还没有最后成功,先记录一下一些连蒙带猜的步骤,以免忘记. 一.单独运行的cloju ...
- Oracle utl_file_dir参数详解
1 UTL_FILE_DIR参数定义 UTL_FILE_DIR是Oracle中的一个"静态参数",可以设置一个或多个路径.用于在PL/SQL中进行文件I/O操作(可以用utl_fi ...
- Tomcat(一)Tomcat常用配置
操作系统:win8 Jdk版本:1.7.0_51 Jdk目录:C:\Program Files\Java\jdk1.7.0_51 Tomcat版本:8.0.3 Tomcat目录:D:\Program ...