UDP (User Datagram Protocol,用户数据报协议), 是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地(服务器)的地址作为参数。
服务器不接受来自客户的连接,而是只管调用recvfrom函数,等待来自某个客户的数据到达。
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, 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:读写字节数;
第四个参数flags:调用操作方式;
第五个参数from/to:指向数据报发送者或接收者的协议地址(如IP地址和端口号)的套接字地址结构;
第六个参数addrlen:from/to参数的大小。
使用UDP写一个长度为0的数据报是可行的,没有数据的IP数据报只包含一个IP首部(IPv4为20字节,IPv6为40字节)和一个8字节UDP首部。
此时对于数据报协议,recvfrom返回0值是可接受的:它并不像TCP套接字上read返回0值来表示对端已关闭连接。
既然UDP是无连接的,因此也就没有诸如关闭一个UDP连接之类的事情。
如果recvfrom函数的from参数是一个空指针,相应的长度参数addrlen也必须为空指针,表示我们并不关心数据发送者的协议地址。
大多数TCP服务器是并发的(fork),大多数UDP服务器是迭代的。
UDP层中隐含有排队发生,每个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字接收缓冲区。
当进程调用recvfrom时缓冲区中的下一个数据报以FIFO顺序返回给进程,而缓冲区大小是有限的。
从客户角度总结UDP客户/服务器:
从服务器角度总结UDP客户/服务器:
服务器从到达的IP数据报中获取的信息:
UDP服务器:
#include <unp.h> #include <iostream> using namespace std; void dg_echo(int sockfd, SA *cliaddr, socklen_t clilen) { int n; socklen_t len = clilen; char msg[MAXLINE]; for ( ; ; ){ bzero(msg, MAXLINE); if ( (n = recvfrom(sockfd, msg, MAXLINE, 0, cliaddr, &len)) < 0){ cout<<"recvfrom error:"<<errno<<endl; exit(0); } cout<<msg<<endl; if (sendto(sockfd, msg, n, 0, cliaddr, len) < 0){ cout<<"sendto error:"<<errno<<endl; exit(0); } } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr, cliaddr; if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ cout<<"socket error:"<<errno<<endl; exit(0); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(30001); if (bind(sockfd, (SA*)&servaddr, sizeof(servaddr)) < 0){ cout<<"bind error:"<<errno<<endl; exit(0); } dg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr)); return 0; }
UDP客户:
#include <unp.h> #include <iostream> using namespace std; void dg_cli(FILE *fp, int sockfd, SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; while (fgets(sendline, MAXLINE, fp) != NULL){ if (sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen) < 0){ cout<<"sendto error"<<endl; exit(0); } if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0){ cout<<"recvfrom error"<<endl; exit(0); } recvline[n] = 0; fputs(recvline, stdout); bzero(sendline, MAXLINE); bzero(recvline, MAXLINE); } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc !=2){ cout<<"usage: udpcli <IPaddress>"<<endl; exit(0); } if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ cout<<"socket error"<<endl; exit(0); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(30001); if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ cout<<"inet_pton error for "<<argv[1]<<endl; exit(0); } dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr)); return 0; }
发送固定数目的数据报到服务器:
#define NDG 2000 #define DGLEN 1400 void cli_send_n(FILE *fp, int sockfd, SA *pservaddr, socklen_t servlen) { int n; char sendline[DGLEN]; for (i = 0; i < NDG; ++i){ sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen); } }
服务器计数统计接收到的数据报:
static int g_count = 0; static void recvfrom_int(int); void count_dg(int sockfd, SA *cliaddr, socklen_t clilen) { socklen_t len = clilen; char msg[MAXLINE]; for ( ; ; ){ len = clilen; recvfrom(sockfd, msg, MAXLINE, 0, cliaddr, &len); g_count++; } } static void recvfrom_int(int sigint) { printf("\nreceived %d datagrams\n", count); exit(0); }
在FreeBSD下UDP套接字接收缓冲区的默认大小为42080,即30个1400字节数据报的容纳空间。
服务器端增大套接字接收队列大小:
void count_dg(int sockfd, SA *cliaddr, socklen_t clilen) { int n; socklen_t len = clilen; char msg[MAXLINE]; n = 220 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); for ( ; ; ){ len = clilen; recvfrom(sockfd, msg, MAXLINE, 0, cliaddr, &len); g_count++; } }