Search code examples
csocketsnetworkingp2pgio

Avoiding simultaneous bi-directional connections in a P2P network architecture


Okay, I am a certified programmer, been doing all kinds of wonders for many years, but I am finally going to ask a question on a problem that I am utterly unable to solve. I hope Stack Overflow is going to save my life once again, after so many years.


It is about the unique pairs problem. I am using GIO's GSocketClient and GSocketService high-level APIs to establish asynchronous connect and accept of incoming connections between two peers (each uses the same program obviously). Since peers can connect to each other at the same time, this results in 2 connections whereas only one is needed.

I tried too many things including dropping the connection which bound IP number comes before the other (so that one connection is discarded) but this still does not always work so my head spins now what and where should come first.

To not over-complicate things with all the b******t I have tried, I am going to ask it straightforward: What is the correct approach in cases like this?


Solution

  • It's been tough, but after 3 days of persistent thinking and testing.. I have a solution.

    I firmly believe this question shouldn't be downvoted (especially because of what it seems to be "pretentious and pompous"), but we can't always get the best of S/O can we.

    The problem

    When two peers want to connect to each other concurrently there will be two connections, whereas only one is needed. It is even more complex when accepting, connecting and handling is asynchronous and can occur at any time and also both peers respectively use the very same program, hence a rather strict P2P architecture. This is a classical example of the Byzantine Generals problem, which is one of the hardest computer problems.

    The solution

    The solution does not involve complete synchronizing so that it lacks the point of having async connect, accept and handling and does not require dropping connection after it has been handled. There are 3 main ideas behind this potent solution.

    1. Records of some sort (I use GList) to contain "pending" IPs
    2. Function to convert IP to a number
    3. System to wait for and give permissions

    If implemented correctly and used on the right place, you have it. Now a bit more details.

    Records of some sort to contain "pending" IPs

    As I said, this can be a list. The append, search and delete operations must be locked by a mutex. The removal of the IP from the list must occur on accept, after a search and in the disconnect notifier.

    Function to convert IP to a number

    e.g rScanf = sscanf(ip, "%u.%u.%u.%u", &bytes[0], &bytes[1], &bytes[2], &bytes[3]);

    then adding each number. This is used as an exclusive criteria. if(remote_ip_number > local_ip_number) is going to be true only for one of the peers.

    System to wait for and give permissions

    The connect must wait for permission from the accepting thread. Although the call must be blocking, it will not cause any overhead, in most cases it will return immediately. After and if it receives permission (denied(1) or granted(2)) it sends ACK (3) and either returns (1- drops connection) or continues. The host waits for the ACK and then on the same way returns (drops connection) or continues (if permission is granted).

    The host shall determine whether to deny or grant based on the exclusive criteria. First it checks if IP exists in list of pending connections (basically means that the program also tries to connect) and if it does, if IP condition matches send grant or deny otherwise.


    It is important that an IP is added to the list of pending connections early in the sync call of connect, before it actually tries to connect and also it is important that the program is constructed in such a way that first it connects to whomever it wants to connect and then hosts, so that first pending IPs are registered. This gives a slight advantage of predictability.

    connect(PETER); /* adds PETER ip to listPending */
    connect(JOHN); /* adds JOHN ip to listPending */
    
    host(ME); /* Gives permission if IP does not exist in listPending or it does but local ip number < remote ip number */