如何绕过连接到已经连接的一方

How to get around connecting to a party that has already been connected

根据我的程序架构,我很难确定如何取消来自两方的连接同步。这是一个P2P聊天。程序启动时,每一方都会自动相互连接。连接过程是异步的。 当有传入连接时,从侦听器调用信号,信号处理程序负责连接。所以 "accept" 也是异步的。

这将最终连接两个用户两次。会有两个连接。 双方将是服务器和客户端。这是不需要的。 现在我尝试了很多方法来解决这个问题,但 none 确实有效。 例如,我在侦听器信号处理程序中检查了该 IP 上的套接字是否已经存在,如果存在则断开连接并 return。但是连接的设计方式使得另一方会不断尝试连接。

设计它的正确方法是什么?备选方案是什么?

让我们看一下有四个同行的情况:

如果每个对等点都连接到每个其他对等点,则每对对等点之间将有两个连接。 (如果有 N 个对等点,则有 N(N-1)/2 个唯一对。)

在上图中,节点按名称排序(A、B、C、D)。与字母表中名字靠后的对等点的连接是蓝色的,与字母表中靠前的对等点的连接是红色的。

如果你能以某种方式对节点进行排序,只连接顺序中自己之前的节点(红色),并接受来自自己之后的节点的连接(再次,红色),将确保你只有一个唯一连接对同龄人。

假设您有 N 个同伴,并且您以某种方式从 0N-1 对他们进行排序,包括在内。然后,对等点 i 需要 accept() 个来自 N-1-i 个对等点的连接,connect()i 个对等点的连接。

当然,在实践中没有那么简单。问题是节点可能会在不同时间出现,而不是同时出现;如果另一端还没有对应地址和端口的监听套接字,则连接将失败。所以,最重要的是先做listening socket,保证backlog足够大,再做connections。

如果我们查看 GIO GSockets(因为这似乎是 OP 正在使用的),解决方案相当简单,假设所有同行都同意他们的顺序。

  1. 所有对等方使用 g_socket_new()g_socket_bind()g_set_backlog()g_socket_listen() 创建一个侦听套接字。 backlog至少应该是同行的数量(少一个)。

    如果新节点可以在 运行 时成为 "invited in",请使用更大的积压。如果无法邀请新的对等点,则顺序中的最后一个对等点不需要侦听套接字,因为它只会建立连接,而不接受传入的连接。

  2. ith peer(第一个 peer 按顺序是 0)使用 [=18 创建 i 套接字连接到它之前的 peers =],可选 g_socket_bind(),可选 g_socket_set_blocking() 使连接成为非阻塞,g_socket_connect().

    如果使用 g_socket_set_blocking() 使套接字成为非阻塞,则对等方可以在 g_socket_check_connect_result()g_socket_condition_check() 中循环以等待所有连接完成。

    请注意,由于每个对等端创建其传入套接字的时间存在实际差异,因此连接可能会失败(因为另一端尚未启动)。在这些情况下,您应该重试;即再次调用 g_socket_connect()

    (您也可以使用 g_socket_create_source() 从套接字创建一个 GSource,将其视为事件源。)

  3. 此时,传入连接应积压在 OS 网络堆栈中(或将积压),因此对等点 i 应使用 g_socket_accept() 来接受来自排序后 N-1-i 对等点的传入连接。


就我个人而言,我会使用一种不同的 architecture/approach,它可以动态管理对等列表。

假设每个点都有一个唯一的标识符。 (这可能是 IPv6 映射地址和端口,或者用户昵称和他们的 public 密钥。)

建立连接后,连接方发送初始握手,其中包含自己的标识符以及它尝试连接的一方的标识符。当接受新连接时,将收到初始握手(在某个超时间隔内)。如果连接方是新的,则将其添加到已连接的对等方列表中。如果它已经连接,则新的或现有的连接将被丢弃(在发送一个说明丢弃原因的数据包之后)。

这也将允许 "invites",并跨网格共享对等连接信息。