目录
1. UDP简介
2. UDP通信流程
3、UDP的函数接口说明
4、UDP通讯测试代码
1. UDP简介
UDP全称 User Datagram Protocol,即:用户数据报协议。是面向无连接的协议。通常,UDP 通信还会被冠以不可靠的头衔。这里的不可靠指的是:无法可靠地得知对方是否收到数据。
UDP有如下特征:
- 无连接:通信双方不需要事先连接
- 无确认:收到数据不给对方发回执确认
- 不保证有序、丢失不重发
- 采用帧同步的数据报通信方式(即通信双方每次的收发数据量相等)
简单来讲,UDP 类似于寄信,如果两个人除了信件之外没有任何别的通信方式,那么信件寄出去了之后,寄件人是无法得知收件人是否收到信件或者是否已经读取内容的。UDP 的特点是无需连接、无需确认、无需缓冲区和分包序列号,因此 UDP 的效率是比较高的。
UDP适用情况
- 发送小尺寸数据(如对DNS服务器进行IP地址查询时)
- 在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
广播、组播模式
- 即时通讯软件的点对点文本通讯以及音视频通讯
- 流媒体、VOD、VoIP、IPTV等网络多媒体服务中的实时数据传输
2. UDP通信流程
发送方:
- 创建 UDP 套接字:
int fd = socket();
- 准备好接收方的地址:
struct sockaddr_in peerAddr;
- 给对方发送 UDP 数据报:
sendto(fd, peerAddr);
接收方:
- 创建 UDP 套接字:
int fd = socket();
- 准备好自己的地址:
struct sockaddr_in addr;
- 绑定套接字和地址:
bind(fd, addr);
- 坐等各方发来的 UDP 数据报:
recvfrom(fd);
3、UDP的函数接口说明
1、建立套接字(socket)#include #include int socket(int domain,int type,int protocol);函数作用:建立套接字,返回套接字文件描述符函数参数:domain:你要选择哪一种地址族PF_INET/AF_INETIPV4网络协议PF ---> Protocol FamilyPF_INET6/AF_INET6 IPV6网络协议AF ---> Address Familytype:你要选择哪一种协议SOCK_STREAM选择TCP -- 流式套接字 --->Stream SocketsSOCK_DGRAM选择UDP -- 数据报套接字--->Datagram Socketsprotocol:传0表示使用默认协议返回值:成功:套接字文件描述符sockfd失败:-12、绑定主机的IP地址和端口号(bind)#include #include int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);函数作用:绑定主机的IP地址和端口号参数:sockfd:套接字文件描述符 addr:自己的IP地址和端口号 addelen:地址的大小长度返回:成功#include ssize_t sendto(int socket,const void* message,size_t length,const struct sockaddr* dest_addr,socklen_t dest_len);函数作用:用于UDP中发送数据,注意是UDP参数:socket:套接字文件描述符message:你要发送的数据length:你要发送的数据大小,注意有多少写多少strlenflags:一般设置成0dest_addr:对方的IP地址和端口号dest_len:结构体的大小返回值:成功:发送出去的字节失败:-1需要新创建一个接收的结构体存储数据struct sockaddr_in recv_addr;int len = sizeof(struct sockaddr_in);#include ssize_t recvform(int socket,void* buffer,size_t length,int flags,struct sockaddr* dest_addr,socklen_t* address_len);函数作用:用于UDP中接收数据参数:socket:套接字文件描述符buffer:接受的数据存储在这里length:接受的数据的大小,以最大的来接受(sizeof)flags:一般设置成0address:存储客户端的IP地址和端口号,可以获取到是谁给你发送的address_len:结构体的大小返回值:成功:接收到的字节数失败:-1#include uint16_t htons(uint16_t hostshort);//将主机端口号转成网络端口号uint16_t ntohs(uint16_t netshort);//将网络端口号转成主机端口号说明:h代表主机(host) n代表网络(network) s代表端口号(short)返回值:成功:要转换的字节序失败:-1从什么(h,n)端口号到(to)什么(n,h)端口号(s)#include #include #include in_addr_t inet_addr(const char *cp);//将主机IP转成网络IPchar* inet_ntoa(struct in_addr in);//将网络IP转成主机IP主机转网络addr(address) 网络转主机ntoa(network to address)
4、UDP通讯测试代码
//udp_client.c#include #include #include #include #include #include #include #include #define SERVER_IP "192.168.5.184" //-->服务器IP#define SERVER_PORT 60000/*UDP客户端代码实现步骤1、建立套接字2、填充结构体,绑定IP和地址(绑定可有可无)3、发送数据(sendto)4、关闭*/int main(int argc,char** argv){//手动传参的错误提示条件// if(argc != 3)// {// perror("./a.out IP PORT");// return -1;// }int ret = 0;char buf[1024] = { 0 };//1、建立套接字文件描述符//参数:地址族 流式套接字 默认协议int socketfd = socket(AF_INET,SOCK_DGRAM,0);if(socketfd == -1){perror("socket fail");return -1;}//2、填充服务端的结构体,绑定IP和地址struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;//server_addr.sin_port = htons(atoi(argv[2]));//传参方式server_addr.sin_port = htons(SERVER_PORT);//宏定义方式//server_addr.sin_addr.s_addr = inet_addr(argv[1]); //传参方式server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //宏定义方式printf("连接服务器成功[%s][%d]\n",SERVER_IP,SERVER_PORT);//printf("连接服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));//3、发送数据while(1){//缓存区清零bzero(buf,sizeof(buf));//发送数据,计算返回值(buf真实数据大小)scanf("%s",buf);//参数 套接字文件描述符 缓存区 缓存区真实大小 默认为0 (旧结构体指针强转取地址)新结构体的大小ret = sendto(socketfd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));printf("发送数据 ret:%d\n",ret);//做主动退出的判断条件if(!strcmp(buf,"exit"))break;}//4、关闭套接字close(socketfd);return 0;}//udp_server.c#include #include #include #include #include #include #include #include #define SERVER_IP "192.168.5.184" //-->虚拟机或者主机IP#define SERVER_PORT 60000/*UDP服务端(接收端)代码实现步骤1、建立套接字2、填充结构体,绑定IP和地址3、定义接收端的一个结构体用来存放,接收数据(recvfrom)4、关闭*/int main(int argc,char** argv){//手动传参的错误提示条件// if(argc != 3)// {// perror("./a.out IP PORT");// return -1;// }int ret = 0;char buf[1024] = { 0 };//1、建立套接字文件描述符//参数:地址族 数据包套接字 默认协议int socketfd = socket(AF_INET,SOCK_DGRAM,0);if(socketfd == -1){perror("socket fail");return -1;}//设置端口复用int optval = 1;//参数套接字文件描述符网络层 端口复用设置值 设置值的大小setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));//2、填充结构体,绑定IP和地址struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;//server_addr.sin_port = htons(atoi(argv[2]));//传参方式server_addr.sin_port = htons(SERVER_PORT);//宏定义方式//server_addr.sin_addr.s_addr = inet_addr(argv[1]); //传参方式server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //宏定义方式//参数套接字文件描述符 IP和端口号(旧结构体指针强转取地址)新结构体的大小ret = bind(socketfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));if(ret == -1){perror("bind fail");return -1;}printf("绑定服务器IP:%s 端口号PORT:%hu\n",SERVER_IP,SERVER_PORT);//printf("绑定服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));//定义一个接收端的地址,用来存放发送端的地址,另一端的端口号确定struct sockaddr_in recv_addr;int address_len = sizeof(struct sockaddr_in);//3、接收数据while(1){//缓存区清零bzero(buf,sizeof(buf));//接收数据,计算返回值(buf真实数据大小)//参数 套接字文件描述符 缓存区 缓存区大小 默认值 旧结构体指针强转取地址)长度取址ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr*)&recv_addr,&address_len);//解析接收到的地址和端口(难点)char *ip= inet_ntoa(recv_addr.sin_addr);//将网络字节序转换成本机字节序int port = ntohs(recv_addr.sin_port);//将网络端口转换为本机端口printf("[%s][%d]收到数据 buf:%s ret:%d\n",ip,port,buf,ret);//做主动退出的判断条件if(!strcmp(buf,"exit"))break;}//4、关闭套接字close(socketfd);return 0;}