UNP Chapter 8 - 基本UDP套接口编程
2012-01-13 16:41
218 查看
8.1. 概述
有些流行的应用程序是用UDP实现的:DNS(域名系统),NFS(网络文件系统),SNMP(简单网络管理协议)就是这样的例子。
8.2. recvfrom和sendto函数
这两个函数类似于标准的read和write函数,但要求有三个附加参数
前三个参数:sockfd, buff, nbytes等同于read和write的前三个参数:描述字,指向读入或者写出缓冲区的指针,读写字节数。
函数sendto的参数to是一个含有数据将发往的协议地址(例如IP地址和端口号)的套接口地址结构,它的大小由addrlen来指定。函数recvfrom用数据报发送者的协议地址装填由from所指的套接口地址结构,存储在此套接口地址结构中的字节数也以addrlen所指的整数返回给调用者。注意,sendto的最后一个参数是一个整数值,而recvfrom的最后一个参数值是一个指向整数值的指针(值-结果参数)。
recvfrom的最后两个参数类似于accept的最后两个参数:返回时套接口地址结构的内容告诉我们是谁发送了数据报(UDP情况下)或是谁发起了连接(TCP情况下)。sendto的最后两个参数类似于connect的最后两个参数:我们用数据报将发往(UDP情况下)或与之建立连接(TCP情况下)的协议地址来装填套接口地址结构。
写一个长度为0的数据报是可行的,这也意味着对于数据报协议,recvfrom返回0值也是可行的;它不表示对方已经关闭了连接,这与TCP套接口上的read返回0的情况不同。由于UDP是无连接的,这就没有诸如关闭UDP连接之类的事情。
8.3. UDP回射服务器程序:main函数
函数socket第二个参数为SOCK_DGRAM,IPv4协议中的数据报套接口
8.4. UDP回射服务器程序: dg_echo函数
许多问题需要考虑,首先,此函数从不终止,因为UDP是一个无连接协议,它没有像TCP中文件结束符之类的东西。其次,该函数提供一个迭代服务器(iterative server),而不是像TCP一样提供了一个并发服务器。没有对fork的调用,所以第一服务器进程就处理了所有客户。一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。
当进程调用recvfrom时,缓冲区中的下一个数据博以FIFO(先进先出)顺序返回给进程。
8.5. UDP回射客户程序: main函数
8.6. UDP回射客户程序: dg_cli函数
8.7. 数据报的丢失
上面的UDP客户-服务器例子是不可靠的。如果一个客户数据报丢失,客户将永远阻塞于dg_cli中对recvfrom的调用,等待一个永远不会到达的服务器应答。与此相似,如果客户数据报到达服务器,但服务器的应答丢失了,客户也将永远阻塞于recvfrom的调用。
8.8. 验证接收到的相应
8.9. 服务器进程未运行
8.10. UDP程序例子小结
8.11. UDP的connect函数
在8.9节结尾我们提到,除非套接口已连接,否则异步错误是不会返回到UDP套接口的。实际上,我们可以给UDP套接口调用connect,但这样做的结果却与TCP连接毫不相同:没有三路握手过程。内核只是记录对方的IP地址和端口号,它们包含在传递给connect的套接口地址结构中,并立即返回给调用进程。
8.12. dg_cli函数(Revisited)
8.13. UDP缺乏流量控制
现在我们检查无任何流量控制的UDP对数据报传输的影响。首先,我们的函数dg_cli修改为发送固定数目的数据报,它不再从标准输入读。
然后我们修改服务器程序以接收数据报并对接收数目计数。此服务器不再将数据报回射给客户,当我们用终端中断键(SIGINT)终止服务器时,它输出所有接收到数据报的数目并终止。
UDP套接口接收缓冲区
由UDP给特定套接口排队的UDP数据报数目受限于套接口接收缓冲区的大小。我们可以用SO_RCVBUF套接口选项改变此值。
8.14. UDP中的外出接口的确定
已连接UDP套接口还可用来确定用于特定目标的外出接口,这是由于函数connect被应用到UDP套接口时的副作用:内核选择本地IP地址(假设进程并没有调用bind以明确地址指派它)。这个本地IP地址是通过给目的IP地址搜索路由表,然后使用结果接口的主IP地址而选定的。
下面是一个简单的UDP程序,它连接到指定的IP地址并调用getsockname,输出本地IP地址和端口号。
8.15. 使用select函数的TCP和UDP回射服务器程序
现在我们将并发的TCP回射服务器程序和迭代UDP回射服务器程序组合为一个使用select来复用TCP和UDP套接口的单个服务器程序
有些流行的应用程序是用UDP实现的:DNS(域名系统),NFS(网络文件系统),SNMP(简单网络管理协议)就是这样的例子。
8.2. recvfrom和sendto函数
这两个函数类似于标准的read和write函数,但要求有三个附加参数
#include <sys/socket> ssize_t recvfrom(int sockfd, void * buff, size_t nbytes, int flags, struct sockaddr * from, socklen_t * addrlen); // 返回: 读写字节数-成功, -1-出错 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的参数to是一个含有数据将发往的协议地址(例如IP地址和端口号)的套接口地址结构,它的大小由addrlen来指定。函数recvfrom用数据报发送者的协议地址装填由from所指的套接口地址结构,存储在此套接口地址结构中的字节数也以addrlen所指的整数返回给调用者。注意,sendto的最后一个参数是一个整数值,而recvfrom的最后一个参数值是一个指向整数值的指针(值-结果参数)。
recvfrom的最后两个参数类似于accept的最后两个参数:返回时套接口地址结构的内容告诉我们是谁发送了数据报(UDP情况下)或是谁发起了连接(TCP情况下)。sendto的最后两个参数类似于connect的最后两个参数:我们用数据报将发往(UDP情况下)或与之建立连接(TCP情况下)的协议地址来装填套接口地址结构。
写一个长度为0的数据报是可行的,这也意味着对于数据报协议,recvfrom返回0值也是可行的;它不表示对方已经关闭了连接,这与TCP套接口上的read返回0的情况不同。由于UDP是无连接的,这就没有诸如关闭UDP连接之类的事情。
8.3. UDP回射服务器程序:main函数
#include "unp.h" int main(int argc, char * * argv) { int sockfd, struct sockaddr_in servaddr, cliaddr; 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*)&cliaddr, sizeof(cliaddr)); }
函数socket第二个参数为SOCK_DGRAM,IPv4协议中的数据报套接口
8.4. UDP回射服务器程序: dg_echo函数
#include "unp.h" void dg_echo(int sockfd, SA * pcliaddr, socklen_t clilen) { int n; socklen_t len; char mesg[MAXLINE]; for( ; ; ) { len = clilen; n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); Sendto(sockfd, mesg, n, 0, pcliaddr, len); } }
许多问题需要考虑,首先,此函数从不终止,因为UDP是一个无连接协议,它没有像TCP中文件结束符之类的东西。其次,该函数提供一个迭代服务器(iterative server),而不是像TCP一样提供了一个并发服务器。没有对fork的调用,所以第一服务器进程就处理了所有客户。一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。
当进程调用recvfrom时,缓冲区中的下一个数据博以FIFO(先进先出)顺序返回给进程。
8.5. UDP回射客户程序: main函数
#include "unp.h" int main(int argc, char ** argv) { int sockfd; struct servaddr_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); }
8.6. 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]; while(Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline = 0; /* null terninate */ Fputs(recvline, stdout); } }
8.7. 数据报的丢失
上面的UDP客户-服务器例子是不可靠的。如果一个客户数据报丢失,客户将永远阻塞于dg_cli中对recvfrom的调用,等待一个永远不会到达的服务器应答。与此相似,如果客户数据报到达服务器,但服务器的应答丢失了,客户也将永远阻塞于recvfrom的调用。
8.8. 验证接收到的相应
8.9. 服务器进程未运行
8.10. UDP程序例子小结
8.11. UDP的connect函数
在8.9节结尾我们提到,除非套接口已连接,否则异步错误是不会返回到UDP套接口的。实际上,我们可以给UDP套接口调用connect,但这样做的结果却与TCP连接毫不相同:没有三路握手过程。内核只是记录对方的IP地址和端口号,它们包含在传递给connect的套接口地址结构中,并立即返回给调用进程。
8.12. dg_cli函数(Revisited)
#include "unp.h" void dg_cli(FILE * fp, int sockfd, const SA * pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE+1]; Connect(sockfd, (SA*)pservaddr, servlen); while(Fgets(sendline, MAXLINE, fp) != NULL) { Write(sockfd, sendline, strlen(sendline)); n = Read(sockfd, recvline, MAXLINE); recvline = 0; /* null terminate */ Fputs(recvline, stdout); } }
8.13. UDP缺乏流量控制
现在我们检查无任何流量控制的UDP对数据报传输的影响。首先,我们的函数dg_cli修改为发送固定数目的数据报,它不再从标准输入读。
#include "unp.h" #define DNG 2000 /* #datagrams to read */ #define DGLEN 1400 /* length of each datagram */ void dg_cli(FILE * fp, int sockfd, const SA * pservaddr, socklen_t servlen) { int i; char sendline[MAXLINE]; for(i = 0; i < NDG; i++) { Sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen); } }
然后我们修改服务器程序以接收数据报并对接收数目计数。此服务器不再将数据报回射给客户,当我们用终端中断键(SIGINT)终止服务器时,它输出所有接收到数据报的数目并终止。
#include "unp.h" static void recvfrom_int(int); static int count; void dg_echo(int sockfd, SA * pcliaddr, socklen_t clilen) { socklen_t len; char mesg[MAXLINE]; Signal(SIGINT, recvfrom_int); for( ; ; ) { len = clilen; Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); count++; } } static void recvfrom_int(int signo) { printf("\n received %d datagrams \n", count); exit(0); }
UDP套接口接收缓冲区
由UDP给特定套接口排队的UDP数据报数目受限于套接口接收缓冲区的大小。我们可以用SO_RCVBUF套接口选项改变此值。
#include "unp.h" static void recvfrom_int(int); static int count; void dg_echo(int sockfd, SA * pcliaddr, socklen_t clilen) { int n; socklen_t len; Signal(SIGINT, recvfrom_int); n = 240 * 1024; Setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); for( ; ; ) { len = clilen; Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); count++; } } static void recvfrom_int(int signo) { printf("\n received %d datagrams \n", count); exit(0); }
8.14. UDP中的外出接口的确定
已连接UDP套接口还可用来确定用于特定目标的外出接口,这是由于函数connect被应用到UDP套接口时的副作用:内核选择本地IP地址(假设进程并没有调用bind以明确地址指派它)。这个本地IP地址是通过给目的IP地址搜索路由表,然后使用结果接口的主IP地址而选定的。
下面是一个简单的UDP程序,它连接到指定的IP地址并调用getsockname,输出本地IP地址和端口号。
#include "unp.h" int main(int argc, char * * argv) { int sockfd; socklen_t len; struct sockaddr_in cliaddr, servaddr; if(argc != 2) err_quit("uasage: udpcli <IPaddress>"); sockfd = Socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); Connect(sockfd, (SA*)&servaddr, sizeof(servaddr)); len = sizeof(cliaddr); Getsockname(sockfd, (SA*)&cliaddr, &len); printf("local address %s \n", Sock_ntop((SA*)&cliaddr, len)); exit(0); }
8.15. 使用select函数的TCP和UDP回射服务器程序
现在我们将并发的TCP回射服务器程序和迭代UDP回射服务器程序组合为一个使用select来复用TCP和UDP套接口的单个服务器程序
#include "unp.h" int main(int argc, char * * argv) { int listenfd, connfd, udpfd, nreadY, maxfdp1; char mesg[MAXLINE]; pid_t childpid; fd_set rset; ssize_t n; socklen_t len; const int on = 1; struct sockaddr_in cliaddr, servaddr; void sig_chid(int); /* create listening TCP socket */ listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); Bind(listenfd, (SA*)&servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); /* create UDP socket */ udpfd = 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(udpfd, (SA*)&servaddr, sizeof(servaddr));
Signal(SIGCHLD, sig_chld); /* must call waitpid() */ FD_ZERO(&rset); maxfdp1 = max(listenfd, udpfd) + 1; for( ; ; ) { FD_SET(listenfd, &rset); FD_SET(udpfd, &rset); if((nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) { if(errno == EINTR) continue; /* back to for() */ else err_sys("select error"); } if(FD_ISSET(listenfd, &rset)) { len = sizeof(cliaddr); connfd = Accept(listenfd, (SA*)&cliaddr, &len); if((childpid = Fork()) == 0) /* child process */ { Close(listenfd); /* close listening socket */ str_echo(connfd); /* process the request */ exit(0); } Close(connfd); } if(FD_ISSET(udpfd, &rset)) { len = sizeof(cliaddr); n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA*)&cliaddr, &len); Sendto(udpfd, mesg, n, 0, (SA*)&cliaddr, len); } }
相关文章推荐
- 基本UDP套接口编程
- 第8章基本UDP套接口编程
- 第8章 基本UDP套接口编程
- UNP Chapter 4 - 基本TCP套接口编程
- UNIX网络编程读书笔记:基本UDP套接口编程
- 基本UDP套接口编程
- UNPv1第八章:基本UDP套接口编程
- 尽量使用接口来编程等基本技巧
- 基本TCP套接口编程
- UNIX网络编程读书笔记:基本SCTP套接口编程
- 基本TCP套接口编程 socket listen bind conncet accept[转]
- 基本的tcp套接口编程
- 第4章 基本TCP套接口编程
- Unix网络编程卷1第4章 - 基本TCP套接口编程
- UDP套接口编程
- 第四章:基本TCP套接口编程
- 基本TCP套接口编程一
- 在UDP套接口编程中使用connect
- Linux下网络编程的基本认识及相关接口
- UNPv1第四章:基本TCP套接口编程