UDP(User Datagram Protocol)是无连接不可靠的数据报协议。使用的UDP的常用应用有:DNS、NFS、SNMP、tftp等。
典型的UDP客户-服务器程序
#include <sys/socket.h>
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 <IP address>");
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 <IP address>");
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 <IP address>");
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 <IP address>");
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 <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <sys/time.h> /* timeval{} for select() */
#include <time.h> /* timespec{} for pselect() */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h> /* for nonblocking */
#include <signal.h>
#include <sys/stat.h> /* for S_xxx file mode constants */
#include <sys/uio.h> /* for iovec{} and readv/writev */
#include <unistd.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/stropts.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 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 <sys/socket.h>, 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网络编程》