I just recently did a small app that is essentially peer to peer. The game plays by itself, but anyone on the local network can join another game by typing "N". There is a 4-second hitch if there is no other game on the local broadcast as I did the network timeout in a loop separate from the main game -- but failure doesn't produce an external prompt.
So the app knows if it is running as client or server there are two network identifiers. On startup the app calls HostNetwork() and stores the result as srvNet. In the main loop it always enumerates clients using GetNetworkFirstClient() and GetNetworkNextClient(). It knows which client is itself via GetNetworkServerID() (though in practice it is always id=1 because it is always the first to join its own network). So the server knows when there is a client because it is able to get a clientID other than its own. It then bothers sending and receiving messages.
If the app tries to join a network it calls CloseNetwork(srvNet) and then sets srvNet = 0 before doing JoinNetwork() -- which result, if successful, is stored as cliNet. So if cliNet is > 0 it knows it is a client and sends and receives messages.
Although, using local variables proved to be less problematic than messages. Those you can always set as server or not, then if cliNet > 0 read from them.