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.
- Records of some sort (I use
GList
) to contain "pending" IPs
- Function to convert IP to a number
- 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 */