p2p和端口转发的python网络打洞实现

博客介绍了P2P和端口转发的实现。P2P连接受NAT网关限制,需第三方服务器辅助打洞,分别基于UDP和TCP实现,UDP更简单可靠,打洞成功后可实现异步聊天。端口转发给出了lcx的Python实现及用法,还提及花生壳内网穿透利用端口转发有带宽限制。

花生壳的内网穿透应该是利用的端口转发,所以才会有带宽等等限制,优点就是可以使用DDNS来访问,打洞效率高。P2P的优点就是点对点带宽只局限于链路而不是服务器限制,缺点就是打洞成功率一般。

实现

一、P2P的实现

分别基于UDP和TCP打洞实现P2P连接-Python

转载出处


受限制于NAT网关的特性,处于不同局域网内的客户端无法直接连接。即使知道了网关的公网ip和映射端口,任何“不请自来”的数据包都会被NAT网关丢弃,从而无法建立连接。因此需要依赖第三方服务器辅助双方“打洞”建立连接,一旦连接成功建立,即开启了真正的P2P通信,再无需服务器介入。

打洞原理为:首先client A和client B(为一对peer)分别向服务器发起请求,服务器记录下双方的公网ip和端口,将对方的地址返回给各自客户端,这样client A和client B都获得了对方的公网地址,接下来就可以尝试通过打洞互相连接了。之前说过,任何“不请自来”的请求都不被允许,因此client A和client B都应异步的向对方发送请求。假设client B首先向client A发送了一次请求,由于之前双方没有任何通信,显然该请求不会成功,但client B已经在它的网关上留下出站数据包(即打了一个洞),此时若client A向client B发送请求,该请求便可以通过client B的网关并成功与client B建立连接。

*注意:打洞能否成功依旧取决于NAT网关是否支持此类操作,并非100%成功。

具体实现方面,UDP比TCP要简单可靠一些,因为UDP通信并不基于实际的连接,即不留下确定的session,只要在发送数据包时指定接收者即可。我这里在客户端连接服务器时需要输入相同的口令以确定身份,在建立P2P连接时也通过服务器给的特定随机数签名验证身份,打洞成功后实现了一个简单的异步聊天功能。

UDP服务器端

import socket
import random

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', 11111))

#记录口令信息
peers = {
   
   }

while True:
    message, address = s.recvfrom(2048)
    message = message.decode()
    if not message.startswith('#connectChain*'):
        continue
    chain = message.replace('#connectChain*', '')
    if chain not in peers:
        peers[chain] = address
    else:
        print('matchedPeers: ', peers[chain], address)
        verifySignature = random.randint(10000, 99999)  #签名验证,用于peers双方验证身份
        #给双方发送peer地址信息和签名
        s.sendto(str([peers[chain], verifySignature]).encode(), address)
        s.sendto(str([address,verifySignature]).encode(), peers[chain])
        peers.pop(chain)

UDP客户端(client A和client B均使用一样的代码互相通信)

import socket
import threading
import time

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverAddress = ('ryanxin.cn', 11111)

#连接服务器
chain = input('连接口令:')
send = ('#connectChain*'+chain).encode()
s.sendto(send, serverAddress)
message = eval(s.recvfrom(2048)[0].decode())
myPeer = message[0]
signature = str(message[1])
print('got myPeer: ', myPeer)

peerConnected = False
#先连接myPeer,再互发消息
def sendToMyPeer():
    #发送包含签名的连接请求
    global peerConnected
    while True:
        s.sendto(signature.encode(), myPeer)
        if peerConnected:
            break
        time.sleep(1)
    
    #发送聊天信息
    while True:
        send_text = input("我方发送:")
        s.sendto(send_text.encode(), myPeer)

def recFromMyPeer():
    #接收请求并验证签名or接收聊天信息
    global peerConnected
    while True:
        message = s.recvfrom(2048)[0].decode()
        if message == signature:
            if not peerConnected:
                print('connected successfully')
            peerConnected = True
        elif peerConnected:
            print('\r对方回复:'+message+'\n我方发送:', end='')

sen_thread = threading.Thread(target=sendToMyPeer)
rec_thread = threading.Thread(target=recFromMyPeer)

sen_thread.start()
rec_thread.start()

sen_thread.join()
rec_thread.join()

TCP部分原理也是类似的,只不过需要先建立连接,这里我把双方的代码分开来写方便实现异步请求打洞。具体的,首先client B向client A发起连接请求(必定失败)尝试打洞,client A等待几秒后向client B正常发起连接请求(成功与否取决于NAT硬件)。

TCP服务器端

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 11111))

#记录口令信息
peers = {
   
   }

sock.listen(5)
while True:
    s, address = sock.accept()
    message = s.recv(2048
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值