Давайте рассмотрим ситуацию с четырьмя одноранговыми узлами:

Если каждый одноранговый узел соединяется с каждым другим одноранговым узлом, между каждой парой одноранговых узлов будет два соединения.(Если имеется N одноранговых узлов, существует N (N-1) / 2 уникальных пар.)
На приведенной выше диаграмме одноранговые узлы упорядочены по их имени (A, B, C, D).Соединения с одноранговыми узлами с именами позже в алфавите выделены синим цветом, соединения с одноранговыми узлами ранее в алфавите выделены красным цветом.
Если вы можете каким-либо образом упорядочить одноранговые узлы, подключайтесь только к одноранговым узлам перед собой в порядкекрасный) и прием соединений от одноранговых узлов после себя (опять же, красный) гарантирует, что у вас будет только одно соединение на уникальную пару одноранговых узлов.
Допустим, у вас есть N
одноранговых узлов, и вы как-то заказываете их из0
до N-1
включительно.Затем одноранговому i
необходимо accept()
подключения от N-1-i
одноранговых узлов и connect()
к i
одноранговым узлам.
Конечно, на практике все не так просто.Проблема в том, что одноранговые группы могут появляться в разное время, а не одновременно;и соединение не будет установлено, если на другом конце еще нет прослушивающего сокета, соответствующего адресу и порту.Таким образом, наиболее важно сначала создать прослушивающий сокет, убедиться, что резерв достаточно велик, а затем выполнить подключения.
Если мы посмотрим на GIO GSocket s (поскольку это, кажется,будь то, что использует OP), решение довольно простое, при условии, что все одноранговые узлы согласны с их порядком.
Все одноранговые узлы создают сокет прослушивания, используя g_socket_new()
, g_socket_bind()
, g_set_backlog()
и g_socket_listen()
.Задержка должна составлять не менее числа пиров (не более одного).
Если новых пиров можно «пригласить» во время выполнения, используйте больший объем запаздывания.Если новые одноранговые узлы не могут быть приглашены, то последнему одноранговому узлу в заказе не требуется сокет прослушивания, поскольку он будет только устанавливать соединения, а не принимать входящие.
i
Третий узел (первый узел в порядке 0
) создает i
сокетов для подключения к узлам перед порядком, используя g_socket_new()
, опционально 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
из сокета, чтобы рассматривать его как источник события.)
На этом этапе входящие соединения должны быть заблокированы в сетевом стеке ОС (или будут), поэтому одноранговый i
должен использовать g_socket_accept()
для приема входящих соединений от одноранговых узлов N-1-i
после него в порядке.
Лично я бы использовал другую архитектуру / подход, который динамически управляет списком пиров.
Допустим, у каждого пира есть уникальный идентификатор.(Это может быть, например, сопоставленный с IPv6 адрес и порт, или псевдоним пользователя и их открытый ключ.)
Когда соединение установлено, соединяющая сторона отправляет начальное рукопожатие, которое содержит свой собственный идентификатор, иидентификатор стороны, к которой он пытается подключиться.Когда новое соединение принято, получено начальное рукопожатие (в течение некоторого интервала времени ожидания).Если присоединяющаяся сторона является новой, она добавляется в список подключенных пиров.Если оно уже подключено, либо новое, либо существующее соединение будет сброшено (после отправки пакета, в котором указана причина сброса).
Это также позволит "приглашать" и обмениваться информацией о одноранговых соединениях черезсетка.