UDP(User Datagram Protocol)是无连接不可靠的数据报协议。使用的UDP的常用应用有:DNS、NFS、SNMP、tftp等。
典型的UDP客户-服务器程序
#include
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *addr, socklen_t addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sendto的最后两个参数类似于connect的最后两个参数:调用时其中套接字地址结构被我们填入数据报将发往(UDP)或与之建立连接(TCP)的协议地址。
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *addr, socklen_t *addrlen);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
recvfrom的最后两个参数类似于accept的最后两个参数:返回时其中套接字地址结构的内容告诉我们是谁发送了数据报(UDP)或是谁发起了连接(TCP)。
(1)UDP层中隐含有排队发生。事实上每个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字接收缓冲区。当进程调用recvfrom时,缓冲区中的下一个数据报以FIFO顺序返回给进程。然而这个缓冲区的大小是有限的。(可以通过SO_RCVBUF进行设置)
(2)知道客户临时端口号的任何进程(无论是与本客户进程相同的主机上还是不同的主机上)都可以向该客户发送数据报,而且这些数据报会与正常的服务器应答混杂。(参见例一)
(3)UDP没有流量控制并且是不可靠的。
一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。
例一:
/* 客户端代码 */ #include "../myunp.h" void UdpClient(int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) { int n = 0; char sendline[MAXLINE] = {0}; char recvline[MAXLINE + 1] = {0}; socklen_t len = 0; struct sockaddr *preplyaddr = NULL; while (fgets(sendline, MAXLINE, stdin) != NULL) { if (sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen) < 0) { printf("sendto error: %s\n", strerror(errno)); return; } len = servlen; if ((n = recvfrom(sockfd, recvline, MAXLINE, 0, preplyaddr, &len)) < 0) { printf("recvfrom error: %s\n", strerror(errno)); return; } recvline[n] = 0; /* null terminate */ fputs(recvline, stdout); } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) { printf("usage: udpcli "); return -1; } memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_aton(argv[1], &servaddr.sin_addr); if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf("sockfd error: %s\n", strerror(errno)); return -1; } UdpClient(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); return 0; }
/* 服务端代码 */ #include "../myunp.h" void UdpEcho(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen) { int n = 0; socklen_t len = 0; char msg[MAXLINE] = {0}; for ( ; ; ) { len = clilen; if ((n = recvfrom(sockfd, msg, MAXLINE, 0, pcliaddr, &len)) < 0) { printf("recvfrom error: %s\n", strerror(errno)); return ; } if (sendto(sockfd, msg, n, 0, pcliaddr, len) < 0) { printf("sendto error: %s\n", strerror(errno)); return ; } } } int main(int argc, int **argv) { int sockfd; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf("socked error: %s\n", strerror(errno)); return -1; } memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("bind error: %s\n", strerror(errno)); return -1; } UdpEcho(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); }
/* 客户端代码 */ #include "../myunp.h" char *sock_ntop(const struct sockaddr *sa, socklen_t len) { char sport[8] = {0}; char *paddr = NULL; static char str[128] = {0}; struct sockaddr_in *sin = (struct sockaddr_in *)sa; paddr = inet_ntoa(sin->sin_addr); if (ntohs(sin->sin_port) != 0) { snprintf(sport, sizeof(sport), ": %d", ntohs(sin->sin_port)); strcat(str, paddr); strcat(str, sport); } return str; } void UdpClient(int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) { int n = 0; char sendline[MAXLINE] = {0}; char recvline[MAXLINE + 1] = {0}; socklen_t len = 0; struct sockaddr *preplyaddr = NULL; while (fgets(sendline, MAXLINE, stdin) != NULL) { if (sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen) < 0) { printf("sendto error: %s\n", strerror(errno)); return; } len = servlen; if ((n = recvfrom(sockfd, recvline, MAXLINE, 0, preplyaddr, &len)) < 0) { printf("recvfrom error: %s\n", strerror(errno)); return; } if (len != servlen || memcmp(pservaddr, preplyaddr, len) != 0) { printf("reply from %s (ingnore)\n", sock_ntop(preplyaddr, len)); continue; } recvline[n] = 0; /* null terminate */ fputs(recvline, stdout); } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) { printf("usage: udpcli "); return -1; } memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(7); inet_aton(argv[1], &servaddr.sin_addr); if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf("sockfd error: %s\n", strerror(errno)); return -1; } UdpClient(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); return 0; }
例三:使用connect函数
/* 客户端代码 */ #include "../myunp.h" char *sock_ntop(const struct sockaddr *sa, socklen_t len) { char sport[8] = {0}; char *paddr = NULL; static char str[128] = {0}; struct sockaddr_in *sin = (struct sockaddr_in *)sa; paddr = inet_ntoa(sin->sin_addr); if (ntohs(sin->sin_port) != 0) { snprintf(sport, sizeof(sport), ": %d", ntohs(sin->sin_port)); strcat(str, paddr); strcat(str, sport); } return str; } int main(int argc, char **argv) { int sockfd; socklen_t len; struct sockaddr_in cliaddr; struct sockaddr_in servaddr; if (argc != 2) { printf("usage: udpcli "); return -1; } if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf("sockfd error: %s\n", strerror(errno)); return -1; } memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_aton(argv[1], &servaddr.sin_addr); if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("connect error: %s\n", strerror(errno)); return -1; } len = sizeof(cliaddr); getsockname(sockfd, (struct sockaddr *)&cliaddr, &len); printf("local address %s\n", sock_ntop((struct sockaddr *)&cliaddr, len)); return 0; }
UDP客户端或服务端进程只在使用自己的UDP套接字与确定的唯一对端进行通信时,才应该调用。
例四:UDP缺乏流量控制
/* 客户端代码 */ #include "../myunp.h" #define SENDCNT 2000 #define DGLEN 1400 void UdpClient(int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) { int i = 0; char sendline[DGLEN]; for (; i < SENDCNT; i++) { if (sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen) < 0) { printf("sendto error: %s\n", strerror(errno)); return; } // printf("i: %d\n", i); } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) { printf("usage: udpcli "); return -1; } memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_aton(argv[1], &servaddr.sin_addr); if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf("sockfd error: %s\n", strerror(errno)); return -1; } UdpClient(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); return 0; }
/* 服务端代码 */ #include "../myunp.h" static void RecvfromInt(int); static int count = 0; static void RecvfromInt(int signo) /* Ctrl + C */ { printf("\nreceived %d datagrams\n", count); exit(0); } void UdpEcho(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen) { int n = 0; socklen_t len = 0; char msg[MAXLINE] = {0}; signal(SIGINT, RecvfromInt); n = 220 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); for ( ; ; ) { len = clilen; if (recvfrom(sockfd, msg, MAXLINE, 0, pcliaddr, &len) < 0) { printf("recvfrom error: %s\n", strerror(errno)); return ; } count++; } } int main(int argc, int **argv) { int sockfd; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf("socked error: %s\n", strerror(errno)); return -1; } memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("bind error: %s\n", strerror(errno)); return -1; } UdpEcho(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); }
#ifndef _MY_UNP_H #define _MY_UNP_H #if 0 typedef uint16_t sa_family_t; typedef uint16_t in_port_t; /* Generic socket address structure (for connect, bind, and accept) */ struct sockaddr { sa_family_t sa_family; /* Protocol family */ char sa_data[14]; /* Address data */ }; typedef uint32_t in_addr_t; // AF_INET struct in_addr { /* IPv4 4-byte address */ in_addr_t s_addr; /* Unsigned 32-bit integer */ }; /* Internet-style socket address structure */ struct sockaddr_in { sa_family_t sin_family; /* Address family (always AF_INET)*/ in_port_t sin_port; /* Port number in network byte order */ struct in_addr sin_addr; /* IP address in network byte order */ unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */ }; // AF_INET6 struct int6_addr { uint8_t s6_addr[16]; /* IPv6 address */ }; struct sockaddr_in6 { sa_family_t sin6_family; /* address family */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* traffic class and flow info */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* set of interfaces for scope */ }; // AF_LOCAL struct sockaddr_un { sa_family_t sun_family; char sum_path[104]; /* null-terminated pathname */ } #endif #include /* basic system data types */ #include /* basic socket definitions */ #include /* timeval{} for select() */ #include /* timespec{} for pselect() */ #include #include #include #include /* for nonblocking */ #include #include /* for S_xxx file mode constants */ #include /* for iovec{} and readv/writev */ #include #include #include #include #include #include #include #include /* Read n bytes from a descriptor */ ssize_t readn(int fd, void *buf, size_t n); /* Write n bytes to a descriptor */ ssize_t writen(int fd, const void *buf, size_t n); /* painfully slow version */ ssize_t readline(int fd, void *buf, size_t maxlen); /* Following could be derived from SOMAXCONN in , but many kernels still #define it as 5, while actually supporting many more */ #define LISTENQ 1024 /* 2nd argument to listen() */ /* Miscellaneous constants */ #define MAXLINE 4096 /* max text line length */ #define BUFFSIZE 8192 /* buffer size for reads and writes */ /* Define some port number that can be used for our examples */ #define SERV_PORT 9877 /* TCP and UDP */ #define SERV_PORT_STR "9877" /* TCP and UDP */ #define UNIXSTR_PATH "/tmp/unix.str" /* Unix domain stream */ #define UNIXDG_PATH "/tmp/unix.dg" /* Unix domain datagram */ /* Following shortens all the typecasts of pointer arguments: */ #define SA struct sockaddr #define max(x, y) ((x) > (y) ? (x) : (y)) #define min(x, y) ((x) > (y) ? (y) : (x)) // char *sock_ntop(const struct sockaddr *sa, socklen_t salen); #endif
#include "myunp.h" /* 内核用于套接字的缓冲区可能已到了极限 */ /* Read n bytes from a descriptor */ ssize_t readn(int fd, void *buf, size_t n) { size_t nleft = n; ssize_t nread = 0; char *ptr = buf; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) { nread = 0; /* call read() again */ } else { return -1; } } else if (nread == 0) { /* EOF */ break; } nleft -= nread; ptr += nread; } return (n - nleft); } /* Write n bytes to a descriptor */ ssize_t writen(int fd, const void *buf, size_t n) { size_t nleft = n; ssize_t nwritten = 0; const char *ptr = buf; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) { nwritten = 0; /* call write() again */ } else { return -1; /* error */ } } nleft -= nwritten; ptr += nwritten; } return n; /* nleft - nwritten? */ } /* painfully slow version */ ssize_t readline(int fd, void *buf, size_t maxlen) { ssize_t n, rc; char c; char *ptr = buf; for (n = 1; n < maxlen; n++) { again: if ((rc = read(fd, &c, 1)) == 1) { *ptr++ = c; if (c == '\n') { break; } } else if (rc == 0) { /* EOF, n - 1 bytes were read */ *ptr = 0; return (n - 1); } else { if (errno == EINTR) goto again; return -1; } } *ptr = 0; return n; }
总结
参考:《UNXI网络编程》