当前位置: 首页 > 知识库问答 >
问题:

Python3 NAT穿孔

师成弘
2023-03-14

我知道这个话题并不新鲜。虽然存在各种信息,但健壮的解决方案并没有出现(至少我没有找到)。我有一个用python3编写的P2P守护进程,pie上的最后一个元素是通过TCP连接NAT后面的两个客户端。我得此主题得参考资料:

https://bford.info/pub/net/P2pnat/

如何使两个客户机在连接了一个汇点服务器后直接连接?

TCP穿孔问题

到目前为止我所做的:

服务器:

#!/usr/bin/env python3

import threading
import socket

MY_AS_SERVER_PORT = 9001

TIMEOUT = 120.0
BUFFER_SIZE = 4096

def get_my_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        # doesn't even have to be reachable
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        s.close()
    return bytes(IP, encoding='utf-8')

def wait_for_msg(new_connection, client_address):
    while True:
        try:
            packet = new_connection.recv(BUFFER_SIZE)
            if packet:
                msg_from_client = packet.decode('utf-8')
                client_connected_from_ip = client_address[0]
                client_connected_from_port = client_address[1]

                print("We have a client. Client advertised his local IP as:", msg_from_client)
                print(f"Although, our connection is from: [{client_connected_from_ip}]:{client_connected_from_port}")

                msg_back = bytes("SERVER registered your data. Your local IP is: " + str(msg_from_client) + " You are connecting to the server FROM: " + str(client_connected_from_ip) + ":" + str(client_connected_from_port), encoding='utf-8')
                new_connection.sendall(msg_back)
                break

        except ConnectionResetError:
            break

        except OSError:
            break

def server():
    sock = socket.socket()

    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

    sock.bind((get_my_local_ip().decode('utf-8'), MY_AS_SERVER_PORT))
    sock.listen(8)
    sock.settimeout(TIMEOUT)
    while True:
        try:
            new_connection, client_address = sock.accept()

            if new_connection:
                threading.Thread(target=wait_for_msg, args=(new_connection,client_address,)).start()
#               print("connected!")
#               print("")
#               print(new_connection)
#               print("")
#               print(client_address)
                msg = bytes("Greetings! This message came from SERVER as message back!", encoding='utf-8')
                new_connection.sendall(msg)
        except socket.timeout:
            pass


if __name__ == '__main__':
    server()
#!/usr/bin/python3

import sys
import socket
import time
import threading

SERVER_IP = '1.2.3.4'
SERVER_PORT = 9001
# We don't want to establish a connection with a static port. Let the OS pick a random empty one.
#MY_AS_CLIENT_PORT = 8510

TIMEOUT = 3
BUFFER_SIZE = 4096

def get_my_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        # doesn't even have to be reachable
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        s.close()
    return bytes(IP, encoding='utf-8')

def constantly_try_to_connect(sock):
    while True:
        try:
            sock.connect((SERVER_IP, SERVER_PORT))
        except ConnectionRefusedError:
            print(f"Can't connect to the SERVER IP [{SERVER_IP}]:{SERVER_PORT} - does the server alive? Sleeping for a while...")
            time.sleep(1)
        except OSError:
            #print("Already connected to the server. Kill current session to reconnect...")
            pass

def client():
    sock = socket.socket()

    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

    #sock.bind((get_my_local_ip().decode('utf-8'), MY_AS_CLIENT_PORT))
    sock.settimeout(TIMEOUT)

    threading.Thread(target=constantly_try_to_connect, args=(sock,)).start()

    while True:
        try:
            packet = sock.recv(BUFFER_SIZE)

            if packet:
                print(packet)
                sock.sendall(get_my_local_ip())

        except OSError:
            pass

if __name__ == '__main__':
    client()

现在,当前代码的结果是:

./tcphole_server.py 
We have a client. Client advertised his local IP as: 10.10.10.50
Although, our connection is from: [89.22.11.50]:32928
We have a client. Client advertised his local IP as: 192.168.1.20
Although, our connection is from: [78.88.77.66]:51928

./tcphole_client1.py              
b'Greetings! This message came from SERVER as message back!'
b'SERVER registered your data. Your local IP is: 192.168.1.20 You are connecting to the server FROM: 89.22.11.50:32928'

./tcphole_client2.py             
b'Greetings! This message came from SERVER as message back!'
b'SERVER registered your data. Your local IP is: 10.10.10.50 You are connecting to the server FROM: 78.88.77.66:51928'

如您所见,服务器拥有连接两个客户端的所有信息。我们可以通过当前的服务器-客户端连接单独发送关于其他对等体的详细信息。

现在有两个问题仍萦绕在我的脑海中:

>

  • 假设服务器为每个对等体发送有关客户端1和客户端2的信息。现在客户端开始连接,如[89.22.11.50]:32928<>[78.88.77.66]:51928服务器是否应该关闭与客户端的当前连接?

    这比我想象的更混乱。感谢任何能帮助我们前进的人。希望这也能帮助其他开发/开发项目。

  • 共有1个答案

    施永宁
    2023-03-14

    终于找到了预期的行为!我不想在这里给出太多的代码,但我希望在此之后,您将理解如何实现它的基本知识。最好在客户端的每个文件夹中都有一个单独的文件--靠近./TCPHOLE_Client1.py和./TCPHOLE_Client2.py。我们需要在与服务器启动会话后快速连接。现在举个例子:

    ./tcphole_client_connector1.py 32928 51928
    
    ./tcphole_client_connector2.py 51928 32928
    

    记得吗?我们需要连接到与使用服务器启动的端口相同的端口:

    [89.22.11.50]:32928 <> [78.88.77.66]:51928
    

    需要第一个端口来绑定套接字(OUR)。使用第二个端口,我们正在尝试连接到客户端。另一个客户端执行相同的过程,只是它绑定到他的端口并连接到你的绑定端口。如果路由器仍有活动连接-成功。

     类似资料:
    • 我目前正在开发一款网络游戏。在游戏中,需要通过TCP和UDP将数据从服务器发送到客户端。实现UDP漏洞穿孔很容易,但我不太确定如何实现TCP漏洞穿孔: null 是3。或者是4。该怎么走?

    • 基础用法 Transfer 的数据通过 data 属性传入。数据需要是一个对象数组,每个对象有以下属性:key 为数据的唯一性标识,label 为显示文本,disabled 表示该项数据是否禁止转移。目标列表中的数据项会同步到绑定至 v-model 的变量,值为数据项的 key 所组成的数组。当然,如果希望在初始状态时目标列表不为空,可以像本例一样为 v-model 绑定的变量赋予一个初始值。 <

    • Transfer 穿梭框 基础用法 :::demo Transfer 的数据通过 data 属性传入。数据需要是一个对象数组,每个对象有以下属性:key 为数据的唯一性标识,label 为显示文本,disabled 表示该项数据是否禁止转移。目标列表中的数据项会同步到绑定至 value 的变量,值为数据项的 key 所组成的数组。当然,如果希望在初始状态时目标列表不为空,可以像本例一样为 valu

    • Transfer 穿梭框 基础用法 Transfer 的数据通过 data 属性传入。数据需要是一个对象数组,每个对象有以下属性:key 为数据的唯一性标识,label 为显示文本,disabled 表示该项数据是否禁止转移。目标列表中的数据项会同步到绑定至 v-model 的变量,值为数据项的 key 所组成的数组。当然,如果希望在初始状态时目标列表不为空,可以像本例一样为 v-model 绑定

    • 物联网是智能穿戴设备的核心技术,智能穿戴设备是物联网的关键载体。两者的结合,实现更加丰富灵活的应用功能。 我们能够提供: 最广泛的网络覆盖面; 灵活的计费方式,降低运营费用; 综合的管理平台和多种应用,支持灵活的业务运营; 稳定,快捷的通信服务。

    • 既然是网页版 word,就得穿上 word 的马甲,让人一眼看上去它就是真正的 word。其实,也非常简单。 CSS3 提供了 border-image 特性,只需搞一个 word 的截屏,再把它设置为 wrapper 容器的边框图像,就有了 word 的轮廓,看上去就像真正的 word 了。 由于图像边框会占据容器的空间,所以,需要调整容器的内边距,以便容器中的内容不会被边框图像所覆盖。CSS代