In the first part, we discussed the way one device can communicate with other devices in the same network. What we did was we typed the IP address and port number of server device into the text field of a client device. This approach works if you have only one possible server on the network and the address of this server never changes. In this case, we do not even need to enter the server’s address to the client’s app for the client to connect. Client already may know the server’s address. However, what if there are more servers in the network? What if a user wants to be able to connect to other servers? The answer is server discovery and server lookup.
Let us start immediately where we left off last time. We had an app for iOS and Android that could act as a client and as a server as well. So let’s get to it.
There are quite a few scenarios we need to cover so that our clients could find their servers.
<2>Consider the first scenario.
The server appears in the network or disappears from it and there are no clients yet. It has to notify all the potential clients that there is a new server or such server is no more available. The server does it using UDP broadcast on some port that our clients should be listening to. It is a shame that no one listens to it yet…
If we build on the previous code, we need some additional classes to handle server information and UDP broadcasting routines:
- ServerInfo – the actual information on the server that is broadcasted as a message over UDP. This should contain IP address, port that server is listening to and some textual information on the server. However, this can easily contain much more like the unique id of the server, the state of the server, any info the client might be interested in, etc.
- SocketBroadcastClient – the abstraction over a UDP socket to handle outgoing UDP messages. The server will use this to broadcast its availability and info. The client will use this for server lookup broadcasts over UDP.
- SocketBroadcastServer – the abstraction over a UDP socket to handle incoming messages. For the server this information can be any kind of binary formatted data that makes our networking server react to it somehow. For the client this could be the actual ServerInfo object.
At this stage, there are no clients interested in this kind of server on the network. The server however is running and is ready to accept any incoming connection over TCP.
This leads us to next scenario: We have a server in the network and a client comes in.
When the client comes to the party, it broadcasts its lookup message. All the available servers in the network are listening to the same port for lookup messages. If a new client comes in, each server replies with its information and state. The client receives all these messages from multiple (possible) servers and adds applicable servers to its list. The response message for lookup broadcast must contain at least IP address and port number for client to be able to connect to each particular server on the network.
Scenario 3 covers the case when server is no longer available or is about to become no longer available. In this case, the server just broadcasts a message that it is no longer available. This message is the same message as the server sends when it becomes available but with a different availability flag. If client has this server on the servers list it is wise to just remove the server from the list. However, there can be situations when server is unable to broadcast a quit message. For example, the server could just crash. In this case, quit message will not be sent over the network and none of clients will be able to react accordingly. Well this kind of situation is kind of an edge case. The server should not crash unexpectedly leading the clients to have incorrect information about the network state. However, we can solve this as well. For example, the client can ping all the servers in its list. If the server is inaccessible, the client simply deletes it from the list.
So let’s sum it up!
We have an app that can be either a server or a client. We have one socket. Because of the nature of UDP protocol – it does not maintain persistent connection, we add in two more sockets to the app to handle incoming and outgoing UDP messages respectively.
- If there’s only a server on the network and a client comes in, it broadcasts a lookup message. This message can contain anything inside. The one and only purpose of it is to trigger all the applicable servers to broadcast their discovery information to the network again. The client listens to these messages and adds servers to the list. The server discovery info should contain the IP address, port number, unique identifier (could be a GUID or something) and any additional information we might think of.
- If there are only clients on the network without servers and then a server comes in, it broadcasts its server info object, so all the clients can connect to it.
- If a server is about to become unavailable, it broadcasts the same server info object with a different flag stating that server is no longer available. Client just deletes the server from its list in this case.
- Client might ping all the servers in its list (not implemented in the sample) to handle situations when server becomes unavailable but does not broadcast a quit message prior to quitting (possible server crashes, etc) and leading to incorrect information on all the clients. This is left as an exercise to the readers
NOTE: this entire infrastructure was added to handle server lookup and server discovery only. The client is not connected to the server yet. However, if a user taps an entry in the client’s server list, the client immediately connects to the server: it already has all the required information for making TCP connections.
So get the code and enjoy.