65.9K
CodeProject 正在变化。 阅读更多。
Home

通用P2P架构、教程与示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (54投票s)

2004年3月9日

7分钟阅读

viewsIcon

270900

downloadIcon

12727

通用P2P架构、教程与示例,涵盖了P2P基本策略。完成本教程后,您将精通基本的P2P设计和编码。

本通用P2P架构教程由Planet API为您提供 – 搜索数千个ASPX / ASP.NET公共Web方法 / 公共Web服务、搜索引擎API、新奇API、B2B API、游戏API、P2P API、趣味API、AI聊天机器人API等。

P2P文化概述

P2P(Peer To Peer,点对点)是指多台计算机共同思考以实现共同目标。计算机程序使用较少的中心服务器,并依赖于一组计算机(例如Gnutella、分布式媒体流、基于DCC的IRC文件服务器网络等),这些程序往往被称为更具P2P特性。而许多终端用户与少量中心服务进行通信的计算机程序,则倾向于被称为P2P程度较低或非P2P。要充分理解和利用P2P技术,必须摆脱“我们的计算机程序必须由我们实际拥有的服务器统一来同步活动”的教条。相反,要从更具数字生活导向的角度来思考我们的计算机程序,将计算机软件分解到多台机器上,并且不使软件的任何单一部分成为集体目标的关键。

P2P哲学

“单个仆从不如单个服务器强大,但众多仆从的集合比任何单个服务器都强大”——丹尼尔·斯蒂芬·鲁尔。

例如,一家大型软件公司给每个员工分配非常少的责任。即使这意味着你可以在几天内完成一个月的编码工作,但对于整个公司来说,不过度依赖任何一个员工更有益,这使得公司拥有更强的整体稳定性和最终编写出比任何一个人都能完成的更大、更复杂的软件包。如果你的软件利用了同样的原则来获得更高的带宽和计算速度,那么它就更具P2P特性。

P2P基本术语

对等点或仆从(Peer or Servant)

一个既充当客户端又充当服务器的计算机程序,服务于整个P2P网络。

连接管理器(Connection Manager)

一个轻量级服务器应用程序,为进入P2P网络的应用程序提供一个起点。连接管理器在您的整体应用程序目标中参与度越低,您的应用程序就越P2P。您的应用程序越P2P,您自己硬件的压力就越小。

Sample image

简单P2P聊天示例

这个示例演示了一个非常简单但高度P2P的应用程序。这个示例由两个基本的P2P部分组成:一个连接管理器和一个仆从。连接管理器应该编译并执行一次。仆从应该被编译,并且其config.xml中的connectionmgr标签应该设置为运行连接管理器的计算机的IP地址或域名。将仆从的可执行文件和config.xml复制多份,并放置在多台计算机上。在不同的机器上执行每个仆从,它们将联系连接管理器来解析彼此的位置并相互组网。每个仆从将频繁询问连接管理器P2P网络中有谁,并保持自己的发布列表最新。当一个仆从离开网络时,一个更新的列表将被发布给所有其他仆从,它们将停止尝试与离开的仆从通信。

如果我只有一台电脑,我还能尝试这个吗?

是的。连接管理器为每个仆从分配一个新端口号,因此每个仆从都监听一个唯一的端口号。你可以在一台机器上运行仆从可执行文件任意多次。对于你的第一次测试,我建议先运行一次连接管理器,然后在与连接管理器相同的机器上运行几次仆从。然后在仆从窗口中聊天,以验证P2P网络已在你的计算机上构建。如果你有其他计算机可以使用,只需在其他计算机上执行仆从并聊天,以验证它们已成功加入P2P网络。

配置文件

  <config> 

将运行连接管理器的服务器IP地址填在此处。

   <connectionmgr>127.0.0.1</connectionmgr> 

除非您更改连接管理器所使用的端口,否则将其保留为85

   <port>85</port>

列出您希望该仆从忽略的IP地址或域名

  <bans>
    <ban>
    <target>1.1.1.1</target> 
    </ban>
    <ban>
     <target>fooUser234.fooISP23423.com</target> 
    </ban>
   </bans>
  </config>

“禁令”标签是做什么用的?

“禁令”标签允许每个仆从列出它们不希望从其获取数据的IP地址或域名。

Sample image

连接管理器

将一个对等点添加到连接管理器对P2P网络的了解中。

Private Sub p2p_ConnectionRequest(Index As Integer, ByVal requestID As Long)
    iPortMax = iPortMax + 1
    Dim a As Integer
    For a = 1 To p2p.UBound
        Dim istate As Integer
        istate = p2p(a).State
        If istate = 0 Or istate = 8 Then
            Call EnterCriticalSection(tCritSection)
            RemovePeerByOffset CStr(a)
            p2p(a).Close
            p2p(a).Accept requestID
            AddPeer p2p(a).RemoteHostIP, CStr(iPortMax), CStr(a)
            Call LeaveCriticalSection(tCritSection)
            Exit Sub
        End If
        DoEvents
    Next a
    DoEvents
    Dim i As Integer
    i = p2p.UBound
    Call EnterCriticalSection(tCritSection)
    Load p2p(i + 1)
    p2p(i + 1).Accept requestID
    AddPeer p2p(i + 1).RemoteHostIP, CStr(iPortMax), CStr(i + 1)
    Call LeaveCriticalSection(tCritSection)
End Sub 

一个仆从需要它所有对等点的列表。

Private Sub p2p_DataArrival(Index As Integer, ByVal bytesTotal As Long)
    Dim a As String
    If p2p(Index).State <> 7 Then p2p(Index).Close: Exit Sub
    p2p(Index).GetData a
    If a = "needs peer list" Then
        On Error GoTo exit_critical_list
        Call EnterCriticalSection(tCritSection)
        Dim pPersonalPeerDoc As MSXML2.DOMDocument
        Set pPersonalPeerDoc = New MSXML2.DOMDocument
        pPersonalPeerDoc.loadXML pDoc.xml
        pPersonalPeerDoc.selectSingleNode("./peers/peer[offset = '" &_
                                           Index & "']/me").Text = "TRUE"
        p2p(Index).SendData pPersonalPeerDoc.xml
exit_critical_list:
        On Error Resume Next
        Call LeaveCriticalSection(tCritSection)
    Else
        MsgBox Index & " sent: " & a & " to the connection manager"
    End If
End Sub 

一个仆从离开了网络。

Private Sub p2p_Close(Index As Integer)
        Call EnterCriticalSection(tCritSection)
            RemovePeerByOffset CStr(Index)
        Call LeaveCriticalSection(tCritSection)
End Sub

仆从

连接管理器有此仆从的最新对等点列表。

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Dim document As String
    If Winsock1.State <> 7 Then Winsock1.Close: Exit Sub
    Winsock1.GetData document
    pDoc1.loadXML document

    Dim pPeerList As MSXML2.IXMLDOMNodeList
    Set pPeerList = pDoc1.selectNodes("./peers/peer/port")
    userList1.Clear
    Dim i As Integer
    For i = 0 To pPeerList.length - 1
        If pPeerList(i).Text = _
          pDoc1.selectSingleNode("./peers/peer[me = 'TRUE']/port").Text Then
            userList1.AddItem "*" & pPeerList(i).Text
        Else
            userList1.AddItem pPeerList(i).Text
        End If
    Next
    servants1(0).Close
    servants1(0).LocalPort = _
      CInt(pDoc1.selectSingleNode("./peers/peer[me = 'TRUE']/port").Text)
    servants1(0).Listen
End Sub

此仆从正在连接到所有对等点并向它们发布一些数据。

Private Sub txtSend1_KeyPress(KeyAscii As Integer)
On Error Resume Next
 If KeyAscii = 13 Then
  iSendsLeft1 = pDoc1.selectNodes("./peers/peer").length
  Dim i As Integer
  For i = 0 To pDoc1.selectNodes("./peers/peer").length - 1
   Dim iIp As String
   Dim iPort As Integer
   iIp = _
    pDoc1.selectNodes("./peers/peer").Item(i).selectSingleNode("./ip").Text
   iPort = _
    CInt(pDoc1.selectNodes("./peers/peer").Item(i).selectSingleNode("./port").Text)
   Dim strState As String
   While send1.State = 6
       DoEvents
   Wend
   send1.Close
   send1.Connect iIp, iPort
  Next
 End If
 DoEvents
End Sub

此仆从的一个对等点想要连接。

Private Sub servants1_ConnectionRequest(Index As Integer,_
                                          ByVal requestID As Long)
    Dim remoteip As String
    Dim remoteaddy As String
    remoteip = servants1(Index).RemoteHostIP
    remoteaddy = servants1(Index).RemoteHost
    If (pConfig.selectNodes("./config/bans/ban[target = '"_
      & remoteip & "']").length = 0) _
      And (pConfig.selectNodes("./config/bans/ban[target = '" _
      & remoteaddy & "']").length = 0) Then
        Dim a As Integer
        For a = 1 To servants1.UBound
            If servants1(a).State = 0 Or servants1(a).State = 8 Then
                Call EnterCriticalSection(tCritSection)
                servants1(a).Close
                servants1(a).Accept requestID
                Call LeaveCriticalSection(tCritSection)
                Exit Sub
            End If
            DoEvents
        Next a
        DoEvents
        Call EnterCriticalSection(tCritSection)
        Dim i As Integer
        i = servants1.UBound
        Load servants1(i + 1)
        servants1(i + 1).Accept requestID
        Call LeaveCriticalSection(tCritSection)
    End If
End Sub

此仆从的一个对等点有一些数据给它。

Private Sub servants1_DataArrival(Index As Integer,_
                                   ByVal bytesTotal As Long)
On Error Resume Next
    Dim a As String
    If servants1(Index).State <> 7 Then servants1(Index).Close: Exit Sub
    servants1(Index).GetData a
    txtChat1.Text = txtChat1.Text & vbCrLf & a
End Sub

为什么我的P2P仆从在路由器后面无法与互联网上的其他仆从通信?

一些路由器具有默认的通信限制,称为“防火墙”。这些限制旨在防止病毒滥用您的计算机,并强制您在需要更多互联网访问时显式禁用它们。最常见的损害P2P网络的限制之一是路由器默认阻止大多数出站端口。您可以通过以下方式测试您的路由器是否正在阻止某个端口:

  1. 在路由器后面的计算机上运行一个连接管理器副本。
  2. 在路由器外的一台电脑上,打开DOS框,输入“telnet <路由器IP地址> 85”。
  3. 防火墙后面的计算机上的连接管理器应该显示
    <peers>
        <peer>
            <ip>ip of peer</ip>
            <port>2224</port>
            <offset>1</offset>
            <me>FALSE</me>
        </peer>
    </peers>

如果不是,那么您需要在路由器中为连接管理器启用端口85。对于每个带有内置防火墙的路由器后面的对等点,也必须这样做。

只要您所有的对等点和连接管理器都在您的防火墙后面,您仍然可以在您的个人网络(路由器后面)上利用P2P技术。每个对等点必须配置路由器分配给连接管理器的IP地址。我强烈建议让路由器为连接管理器机器分配一个静态IP。这样,每次您重启运行连接管理器的盒子时,对等点就不必重新配置。然而,仆从每次启动时都可以从路由器获取一个动态IP地址,因为它们将使用连接管理器来解析彼此的位置。

但我确实需要路由器后面的仆从和互联网上的仆从都成为我的P2P网络的一部分。

这个话题超出了本文的范围,但简而言之,以下是其他P2P技术常用的一种解决方案

  1. 将您的连接管理器设置在旨在作为Web服务器的盒子上。如果您的连接管理器在路由器后面,则配置您的路由器以阻止所有入站端口,但让路由器将端口85连接转发到运行连接管理器的机器。
  2. 让每个仆从向连接管理器报告任何无法联系的对等点。让连接管理器通过查看可疑仆从是否发送另一个心跳来确定它们是否在端口阻塞路由器后面。如果大多数对等点抱怨无法连接到可疑仆从,但可疑仆从不断向连接管理器请求用户列表,那么连接管理器可以得出结论,该仆从可能在端口阻塞路由器后面。
  3. 如果连接管理器做出此判断,则应通知可疑仆从它需要从另一个对等点拉取数据。在可疑仆从实例的生命周期内,它会从另一个对等点拉取,该对等点会发布其获取的任何数据。所选择的对等点本身不能是路由器后面的可疑对等点。

这将降低P2P网络的速度,但允许位于端口阻塞路由器后面的对等点加入P2P网络。我建议连接管理器不要在其他对等点抱怨时就立即假定某个对等点可疑,而应该在收到来自其他每个对等点2或3次抱怨的阈值后,才告知其可疑并为其分配一个选定的对等点进行拉取。

Sample image

© . All rights reserved.