1. UDP概述
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。
UDP协议全称是用户数据报协议 ,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层--传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
与所熟知的TCP(传输控制协议)协议一样,UDP协议直接位于IP(网际协议)协议的顶层。根据OSI(开放系统互连)参考模型,UDP和TCP都属于传输层协议。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
UDP协议全称是用户数据报协议 ,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层--传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
与所熟知的TCP(传输控制协议)协议一样,UDP协议直接位于IP(网际协议)协议的顶层。根据OSI(开放系统互连)参考模型,UDP和TCP都属于传输层协议。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
在使用TCP编写的应用程序与使用UDP编写的应用程序之间存在一些本质差异,其原因在于这两个传输层之间的差别:UDP是无连接不可靠的数据报协议,非常不同于TCP提供的面向连接的可靠字节流协议。然而,相比TCP,有些场合确实更适合使用UDP,典型应用程序又:DNS(域名系统)、NFS(网络文件系统)和SNMP(简单网络管理协议)。
2. UDP套接字编程
与面向连接的协议相比,面向无连接协议极为不同。其中一个重要的不同点就是客户端与服务器之间不必建立连接。
对于UDP套接字编程而言,服务器创建套接字后,调用bind()函数将套接字与准备接收数据的接口绑定在一起。和TCP编程不同的是,应用程序不必调用listen()和accept()函数等待客户端的连接。而只需要等待接收数据了。开发UDP套接字应用程序,有两个重要的函数sendto()和recvfrom()。服务器采用recvfrom()来接收来自客户端的数据报,并获得客户端的端地址,之后向客户端发送数据时,采用sendto()函数。
对于UDP套接字编程而言,服务器创建套接字后,调用bind()函数将套接字与准备接收数据的接口绑定在一起。和TCP编程不同的是,应用程序不必调用listen()和accept()函数等待客户端的连接。而只需要等待接收数据了。开发UDP套接字应用程序,有两个重要的函数sendto()和recvfrom()。服务器采用recvfrom()来接收来自客户端的数据报,并获得客户端的端地址,之后向客户端发送数据时,采用sendto()函数。
下图为UDP套接字编程流程图:
从图示中可以明显的看出UDP套接字网络编程与TCP的区别。
3. UDP套接字函数
套接字创建socket()、地址绑定bind()函数与TCP套接字编程相同,具体请参考基本套接字编程(1) -- tcp篇,此处仅介绍消息传输函数sendto()与recvfrom();
3.1 消息发送函数sendto()
函数原型:
#include <sys/socket.h>
ssize_t sendto(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *to , socklen_t addrlen);
<span style="white-space:pre"> </span>返回值:成功返回写的字节数,出错返回-1
函数说明:
sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数buf指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send()。参数to用来指定欲传送的网络地址,结构sockaddr请参考bind()。参数addrlen为sockaddr的结构长度。
参数:
- 前三个参数sockfd , buff 和 nbytes等同于read 和 write函数的三个参数:描述符、指向写出缓冲区的指针和写字节数;
- falgs参数一般置0;
- to参数指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小由addrlen指定;
返回值:
若无错误发生,返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误(-1),应用程序可通过WSAGetLastError()获取相应错误代码。
- WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup();
- WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效;
- WSAEACESS:要求地址为广播地址,但相关标志未能正确设置;
- WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用;
- WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中;
- WSAEFAULT:buf或to参数不是用户地址空间的一部分,或to参数太小(小于sockaddr结构大小);
- WSAENETRESET:由于WINDOWS套接口实现放弃了连接,故该连接必需被复位;
- WSAENOBUFS:WINDOWS套接口实现报告一个缓冲区死锁;
- WSAENOTCONN:套接口未被连接;
- WSAENOTSOCK:描述字不是一个套接口;
- WSAEOPNOTSUPP:已设置了MSG_OOB,但套接口非SOCK_STREAM类型;
- WSAESHUTDOWN:套接口已被关闭。一个套接口以1或2的how参数调用shutdown()关闭后,无法再用sned()函数;
- WSAEWOULDBLOCK:套接口被标志为非阻塞, 但该调用会产生阻塞;
- WSAEMSGSIZE:套接口为SOCK_DGRAM类型,且数据报大于WINDOWS套接口实现所支持的最大值;
- WSAECONNABORTED:由于超时或其他原因引起虚电路的中断;
- WSAECONNRESET:虚电路被远端复位;
- WSAEADDRNOTAVAIL:所指地址无法从本地主机获得;
- WSAEAFNOSUPPORT:所指定地址族中地址无法与本套接口一切使用;
- WSAEDESADDRREQ:需要目的地址;
- WSAENETUNREACH:当前无法从本主机联上网络;
3.2 消息接收函数recvfrom()
函数原型:
#include <sys/socket.h>
ssize_t recvfrom(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *from, socklen_t addrlen);
返回值:成功返回写的字节数,出错返回-1
函数说明:
recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数nbytes为可接收数据的最大长度.参数,flags一般设0,其他数值定义参考recv(),参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数,参数fromlen为sockaddr的结构长度。
参数说明:
- 前三个参数sockfd , buff 和 nbytes等同于read 和 write函数的三个参数:描述符、指向写出缓冲区的指针和写字节数;
- falgs参数一般置0;
- from参数指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小由addrlen指定;
返回值:
同sendto()函数!若无错误发生,返回所接收数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误(-1),应用程序可通过WSAGetLastError()获取相应错误代码。
- WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup();
- WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效;
- WSAEACESS:要求地址为广播地址,但相关标志未能正确设置;
- WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用;
- WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中;
- WSAEFAULT:buf或to参数不是用户地址空间的一部分,或to参数太小(小于sockaddr结构大小);
- WSAENETRESET:由于WINDOWS套接口实现放弃了连接,故该连接必需被复位;
- WSAENOBUFS:WINDOWS套接口实现报告一个缓冲区死锁;
- WSAENOTCONN:套接口未被连接;
- WSAENOTSOCK:描述字不是一个套接口;
- WSAEOPNOTSUPP:已设置了MSG_OOB,但套接口非SOCK_STREAM类型;
- WSAESHUTDOWN:套接口已被关闭。一个套接口以1或2的how参数调用shutdown()关闭后,无法再用sned()函数;
- WSAEWOULDBLOCK:套接口被标志为非阻塞, 但该调用会产生阻塞;
- WSAEMSGSIZE:套接口为SOCK_DGRAM类型,且数据报大于WINDOWS套接口实现所支持的最大值;
- WSAECONNABORTED:由于超时或其他原因引起虚电路的中断;
- WSAECONNRESET:虚电路被远端复位;
- WSAEADDRNOTAVAIL:所指地址无法从本地主机获得;
- WSAEAFNOSUPPORT:所指定地址族中地址无法与本套接口一切使用;
- WSAEDESADDRREQ:需要目的地址;
- WSAENETUNREACH:当前无法从本主机联上网络;
4. UDP回射程序实例
4.1 server.c
<pre name="code" class="cpp">#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/ip.h> const int SERV_PORT = 6000;
const int MAXLINE = 2048;
void dg_echo(int sockfd , struct sockaddr *pcliaddr , socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
for( ; ;)
{
len = clilen;
if((n = recvfrom(sockfd , mesg , MAXLINE , 0 , pcliaddr , &len))<0)
{
perror("recvfrom error");
exit(1);
}//if if((n = sendto(sockfd , mesg , n , 0 , pcliaddr , len)) < 0)
{
perror("sendto error");
exit(1);
}//if
}//for
}
int main(int argc , char **argv)
{
int sockfd;
struct sockaddr_in servaddr , cliaddr;
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0)
{
perror("socket error");
exit(1);
}//if if(bind(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)))
{
perror("bind error");
exit(1);
}//if
dg_echo(sockfd , (struct sockaddr *)&cliaddr , sizeof(cliaddr));
}
4.2 client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h> const int SERV_PORT = 6000;
const int MAXLINE = 2048; void dg_cli(FILE *fp , int sockfd , const struct sockaddr *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)
{
perror("sendto error");
exit(1);
}//if if( ( n = recvfrom(sockfd , recvline , MAXLINE , 0 , NULL , NULL)) < 0)
{
perror("recvfrom error");
exit(1);
}//if
recvline[n] = '\0';
fputs(recvline , stdout);
}//while
} int main(int argc , char **argv)
{
int sockfd , t;
struct sockaddr_in servaddr;
if(argc != 2)
{
perror("usage: udpcli <IPaddress>");
exit(1);
}//if
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if((t = inet_pton(AF_INET , argv[1], &servaddr.sin_addr)) <= 0)
{
perror("inet_pton error");
<span style="white-space:pre"> </span> exit(1);
}//if if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0)
{
perror("socket error");
exit(1);
}//if dg_cli(stdin , sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr));
exit(0);
}