p2p中的UDP穿透方法以及python實現


現在大部分的個人主機都是通過路由器連接外網,整個內網使用一個公共IP,由路由器進行內網IP、端口和外網IP、端口的映射(NAT)。

而這種映射方式只能由內網主動向外發送請求時才會建立,並通過映射出的該端口監聽返回消息。

根據映射方式的不同,建立映射的規則也不同,允許接收消息的范圍也不同。如(參考https://www.cnblogs.com/dyufei/p/7466924.html

  1、對稱 NAT(Symmetric NAT) 盡量會保持內網端口和映射端口相同。但是同一端口發出的指向不同外網服務器\端口的請求會映射不同的端口。

  2、錐形NAT(Cone NAT)同一內網主機/端口的消息都會映射到相同的端口

    2.1 完全錐形映射(Full Cone NAT ):映射的端口允許接收任意公網IP的消息

    2.2 限制錐形( Restricted Cone NAT):映射的端口只允許接收建立映射時對應的公網IP發送的消息

    2.3 端口限制錐形 NAT ( Port Restricted Cone NAT ):在2.2的基礎上增加了接收端口限制

在錐形NAT的情況下進行udp打洞是相對容易實現的,具體思路如下:

  1、需要一台作為中繼的公網服務器S

  2、假設有內網主機A、B,首先A主動向S發出請求,此時NAT建立映射(ip1,port1)->(ipa,porta)。然后B向S發出請求,建立(ip2,port2)->(ipb,portb)。

  3、此時服務器S擁有A、B的公網映射信息,並將A的信息發給B,B的信息發給A。

  4、如果是完全錐形映射,此時AB可以直接發送消息通信。但是另外兩種並不能行。

  5、此時A主動向B發送消息,A的NAT會建立A->B的映射,映射端口同樣是porta。該消息會被B的NAT阻攔,並不會被接收,但此時A已經向B打開了通道。

  6、此時再由B向A發送消息,打開B->A 的通道,由此A、B已經可以正常進行通訊

簡單demo如下

Sever.py

import socket
import _thread
import json
import time
udpSocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udpSocket.bind(("***.***.***.***", 40002))#注意如果使用雲服務器,此處應綁定雲服務器內網ip
add=[]#保存所用連接用戶
severPort=30001
def acc(sc):
    cont,destinf=sc.recvfrom(1024)
    print(cont.decode("utf-8"))
    print(destinf)
    jsn=json.loads(cont.decode("utf-8"))
    if(jsn["cod"]==1):#用戶連接
        if(len(add)>0):
            for x in add:
                print(x)
                jtt={"cod":2,"msg":destinf}
                sc.sendto(json.dumps(jtt).encode("utf-8"),x)#向所有用戶廣播新加入用戶的地址
            jtn={"cod":3,"msg":add}
            sc.sendto(json.dumps(jtn).encode('utf-8'),destinf)
        add.append(destinf)
    acc(sc)
_thread.start_new_thread(acc,(udpSocket,))


s=input("")
while(1==1):
    time.sleep(10)     

  

Client.py

import socket
import _thread
import json
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('www.baidu.com', 0))
ip = s.getsockname()[0]#獲取本機局域網IP
udpSocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udpSocket.bind((ip, 30001))#綁定端口
otherAddr=[]#保存其他用戶的地址信息
severAddr="***.***.***.***"
severPort=40002
def acc(sc):
    global otherAddr
    cont,destinf=sc.recvfrom(1024)
    jsn=json.loads(cont.decode("utf-8"))
    if(jsn["cod"]==2):#0打洞1初始連接2服務器向其他用戶廣播新加入地址3向新加入用戶通知已在線人員4用戶消息
        print(str(jsn['msg'])+"進入聊天")
        otherAddr.append(jsn['msg'])#把新加入用戶添加到otherAddr
        sc.sendto('{"cod":0}'.encode('utf-8'),tuple(jsn['msg']))#打洞
    if(jsn["cod"]==3):
        otherAddr=jsn['msg']
        print("進入聊天,其他人有"+str(jsn['msg']))
        for x in jsn['msg']:
            sc.sendto('{"cod":0}'.encode('utf-8'),tuple(x))#打洞
    if(jsn["cod"]==4):
        print(str(destinf)+":"+jsn['msg'])
    acc(sc)
_thread.start_new_thread(acc,(udpSocket,))
jsn={"cod":1,"msg":"a"}
udpSocket.sendto(json.dumps(jsn).encode("utf-8"),(severAddr,severPort))#向服務器第一次請求

while(1==1):
    s=input("")
    if(s!=""):
        jsn={"cod":4,"msg":s}
        for x in otherAddr:#向所有其他用戶廣播
            udpSocket.sendto(json.dumps(jsn).encode("utf-8"),(x[0],x[1]))

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM