Our UDP client/server example is not reliable. If a client datagram is lost (say it is discarded by some router between the client and server), the client will block forever in its call to recvfrom in the function dg_cli, waiting for a server reply that will never arrive. Similarly, if the client datagram arrives at the server but the server’s reply is lost, the client will again block forever in its call to recvfrom. A typical way to prevent this is to place a timeout on the client’s call to recvfrom.
Verifying Received Response
At the end of Section 8.6(See 8.6.6), we mentioned that any process that knows the client’s ephemeral port number could send datagrams to our client, and these would be intermixed with the normal server replies. What we can do is change the call to recvfrom in Figure 8.8(See 8.6.6) to return the IP address and port of who sent the reply and ignore any received datagrams that are not from the server to whom we sent the datagram. There are a few pitfalls with this, however, as we will see.
#include "unp.h"
void dg_client(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE+1];
socketlen_t len;
struct sockaddr *preply_addr;
while(Fgets(sendline, MAXLINE, fp)!=NULL)
{
Sendto(sockfd, sendline, strlen(sendline),0, pservaddr, servlen);
len=servlen;
n=Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);]
if(len!=servlen||memcmp(pserveraddr, preply_addr, len)!=0)
{
printf("reply from %s (ignored)\n",Sock_ntop(preply_addr, len));
continue;
}
recvlien[n]=0;
Fputs(recvline, stdout);
}
}
Version of dg_cli that verifies returned socket address
The next scenario to examine is starting the client without starting the server. If we do so and type in a single line to the client, nothing happens. The client blocks forever in its call to recvfrom, waiting for a server reply that will never appear. But, this is an example where we need to understand more about the underlying protocols to understand what is happening to our networking application.
The basic rule is that an asynchronous error is not returned for a UDP socket unless the socket has been connected. We will describe how to call connect for a UDP socket in Section 8.11(See 8.6.11). Why this design decision was made when sockets were first implemented is rarely understood. (The implementation implications are discussed on pp. 748–749 of TCPv2.)
Overloading the connect function with this capability for UDP sockets is confusing. If the convention that sockname is the local protocol address and peername is the foreign protocol address is used, then a better name would have been setpeername. Similarly, a better name for the bind function would be setsockname.
With this capability, we must now distinguish between
With a connected UDP socket, three things change, compared to the default unconnected UDP socket:
1.We can no longer specify the destination IP address and port for an output operation. That is, we do not use sendto, but write or send instead. Anything written to a connected UDP socket is automatically sent to the protocol address (e.g., IP address and port) specified by connect.
2.We do not need to use recvfrom to learn the sender of a datagram, but read, recv, or recvmsg instead. The only datagrams returned by the kernel for an input operation on a connected UDP socket are those arriving from the protocol address specified in connect. Datagrams destined to the connected UDP socket’s local protocol address (e.g., IP address and port) but arriving from a protocol address other than the one to which the socket was connected are not passed to the connected socket. This limits a connected UDP socket to exchanging datagrams with one and only one peer.
3.Asynchronous errors are returned to the process for connected UDP sockets.
The corollary, as we previously described, is that unconnected UDP sockets do not receive asynchronous errors.
Calling connect Multiple Times for a UDP Socket
A process with a connected UDP socket can call connect again for that socket for one of two reasons:
The first case, specifying a new peer for a connected UDP socket, differs from the use of connect with a TCP socket: connect can be called only one time for a TCP socket.
To unconnect a UDP socket, we call connect but set the family member of the socket address structure (sin_family for IPv4 or sin6_family for IPv6) to AF_UNSPEC. This might return an error of EAFNOSUPPORT (p. 736 of TCPv2), but that is acceptable. It is the process of calling connect on an already connected UDP socket that causes the socket to become unconnected (pp. 787–788 of TCPv2).