UNIX网络编程——基本UDP套接字编程
概述
UDP是无连接不可靠的数据报协议。
使用UDP编写的一些常见的应用程序有:DNS(域名系统)、NFS(网络文件系统)和 SNMP(简单网络管理协议)。
recvfrom 和 sendto函数
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void* buff, size_t nbytes, int flags,
struct sockaddr* from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void* buff, size_t nbytes, int flags,
const struct sockaddr* to, socklen_t addrlen);
均返回:若成功则为读或写的字节数,若出错则为-1
前三个参数 sockfd、buff 和 nbytes 等同于 read 和 write 函数的三个参数:描述符、指向读入或写成缓冲区的指针和读写字节数。
使用sendto、recvfrom发送和接收数据量为0的数据报是允许的。
recvfrom返回0,并不意味着收到对端FIN(sendto,recvfrom使用的场景下没有连接的概念)。
UDP 回射服务器程序:main 函数
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr, cliaddr;
/*创建一个UDP套接字*/
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT); /*SERV_PORT 服务器的众所周知端口*/
bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
/*调用函数 dg_echo 来执行服务器的处理工作*/
dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}
UDP 回射服务器程序:dg_echo 函数
#include "unp.h"
void
dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
/*该函数是一个简单的循环,它使用 recvfrom 读入下一个到达服务器端口的数据报,再使用 sendto 把它发送回发送者*/
for ( ; ;)
{
len = clilen;
n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
sendto(sockfd, mesg, n, 0, pcliaddr, len);
}
}
图中总结了TCP客户/服务器在两个客户与服务器建立连接时情形。
服务器主机上有两个已连接套接字,其中每一个都有各自的套接字接收缓冲区。
下图展示了两个客户发送数据报到UDP服务器的情形。
UDP 回射客户程序: main 函数
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: udpcli <IPaddress>");
/*把服务器的IP地址和端口号填入一个IPv4的套接字地址结构*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
/*创建一个UDP套接字,然后调用dg_cli*/
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));
exit(0);
}
UDP 回射客户程序: dg_cli 函数
#include "unp.h"
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
/*客户处理循环中的四个步骤*/
/*使用 fgets 从标准输入读入一个文本行*/
while (fgets(sendline, MAXLINE, fp) != NULL)
{
/*使用 sendto 将该文本行发送给服务器*/
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
/*使用 recvfrom 读回服务器的回射*/
n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0; /*null terminate*/
/*使用 fputs 把回射的文本行显示到标准输出*/
fputs(recvline, stdout);
}
}
数据报的丢失
如果一个客户数据报丢失(如,被客户主机与服务器主机之间的某个路由器丢弃),客户将永远阻塞于 dg_cli 函数中的 recvfrom 调用,等待一个永远不会到达的服务器应答。
防止这样永久阻塞的一般方法是给客户的 recvfrom 调用设置一个超时。
验证接收到的响应
recvfrom 返回的IP地址(UDP数据报的源IP地址)不是我们所发送数据报的目的IP地址。
保证应答的源地址与请求的目的地址相同的方法:
- 一个解决办法是:得到由 recvfrom 返回的IP地址后,客户通过在DNS中查找服务器主机的名字来验证该主机的域名(而不是它的IP地址)。
- 另一个解决办法是:UDP服务器给服务器主机上配置的每个IP地址创建一个套接字,用bind捆绑每个IP地址到各自的套接字,然后在所有这些套接字上使用 select (等待其中任何一个变得可读),再从可读的套接字给出应答。
学习参考资料:
《UNIX网络编程 卷1:套接字联网API》 第3版