Поскольку сервер знает адреса обоих клиентов, он может отправлять эту информацию им, чтобы они знали адреса друг друга.Есть много способов, которыми сервер может отправлять эти данные - зарезервированные, json-кодированные, необработанные байты.Я думаю, что лучшим вариантом является преобразование адреса в байты, потому что клиент будет точно знать, сколько байтов нужно прочитать: 4 для IP (целое число) и 2 для порта (беззнаковое короткое).Мы можем преобразовать адрес в байты и обратно с помощью функций ниже.
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])
Когда клиенты получают и декодируют адрес, им больше не нужен сервер, и они могут установить новое соединение между ними.
Теперь у нас есть два основных варианта, насколько я знаю.
Один клиент выступает в качестве сервера.Этот клиент закрыл бы соединение с сервером и начал бы слушать на том же порту.Проблема этого метода заключается в том, что он будет работать только в том случае, если оба клиента находятся в одной локальной сети или этот порт открыт для входящих подключений.
Перфорация.Оба клиента начинают отправлять и принимать данные друг от друга одновременно.Клиенты должны принимать данные по тому же адресу, который они использовали для подключения к серверу рандеву, который связан друг с другом.Это пробило бы дыру в натах клиента, и клиенты могли бы общаться напрямую, даже если они находятся в разных сетях.Этот процесс подробно описан в этой статье Одноранговая связь через трансляторы сетевых адресов , раздел 3.4 Одноранговые узлы за различными NAT.
Пример Python для UDPПерфорация отверстий:
Сервер:
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)
Клиент:
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)
Этот код требует, чтобы на сервере рандеву был открыт порт (4000 в данном случае),и быть доступным обоим клиентам.Клиенты могут находиться в одной или разных локальных сетях.Код был протестирован в Windows, и он хорошо работает с локальным или общедоступным IP-адресом.
Я экспериментировал с пробоем TCP-дырок, но у меня был ограниченный успех (иногда кажется, что он работает, иногда - нет).т).Я могу включить код, если кто-то хочет экспериментировать.Концепция более или менее одинакова, оба клиента начинают отправлять и получать одновременно, и она подробно описана в Одноранговая связь через трансляторы сетевых адресов , раздел 4, Перфорация отверстий TCP.
Если оба клиента находятся в одной сети, будет намного проще общаться друг с другом.Им нужно будет каким-то образом выбрать, какой из них будет сервером, затем они смогут создать нормальное соединение сервер-клиент.Единственная проблема здесь заключается в том, что клиенты должны определить, находятся ли они в одной сети.Опять же, сервер может помочь с этой проблемой, так как он знает общедоступный адрес обоих клиентов.Например:
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 ...
Здесь сервер отправляет два адреса каждому клиенту: публичный адрес партнера и собственный публичный адрес клиента.Клиенты сравнивают два IP-адреса, если они совпадают, они должны быть в одной локальной сети.