两个客户端都连接了一个会面点服务器后,如何让两个客户端直接相互连接? [英] How to make 2 clients connect each other directly, after having both connected a meeting-point server?

查看:79
本文介绍了两个客户端都连接了一个会面点服务器后,如何让两个客户端直接相互连接?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为两个客户端A"和B"编写一个侦听端口 5555 的玩具会议点/中继服务器.

I'm writing a toy meeting-point/relay server listening on port 5555 for two clients "A" and "B".

它的工作原理是:服务器从第一个连接的客户端 A 接收到的每个字节都将发送到第二个连接的客户端 B,即使 A 和 B 不知道各自的 IP:

It works like this: every byte received by the server from the firstly-connected client A will be sent to the secondly-connected client B, even if A and B don't know their respective IP:

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

此代码目前正在运行:

# 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

并且您可以通过在服务器上启动它来测试它,并对其进行两个 netcat 连接:nc 5555.

and you can test it by launching it on a server, and do two netcat connections to it: nc <SERVER_IP> 5555.

然后我如何将信息传递给客户端 A 和 B,让它们可以直接相互交谈,而无需通过服务器传输字节?

有两种情况:

  • 一般情况,即即使 A 和 B 不在同一个本地网络中

  • General case, i.e. even if A and B are not in the same local network

这两个客户端在同一个本地网络中的特殊情况(例如:使用同一个家庭路由器),当这两个客户端通过端口 5555 连接到服务器时,这将显示在服务器上:

Particular case where these two clients are in the same local network (example: using the same home router), this will be displayed on the server when the 2 clients will connect to the server on port 5555:

('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

备注:之前在这里尝试失败:UDP 或 TCP 打孔以连接两个对等点(每个在一个路由器后面)UDP 打孔与第三方

Remark: a previous unsuccesful attempt here: UDP or TCP hole punching to connect two peers (each one behind a router) and UDP hole punching with a third party

推荐答案

由于服务器知道两个客户端的地址,它可以将这些信息发送给他们,这样他们就可以知道彼此的地址.服务器可以通过多种方式发送此数据 - 腌制、json 编码、原始字节.我认为最好的选择是将地址转换为字节,因为客户端将确切知道要读取的字节数:4 个用于 IP(整数),2 个用于端口(无符号短).我们可以使用以下函数将地址转换为字节并返回.

Since the server knows the addresses of both clients, it can send that information to them and so they would know each others adress. There are many ways the server can send this data - pickled, json-encoded, raw bytes. I think the best option is to convert the address to bytes, because the client will know exactly how many bytes to read: 4 for the IP (integer) and 2 for the port (unsigned short). We can convert an address to bytes and back with the functions below.

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])

客户端收到地址并解码后,就不再需要服务器了,可以在它们之间建立新的连接.

When the clients receive and decode the address, they no longer need the server, and they can establish a new connection between them.

据我所知,现在我们有两个主要的选项.

Now we have two main otions, as far as I know.

  • 一个客户端充当服务器.此客户端将关闭与服务器的连接并开始侦听同一端口.这种方法的问题在于,只有当两个客户端都在同一个本地网络上,或者该端口对传入连接开放时,它才会起作用.

  • One client acts as a server. This client would close the connection to the server and would start listening on the same port. The problem with this method is that it will only work if both clients are on the same local network, or if that port is open for incoming connections.

打孔.两个客户端同时开始相互发送和接受数据.客户端必须在它们用来连接到彼此已知的集合点服务器的同一地址上接受数据.这会在客户端的 nat 上打一个洞,即使客户端在不同的网络上,它们也可以直接通信.这篇文章详细解释了这个过程跨网络地址转换器的点对点通信,第 3.4 节不同 NAT 背后的对等点.

Hole punching. Both clients start sending and accepting data from each other simultaneously. The clients must accept data on the same address they used to connect to the rendezvous server, which is knwn to each other. That would punch a hole in the client's nat and the clients would be able to communicate directly even if they are on different networks. This proccess is expleined in detail in this article Peer-to-Peer Communication Across Network Address Translators, section 3.4 Peers Behind Different NATs.

UDP 打孔的 Python 示例:

A Python example for UDP Hole Punching:

服务器:

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 还是公共 IP,它都能正常运行.

This code requires for the rendezvous server to have a port open (4000 in this case), and be accessible by both clients. The clients can be on the same or on different local networks. The code was tested on Windows and it works well, either with a local or a public IP.

我已经尝试过 TCP 打孔,但我的成功有限(有时似乎有效,有时却无效).如果有人想尝试,我可以包含代码.概念或多或少相同,两个客户端同时开始发送和接收,在跨网络地址转换器的点对点通信,第 4 节,TCP 打孔.

I have experimented with TCP hole punching but I had limited success (sometimes it seems that it works, sometimes it doesn't). I can include the code if someone wants to experiment. The concept is more or less the same, both clients start sending and receiving simultaneously, and it is described in detail in Peer-to-Peer Communication Across Network Address Translators, section 4, TCP Hole Punching.

如果两个客户端在同一个网络上,彼此之间的通信会容易得多.他们必须以某种方式选择哪一个作为服务器,然后他们才能创建正常的服务器-客户端连接.这里唯一的问题是客户端必须检测它们是否在同一网络上.同样,服务器可以帮助解决这个问题,因为它知道两个客户端的公共地址.例如:

If both clients are on the same network, it will be much easier to communicate with each other. They would have to choose somehow which one will be a server, then they can create a normal server-client connection. The only problem here is that the clients must detect if they are on the same network. Again, the server can help with this problem, as it knows the public address of both clients. For example:

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 ...

这里服务器向每个客户端发送两个地址,peer的公共地址和客户端自己的公共地址.客户端比较两个 IP,如果匹配,则它们必须在同一本地网络上.

Here the server sends two addresses to each client, the peer's public address and the client's own public address. The clients compare the two IPs, if they match then they must be on the same local network.

这篇关于两个客户端都连接了一个会面点服务器后,如何让两个客户端直接相互连接?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆