使用UDP编写的一些常见的应用程序有:DNS(域名系统),NFS(网络文件系统)和SNMP(简单网络管理协议)
UDP客户/服务器交互中发生的典型情形的时间线图。
recvfrom和sendto函数
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);
sendto的to参数指向一个含有数据报接受者的协议地址的套接字地址结构,其大小由addrlen参数指定。recvfrom的from参数指向一个将由该函数在返回时填写数据报发送者的协议地址的套接字地址结构,而在该套接字地址结构中填写的字节数放在addrlen参数所值的证书中返回给调用者。
两个客户发送数据报到UDP服务器的情形
UDP层中隐含有排队发生。每个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字接收缓冲区。
UDP回射服务器程序:main函数
#include "unp.h" void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen); int main(int argc,char *argv[]) { int sockfd; struct sockaddr_in servaddr, clntaddr; 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); bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); dg_echo(sockfd, (SA *) &clntaddr, sizeof(clntaddr)); } void dg_echo(int sockfd, SA *pcliaddr, socklen_t clntlen) { int n; socklen_t len; char mesg[MAXLINE]; while(1) { len = clntlen; n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); //客户端地址和端口好存到pcliaddr指向的结构体 sendto(sockfd, mesg, n, 0, pcliaddr, len); } }
29~31行:recvfrom返回时把客户的IP地址和端口号填入pcliaddr结构,而后作为目的地址传递给sendto的是同一个指针。
UDP回射服务器客户程序:main函数
#include "unp.h" #include "myerr.h" void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen); int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) err_quit("Usage: udpcli <IPaddress> "); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); sockfd = socket(AF_INET, SOCK_DGRAM, 0); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0); } void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; while (fgets(sendline, MAXLINE, fp) != NULL) { sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline[n] = 0; fputs(recvline, stdout); } }
数据报丢失
UDP客户/服务器是不可靠的,如果一个客户数据报丢失,客户将永远阻塞于dg_cli函数中的recvfrom调用,等待一个永远不会到达的服务器应答。类似地,如果客户数据报到达服务器,但是服务器的应答丢失了,客户也将永远阻塞于recvfrom调用。
之后讨论如何给UDP客户/服务器程序增加可靠性。
服务器进程未运行
在不启动服务器的前提下启动客户。客户永远阻塞于它的recvfrom调用,等待一个永不出现的服务器应答。
UDP程序例子小结
客户角度:
以圆点标准了在客户发送UDP数据报时必须指定或选择的四个值。
客户必须给sendto调用指定服务器IP地址和端口号,一般来说,客户的IP地址和端口号由内核自动选择,客户也可以自己调用bind指定。客户的临时端口是在第一次调用sendto时一次性选定,不能改变。
服务器角度:
服务器想从到达的IP数据报中取得至少四条信息:源IP地址,目的IP地址,源端口号和目的端口号。
UDP的connect函数
可以给UDP套接字调用connect。
区分:
--未连接UDP套接字:新创建的UDP套接字默认如此。
--已连接UDP套接字:对UDP套接字调用connect的结果。
已连接的UDP套接字和未连接UDP套接字的区别:
1.不能再给输出操作指定目的IP地址和端口号,不使用sendto,而改用write或send。
2.不必使用recvfrom以获悉数据报的发送者,而改用rend,recv。
3.由已连接UDP套接字引发的异步错误返回给它们的进程,未连接UDP套接字不接受任何异步错误。
性能:
当在一个未连接的UDP套接字上给两个数据报调用sendto函数于是有以下步骤:
而当应用进程知道自己要给同一目的地址发送多个数据报时,显示连接套接字效率更高。调用connect后调用两次write有以下步骤:
UDP套接字接收缓冲区
由UDP给某个特定套接字排队的UDP数据报数目受限于该套接字接收缓冲区的大小。可以使用SO_RCVBUF套接字选项修改该值。