udp套接字编程:sendto、recvfrom
如果recvfrom的from参数是一个空指针,那么相应的长度参数(addrlen)也必须是一个空指针,表示我们并不关心数据发送者的协议地址。
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
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(pservaddr, preply_addr, len) != 0)
{
printf("reply from %s (ignored)\n", Sock_ntop(preply_addr, len));
continue;
}
recvline[n] = 0; //null terminate
Fputs(recvline, stdout);
}
}
针对有两个接口和两个IP地址的主机freebsd执行:
$./host freebsd
freebsd.unpbook.com has address 172.31.37.96
freebsd.unpbook.com has address 136.197.16.100
$./cli 136.197.16.100
hello
reply from 172.31.37.96:6 (ignored)
goodbye
reply from 172.31.37.96:6 (ignored)
我们指定的服务器IP地址不与客户主机共享同一个子网。
recvfrom返回的IP地址不是我们所发送数据报的目的IP地址,主机freebsd内核中的路由功能为之选择172.31.37.96作为外出接口。既然服务器没有在其套接字上绑定一个实际的IP地址(服务器绑定在其套接字上的是通配IP地址,这一点可通过在freebsd上运行netstat来验证),因此内核将为封装这些应答的IP数据报选择源地址,选为源地址的是外出接口的主IP地址。
一个解决办法是:得到由recvfrom返回的IP地址后,客户通过在DNS(11)中查找服务器主机的名字来验证该主机的域名(而不是它的IP地址)。
另一个解决办法是:UDP服务器给服务器主机上配置的每个IP地址创建一个套接字,用bind捆绑每个IP地址到各自的套接字,然后在所有这些套接字上使用select(等待其中任何一个变得可读),再从可读的套接字给出应答。既然用于给出应答的套接字上绑定的IP地址就是客户请求的目的IP地址(否则该数据报不会被投递到该套接字),这就保证应答的源地址与请求的目的地址相同(22.6)。
一个基本规则是:对于一个UDP套接字,由它引发的异步错误却并不返回给它,除非它已连接。
我们确实可以给UDP套接字调用connect,然而这样做的结果却与TCP连接大相径庭:没有三路握手过程。内核只是检查是否存在立即可知的错误,记录对端的IP地址和端口号(取自传递给connect的套接字地址结构),然后立即返回到调用进程。
对于已连接套接字:
1.我们再也不能给输出操作指定IP地址和端口号。也就是说,我们不使用sendto,而改用write或send。写道已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址(例如IP地址和端口号)。
2.我们不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。在一个已连接UDP套接字上,由内核为输入操作返回的数据报只有那些来自connect所指定协议地址的数据报。目的地为这个已连接UDP套接字的本地协议地址(例如IP地址和端口号),发源地不是该套接字早先connect到的协议地址的数据报,不会投递到该套接字。这样就限制一个已连接UDP套接字能且仅能与一个对端(更确切地说,是一个IP地址)交换数据报。
3.由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接受任何异步错误。
拥有一个已连接UDP套接字的进程可处于下列两个目的之一再次调用connect:
指定新的IP地址和端口号(对于TCP套接字,connect只能调用一次);断开套接字(地址族成员设置为AF_UNSPEC)
当应用进程知道自己要给同一目的地址发送多个数据报时,显示连接套接字效率更高。
UDP发送端淹没其接收端是轻而易举的事情,例如因套接字缓冲区满而丢弃的数据报
增加缓冲区的大小会稍有改善,不过仍然不能从根本上解决问题
已连接UDP套接字还可用来确定用于某个特定目的地的外出接口,这是由connect函数应用到UDP套接字时的一个副作用造成的:内核选择本地IP地址(假设其进程未曾调用bind显示指派它)。这个本地IP地址通过为目的IP地址搜索路由表得到外出接口,然后选用该接口的主IP地址而选定。
在UDP套接字上调用connect并不给对端主机发送任何消息,它完全是一个本地操作,只是保存对端的IP地址和端口号。
我们还看到,在一个为绑定端口号的UDP套接字上调用connect同时也给该套接字指派一个临时端口。
把TCP回射程序转换成UDP程序比较容易,然而TCP提供的许多功能也消失了:检测丢失的分组并重传,验证响应是否来自正确的对端,等等。
UDP套接字可能产生异步错误,例如服务器主机没有启动应答程序等,它们是在分组发送完一段时间后才报告的错误。TCP套接字总是给应用进程报告这些错误,但是UDP套接字必须已连接才能接收这些错误。
UDP没有流量控制,因此一般UDP程序不用于传送大量数据