Angenommen, wir haben zwei Peer-Knoten: Der erste Knoten kann eine Verbindungsanforderung an den zweiten senden, aber auch der zweite Knoten kann eine Verbindungsanforderung an den ersten senden. Wie vermeide ich eine doppelte Verbindung zwischen den beiden Knoten? Um dieses Problem zu beheben, ist es ausreichend, die zum Erstellen eingehender oder ausgehender TCP-Verbindungen ausgeführten Vorgänge nacheinander auszuführen.
Dies bedeutet, dass jeder Knoten nacheinander jede neue Verbindungserstellungsoperation verarbeiten sollte, sowohl für eingehende als auch für ausgehende Verbindungen. Auf diese Weise reicht es aus, eine Liste der verbundenen Knoten zu führen, bevor eine neue eingehende Verbindung von einem Knoten akzeptiert oder eine Verbindungsanforderung an einen Knoten gesendet wird, um zu überprüfen, ob dieser Knoten bereits in der Liste vorhanden ist.
Um die Vorgänge zum Erstellen von Verbindungen sequentiell durchzuführen, ist es ausreichend, die Liste der verbundenen Knoten zu sperren : Tatsächlich wird für jede neue Verbindung die Kennung des neuen verbundenen Knotens zu dieser Liste hinzugefügt. Ich frage mich jedoch, ob dieser Ansatz zu einem verteilten Deadlock führen kann :
- Der erste Knoten könnte eine Verbindungsanforderung an den zweiten senden.
- Der zweite Knoten könnte eine Verbindungsanforderung an den ersten senden.
- Unter der Annahme, dass die beiden Verbindungsanforderungen nicht asynchron sind, sperren beide Knoten alle eingehenden Verbindungsanforderungen.
Wie könnte ich dieses Problem lösen?
UPDATE: Ich muss die Liste jedoch immer noch jedes Mal sperren, wenn eine neue (eingehende oder ausgehende) Verbindung erstellt wird. Da andere Threads möglicherweise auf diese Liste zugreifen, bleibt das Problem des Deadlocks weiterhin bestehen.
UPDATE 2: Basierend auf Ihrem Rat habe ich einen Algorithmus geschrieben, um die gegenseitige Annahme einer Anmeldeanforderung zu verhindern. Da jeder Knoten ein Peer ist, kann er eine Client-Routine zum Senden neuer Verbindungsanforderungen und eine Server-Routine zum Akzeptieren eingehender Verbindungen haben.
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
}
Beispiel: Der IP: -Port von Knoten A ist A: 7001 - Der IP: -Port von Knoten B ist B: 8001.
Angenommen, der Knoten A hat eine Anmeldeanforderung an den Knoten B: 8001 gesendet. In diesem Fall ruft der Knoten A die Anmelderoutine auf, indem er sendet, indem er seine eigene Abhöradresse sendet (A: 7001). Infolgedessen enthält die Nachbarn-Tabelle von Knoten A die Adresse des entfernten Knotens (B: 8001): Diese Adresse ist dem Status CONNECTING zugeordnet. Knoten A wartet darauf, dass Knoten B die Anmeldeanforderung akzeptiert oder ablehnt.
In der Zwischenzeit kann der Knoten B auch eine Verbindungsanforderung an die Adresse von Knoten A (A: 7001) gesendet haben, dann kann Knoten A die Anforderung des Knotens B verarbeiten. Die Nachbarn-Tabelle von Knoten B enthält also die Adresse der Fernbedienung Knoten (A: 7001): Diese Adresse ist dem Status CONNECTING zugeordnet. Knoten B wartet darauf, dass Knoten A die Anmeldeanforderung akzeptiert oder ablehnt.
Wenn die Serverseite von Knoten A die Anforderung von B: 8001 ablehnt, muss ich sicher sein, dass die Serverseite von Knoten B die Anforderung von A: 7001 akzeptiert. Wenn die Serverseite von Knoten B die Anforderung von A: 7001 ablehnt, muss ich ebenfalls sicher sein, dass die Serverseite von Knoten A die Anforderung von B: 8001 akzeptiert.
Gemäß der Regel "kleine Adresse" lehnt in diesem Fall der Knoten A die Anmeldeanforderung durch den Knoten B ab, während der Knoten B die Anforderung von Knoten A akzeptiert.
Was denkst du darüber?