it-swarm.com.de

Wie werden zwei Clients direkt miteinander verbunden, nachdem beide einen Meetingpoint-Server verbunden haben?

Ich schreibe einen Spielzeug-Treffpunkt/Relay-Server, der auf Port 5555 nach zwei Clients "A" und "B" wartet.

Das funktioniert so: Jedes Byte, das der Server vom erstverbundenen Client A empfängt, wird an den zweitverbundenen Client B gesendet, auch wenn A und B ihre jeweilige IP nicht kennen :

A -----------> server <----------- B     # they both connect the server first
A --"hello"--> server                    # A sends a message to server
               server --"hello"--> B     # the server sends the message to B

Dieser Code funktioniert derzeit:

# server.py
import socket, time
from threading import Thread
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.bind(('', 5555))
socket.listen(5)
buf = ''
i = 0

def handler(client, i):
    global buf
    print 'Hello!', client, i 
    if i == 0:  # client A, who sends data to server
        while True:
            req = client.recv(1000)
            buf = str(req).strip()  # removes end of line 
            print 'Received from Client A: %s' % buf
    Elif i == 1:  # client B, who receives data sent to server by client A
        while True:
            if buf != '':
                client.send(buf)
                buf = ''
            time.sleep(0.1)

while True:  # very simple concurrency: accept new clients and create a Thread for each one
    client, address = socket.accept()
    print "{} connected".format(address)
    Thread(target=handler, args=(client, i)).start()
    i += 1

sie können es testen, indem Sie es auf einem Server starten und zwei Netcat-Verbindungen herstellen: nc <SERVER_IP> 5555.

Wie kann ich dann die Information an die Clients A und B weitergeben, dass sie direkt miteinander sprechen können, ohne dass die Bytes über den Server übertragen werden?

Es gibt 2 Fälle:

  • Allgemeiner Fall, d. H. Selbst wenn A und B nicht im selben lokalen Netzwerk sind

  • In bestimmten Fällen, in denen sich diese beiden Clients im selben lokalen Netzwerk befinden (Beispiel: Verwenden desselben Heimrouters), wird dies auf dem Server angezeigt, wenn die beiden Clients über Port 5555 eine Verbindung zum Server herstellen:

    ('203.0.113.0', 50340) connected  # client A, router translated port to 50340
    ('203.0.113.0', 52750) connected  # same public IP, client B, router translated port to 52750
    

Bemerkung: ein früherer erfolgloser Versuch hier: UDP oder TCP Hole Punching, um zwei Peers (jeweils hinter einem Router) zu verbinden und UDP Hole Punching mit einem Drittanbieter

6
Basj

Da der Server die Adressen beider Clients kennt, kann er diese Informationen an sie senden, sodass sie sich gegenseitig kennen. Es gibt viele Möglichkeiten, wie der Server diese Daten senden kann - eingelegte, json-codierte, unformatierte Bytes. Ich denke, die beste Option ist, die Adresse in Bytes umzuwandeln, da der Client genau weiß, wie viele Bytes zu lesen sind: 4 für die IP (Ganzzahl) und 2 für den Port (vorzeichenlos kurz). Mit den folgenden Funktionen können wir eine Adresse in Bytes und zurück konvertieren.

import socket
import struct

def addr_to_bytes(addr):
    return socket.inet_aton(addr[0]) + struct.pack('H', addr[1])

def bytes_to_addr(addr):
    return (socket.inet_ntoa(addr[:4]), struct.unpack('H', addr[4:])[0])

Wenn die Clients die Adresse empfangen und dekodieren, benötigen sie den Server nicht mehr und können eine neue Verbindung zwischen ihnen herstellen.

Soweit ich weiß, haben wir jetzt zwei Hauptthemen.

  • Ein Client fungiert als Server. Dieser Client würde die Verbindung zum Server trennen und den gleichen Port abhören. Das Problem bei dieser Methode ist, dass sie nur funktioniert, wenn sich beide Clients im selben lokalen Netzwerk befinden oder dieser Port für eingehende Verbindungen geöffnet ist.

  • Lochstanzen. Beide Clients senden und akzeptieren gleichzeitig Daten voneinander. Die Clients müssen Daten von derselben Adresse akzeptieren, die sie für die Verbindung zum Rendezvous-Server verwendet haben, der untereinander bekannt ist. Das würde ein Loch in das Netzwerk des Kunden schlagen und die Kunden könnten direkt kommunizieren, selbst wenn sie sich in verschiedenen Netzwerken befinden. Dieser Vorgang wird in diesem Artikel ausführlich erläutert. Peer-to-Peer-Kommunikation über Netzwerkadressübersetzer hinweg , Abschnitt 3.4 Peers hinter verschiedenen NATs.

Ein Python-Beispiel für UDP Hole Punching:

Server:

import socket

def udp_server(addr):
    soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    soc.bind(addr)

    _, client_a = soc.recvfrom(0)
    _, client_b = soc.recvfrom(0)
    soc.sendto(addr_to_bytes(client_b), client_a)
    soc.sendto(addr_to_bytes(client_a), client_b)

addr = ('0.0.0.0', 4000)
udp_server(addr)

Klient:

import socket
from threading import Thread

def udp_client(server):
    soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    soc.sendto(b'', server)
    data, _ = soc.recvfrom(6)
    peer = bytes_to_addr(data)
    print('peer:', *peer)

    Thread(target=soc.sendto, args=(b'hello', peer)).start()
    data, addr = soc.recvfrom(1024)
    print('{}:{} says {}'.format(*addr, data))

server_addr = ('server_ip', 4000) # the server's  public address
udp_client(server_addr)

Dieser Code setzt voraus, dass der Rendezvous-Server einen offenen Port (in diesem Fall 4000) hat und für beide Clients zugänglich ist. Die Clients können sich in demselben oder in verschiedenen lokalen Netzwerken befinden. Der Code wurde unter Windows getestet und funktioniert gut, entweder mit einer lokalen oder einer öffentlichen IP.

Ich habe mit TCP Lochstanzen experimentiert, aber ich hatte begrenzten Erfolg (manchmal scheint es, dass es funktioniert, manchmal nicht). Ich kann den Code einfügen, wenn jemand experimentieren möchte. Das Konzept ist mehr oder weniger dasselbe, beide Clients senden und empfangen gleichzeitig und es wird ausführlich in Peer-to-Peer-Kommunikation über Netzwerkadressübersetzer , Abschnitt 4, TCP Hole beschrieben Stanzen.


Wenn sich beide Clients im selben Netzwerk befinden, ist die Kommunikation untereinander viel einfacher. Sie müssten sich irgendwie entscheiden, welcher Server ein Server sein soll, dann können sie eine normale Server-Client-Verbindung herstellen. Das einzige Problem hierbei ist, dass die Clients erkennen müssen, ob sie sich im selben Netzwerk befinden. Auch hier kann der Server Abhilfe schaffen, da er die öffentliche Adresse beider Clients kennt. Zum Beispiel:

def tcp_server(addr):
    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    soc.bind(addr)
    soc.listen()

    client_a, addr_a = soc.accept()
    client_b, addr_b = soc.accept()
    client_a.send(addr_to_bytes(addr_b) + addr_to_bytes(addr_a))
    client_b.send(addr_to_bytes(addr_a) + addr_to_bytes(addr_b))

def tcp_client(server):
    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    soc.connect(server)

    data = soc.recv(12)
    peer_addr = bytes_to_addr(data[:6])
    my_addr = bytes_to_addr(data[6:])

    if my_addr[0] == peer_addr[0]:
        local_addr = (soc.getsockname()[0], peer_addr[1])
        ... connect to local address ...

Hier sendet der Server zwei Adressen an jeden Client, die öffentliche Adresse des Peers und die öffentliche Adresse des Clients. Die Clients vergleichen die beiden IPs. Wenn sie übereinstimmen, müssen sie sich im selben lokalen Netzwerk befinden.

5
t.m.adam

Die akzeptierte Antwort gibt die Lösung. Hier einige zusätzliche Informationen für den Fall "Client A und Client B befinden sich im selben lokalen Netzwerk" . Diese Situation kann tatsächlich vom Server erkannt werden, wenn festgestellt wird, dass beide Clients dieselbe öffentliche IP-Adresse haben.

Dann kann der Server Client A als "lokalen Server" und Client B als "lokalen Client" auswählen.

Der Server fragt dann Client A nach seiner "lokalen Netzwerk-IP". Client A kann es finden mit :

import socket
localip = socket.gethostbyname(socket.gethostname())  # example: 192.168.1.21

und senden Sie es dann an den Server zurück. Der Server teilt Client B diese "lokale Netzwerk-IP" mit.

Dann wird Client A einen "lokalen Server" ausführen:

import socket
soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
soc.bind(('0.0.0.0', 4000))
data, client = soc.recvfrom(1024)
print("Connected client:", client)
print("Received message:", data)
soc.sendto(b"I am the server", client)

und Client B wird als "lokaler Client" ausgeführt:

import socket
soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server = ('192.168.1.21', 4000)   # this "local network IP" has been sent Client A => server => Client B
soc.sendto("I am the client", server)
data, client = soc.recvfrom(1024)
print("Received message:", data)
1
Basj