16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度;
如果校验和出错,就会直接丢弃
UDP传输的过程类似于寄信
*无连接:知道对短的IP和端口号就可以直接进行传输,不需要建立连接;
*不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发送到对方,UDP协议层也不会给应用层返回任何错误信息;
*面向数据报:不能够灵活的控制读写数据的次数和数量;
应用层交给UDP多长的报文,UDP原样发送,既不会查分,也不会合并;
UDP的缓冲区
*UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议,进行后续的传输动作;
*UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的YDO报文就会被丢弃
DP的socket既能读也能写,这个概念叫做全双工
UDP使用注意事项
我们注意到UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大长队是64K(包含UDP首部)。
所以,如果我们需要传输的数据超过64K,那么就需要我们在应用层手动的分包,多次发送,并在接收端手动拼装
基于UDP的应用层协议
*NFS:网络文件系统
*TFTP:简单文件传输协议
*DHCP:动态主机配置协议
*BOOTP:启动协议(用于无盘设备启动)
*DNS:域名解析协议
还包括自己写UDP程序时自定义的应用层协议;
地址转换函数
在我们上节课讲的sockaddr_in结构中的成员struct in_addr sin_addr表示32位IP地址,但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换
字符串转in_addr函数
#include
int inet_aton(const char*strptr,struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family,const char *strptr,void *addrptr);
in_addr转字符串函数
char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family,const void *addrptr,char *strptr,size_t len);
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr还可以转换IPv6的in6_addr,因此函数接口是void *addrptr
关于inet_ntoa
inet_ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存IP的结果,那么是否需要手动释放呢?
man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区,这个时候不需要我们手动释放
那么问题来了,如果我们多次调用这个函数,就会把上一次的结果覆盖掉
所以要是有多个线程调用inet_ntoa就会出现线程安全问题
因此在多线程环境下,就用inet_ntop,这个函数由调用者提供一个缓冲区来保存结果,可以规避线程安全问题
下面我们来编写一个简单的UDP网络程序
服务器端
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("usage %s [IP] [port]\n",argv[0]);//argv[0]是什么
return 1;
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
{
printf("socket error\n");
return 2;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));//将char型的字符串转换为整形的
//in_addr用来表示一个IP地址,其实就是一个32位的数
//inet_addr功能是将一个点分十进制的IP转换为长整数型
//且它返回的地址已经是网络字节序列了,所以不用htonl函数
local.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(sock,(struct sockaddr *)&local,sizeof(local)) < 0)
{
printf("bind error\n");
return 3;
}
char buf[1024];
struct sockaddr_in client;
while(1)
{
socklen_t len = sizeof(client);
buf[0] = 0;//初始化
ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,
(struct sockaddr *)&client,&len);
//ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flag,
//(struct sockaddr *)from,socket_t *fromlen)
//recvfrom函数中参数
//buf:接收缓冲区
//len:缓冲区长度
//flags:调用操作方式
//from:指针,指向装有源地址的缓冲区,用(struct sockaddr*)强转
//fromlen:指针,指向from缓冲区长度值
if(s > 0)
{
buf[s] = 0;//相当于增加了一个\0,且这个数组本身就是char型
//inet_ntoa(struct in_addr in)
//将in_addr类型的参数转换为字符串
printf("[%s|%d]:%s\n",inet_ntoa(client.sin_addr)
,ntohs(client.sin_port),buf);
sendto(sock,buf,strlen(buf),0,(struct sockaddr *)&client,len);
//int sendto(socket s,const void *msg,int len,unsigned int flags,
//const struct sockaddr *to,int tolen)
}
}
close(sock);
return 0;
}
客户端
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("usage %s [IP] [port]\n",argv[0]);//argv[0]是什么
return 1;
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
{
printf("socket error\n");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));//将char型的字符串转换为整形的
//in_addr用来表示一个IP地址,其实就是一个32位的数
//inet_addr功能是将一个点分十进制的IP转换为长整数型
//且它返回的地址已经是网络字节序列了,所以不用htonl函数
server.sin_addr.s_addr = inet_addr(argv[1]);
char buf[1024];
struct sockaddr_in peer;
while(1)
{
socklen_t len = sizeof(peer);
buf[0] = 0;//初始化
printf("please enter#");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s < 0)
{
printf("read error\n");
return 3;
}
buf[s-1] = 0;
sendto(sock,buf,strlen(buf),0,(struct sockaddr *)&server,sizeof(server));
//int sendto(socket s,const void *msg,int len,unsigned int flags,
//const struct sockaddr *to,int tolen)
ssize_t q = recvfrom(sock,buf,sizeof(buf)-1,0,
(struct sockaddr *)&peer,&len);
if(q > 0)
{
buf[q] = 0;
printf("perr return %s\n",buf);
}
}
close(sock);
return 0;
}