Chapter 2 ■ UDp
28
Of course, giving up makes sense only if your program is trying to perform some brief task and needs to produce
output or return some kind of result to the user. If you are writing a daemon program that runs all day—like, say,
a weather icon in the corner of the screen that displays the temperature and forecast fetched from a remote UDP
service—then it is fine to have code that keeps retrying “forever.” After all, a desktop or laptop machine might be off
the network for long periods of time, and your code might have to wait patiently for hours or days until the forecast
server can be contacted again.
If you are writing daemon code that retries all day, then do not adhere to a strict exponential backoff, or you
will soon have ramped the delay up to a value of like two hours, and then you will probably miss the entire half-hour
period during which the laptop owner sits down in a coffee shop and you could actually have gotten to the network.
Instead, choose some maximum delay—like, say, five minutes—and once the exponential backoff has reached that
period, keep it there so that you are always guaranteed to attempt an update once the user has been on the network
for five minutes after a long time disconnected.
If your operating system lets your process be signaled for events like the network coming back up, then you
will be able to do much better than to play with timers and guess about when the network might come back.
But system-specific mechanisms like that are, sadly, beyond the scope of this book, so let’s now return to UDP and
a few more issues that it raises.
Connecting UDP Sockets
Listing 2-2, which you examined in the previous section, introduced another new concept that needs explanation.
I have already discussed binding—both the explicit bind() call that a server uses to grab the address that it wants to use
and the implicit binding that takes place when the client first tries to use a socket and is assigned a random ephemeral
port number by the operating system.
But the remote UDP client in Listing 2-2 also uses a new call that I have not discussed before: the connect()
socket operation. You can see easily enough what it does. Instead of having to use sendto() with an explicit address
tuple every time you want to send something to the server, the connect() call lets the operating system know ahead of
time the remote address to which you want to send packets so that you can simply supply data to the send() call and
not have to repeat the server address again.
But connect() does something else important, which will not be obvious at all from reading Listing 2-2: it solves
the problem of the client being promiscuous! If you perform the test that you performed in the “Promiscuity” section
on this client, you will find that the Listing 2-2 client is not susceptible to receiving packets from other servers. This is
because of the second, less-obvious effect of using connect() to configure a UDP socket’s preferred destination: once
you have run connect(), the operating system will discard any incoming packets to your port whose return address
does not match the address to which you have connected.
There are, then, two ways to write UDP clients that are careful about the return addresses of the packets arriving back.
• You can use sendto() and direct each outgoing packet to a specific destination, then use
recvfrom() to receive the replies and carefully check each return address against the list of
servers to which you have made outstanding requests.
• You can instead connect() your socket right after creating it and communicate with send()
and recv(). The operating system will filter out unwanted packets for you. This works only for
speaking to one server at a time because running connect() again on the same socket does
not add a second destination address. Instead, it wipes out the first address entirely so that no
further replies from the earlier address will be delivered to your program.
After you have connected a UDP socket using connect(), you can use the socket’s getpeername() method
to remember the address to which you have connected it. Be careful about calling this on a socket that is not yet
connected. Rather than returning 0.0.0.0 or some other wildcard response, the call will raise socket.error instead.
Two last points should be made about the connect() call.