Припустимо, у нас є два однорангові вузли: перший вузол може надіслати запит на з'єднання другому, а також другий може надіслати запит на з'єднання до першого. Як уникнути подвійного зв’язку між двома вузлами? Для вирішення цього питання достатньо зробити послідовні операції, що виконуються для створення вхідних або вихідних TCP-з'єднань.
Це означає, що кожен вузол повинен обробляти послідовно кожну нову операцію створення з'єднання, як для вхідних, так і для вихідних з'єднань. Таким чином, підтримуючи список підключених вузлів, перш ніж приймати нове вхідне з'єднання від вузла або перед відправленням запиту на з'єднання до вузла, буде достатньо перевірити, чи цей вузол вже присутній у списку.
Для здійснення послідовних операцій створення з'єднань достатньо виконати блокування у списку підключених вузлів: насправді для кожного нового з'єднання до цього списку додається ідентифікатор нового підключеного вузла. Однак мені цікаво, чи може такий підхід спричинити поширений глухий кут :
- перший вузол міг надіслати запит на з'єднання другому;
- другий вузол може надіслати запит на з'єднання до першого;
- припускаючи, що два запити на з'єднання не є асинхронними, обидва вузли блокують будь-які вхідні запити на з'єднання.
Як я міг вирішити цю проблему?
ОНОВЛЕННЯ: Однак мені все одно доводиться блокувати список щоразу, коли створюється нове (вхідне чи вихідне) з'єднання, оскільки інші потоки можуть отримати доступ до цього списку, тоді проблема з тупиком все ще залишатиметься.
ОНОВЛЕННЯ 2: На основі вашої поради я написав алгоритм, щоб запобігти взаємному прийняттю запиту на вхід. Оскільки кожен вузол є одноранговим, він може мати клієнтську процедуру для надсилання нових запитів на з'єднання та процедуру сервера для прийому вхідних з'єднань.
ClientSideLoginRoutine() {
for each (address in cache) {
lock (neighbors_table) {
if (neighbors_table.contains(address)) {
// there is already a neighbor with the same address
continue;
}
neighbors_table.add(address, status: CONNECTING);
} // end lock
// ...
// The node tries to establish a TCP connection with the remote address
// and perform the login procedure by sending its listening address (IP and port).
boolean login_result = // ...
// ...
if (login_result)
lock (neighbors_table)
neighbors_table.add(address, status: CONNECTED);
} // end for
}
ServerSideLoginRoutine(remoteListeningAddress) {
// ...
// initialization of data structures needed for communication (queues, etc)
// ...
lock(neighbors_table) {
if(neighbors_table.contains(remoteAddress) && its status is CONNECTING) {
// In this case, the client-side on the same node has already
// initiated the procedure of logging in to the remote node.
if (myListeningAddress < remoteListeningAddress) {
refusesLogin();
return;
}
}
neighbors_table.add(remoteListeningAddress, status: CONNECTED);
} // end lock
}
Приклад: Порт IP: вузол A дорівнює A: 7001 - IP: порт вузла B дорівнює B: 8001.
Припустимо, що вузол A надіслав запит на вхід у вузол B: 8001. У цьому випадку вузол A викликає процедуру входу, надсилаючи, надсилаючи власну адресу прослуховування (A: 7001). Як наслідок, сусідній_таблиця вузла A містить адресу віддаленого вузла (B: 8001): ця адреса пов'язана зі станом ЗВ'ЯЗКУ. Вузол A чекає, коли вузол B прийме або відхилить запит на вхід.
Тим часом, вузол B також, можливо, надіслав запит на з'єднання на адресу вузла A (A: 7001), тоді вузол A може обробляти запит вузла B. Отже, сусіда_ таблиця вузла B містить адресу віддаленого вузол (A: 7001): ця адреса асоціюється зі станом З’єднання. Вузол B чекає на вузол A прийняття або відхилення запиту на вхід.
Якщо сторона сервера вузла A відхиляє запит від B: 8001, я повинен бути впевнений, що сторона сервера вузла B прийме запит від A: 7001. Аналогічно, якщо серверна частина вузла B відхиляє запит від A: 7001, я повинен бути впевнений, що сторона сервера вузла A прийме запит від B: 8001.
Згідно з правилом "малої адреси" , у цьому випадку вузол A буде відхиляти запит на вхід вузлом B, тоді як вузол B прийме запит від вузла A.
Що ти думаєш про це?