广播UDP的实现
**1.**DUP简介:
协议UDP:全称 User Datagram Protocol,即:用户数据报协议。是面向无连接的协议。
UDP有如下特征:
1.无连接:通信双方不需要实现连接
2.无确认:收到数据不给对方发回执确认
3.不确保有序、丢失不重发
4.采用帧同步的数据报通信方式
简单来讲,UDP类似于寄信,如果两个人除了信件之外没有任何别的通信方式,那么信件寄出去了之后,寄件人是无法得知收件人是否收到信件或者是否已经读取内容的。UDP的特点是无需连接、无需确认、无需缓冲区和分包序列号,因此UDP的效率是比较高的。
UDP适用情况
发送小尺寸数据(如对DNS服务器进行IP地址查询时)在网络环境比较好的情况下,udp的效率比较高。
UPD工作模式
广播:同时发给局域网中的所有主机
组播:是介于单播与广播之间,在一个局域网内,将某些主机添加到组中,并设置要给组地址,我们只需要将数据发送到组播地址即可,加入到该组的所有主机都能接收到数据
如下图红框选中部分为广播地址 :
2.广播通信过程:
发送方:
1.创建套接字
2.设置为允许进行广播
3.发送数据至广播地址
4.关闭
接收方:
1.创建套接字
2.绑定广播的IP和端口
3.接收数据
4.关闭
3.核心API:
3.1IP地址结构体
struct sockaddr_in addr = {0};
addr.sin_family =AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY) ; //服务器IP
addr.sin_port = htons(PORT);
3.2创建套接字
int socket(int domain, int type,int protocol);
参数
domain- 域,比如因特网AF_INET、UNIX本地域AF_LOCAL等
**type -**套接字类型,比如字节流SOCKET_STREAM、数据报SOCKET_DGRAM等
protocol **-**传输层协议,比如UDP协议IPPROTO_UDP、TCP协议IPPROTO_TCP等返回值
成功返回大于零的套接字文件描述符失败返回-1
3.3绑定地址
int bind(int sockfd,const struct sockaddr *addr, socklen_t addrlen);
参数
**sockfd-**套接字文件描述符
addr - lP地址与端口号,注意需转换成标准地址结构体
**addrlen -**地址长度
返回值
成功返回0
失败返回-1
3.4发送UDP数据
ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数
**sockfd -**套接字文件描述符buf-指向要发送的数据的首地址
**len -**要发送的数据的长度(单位:字节)
**flags -**发送标记,比如带外数据MSG_oOB等,一般设置为0
**dest_addr -**目标地址,包括lP地址和端口号
**addrlen -**目标地址长度
返回值
成功返回已发送的数据(单位:字节)
失败返回-1
3.5接收UDP数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
参数
**sockfd -**套接字文件描述符buf-接收数据缓冲区
**len -**接收数据缓冲区大小(单位:字节)
**flags -**接收标记,比如带外数据MSG_oOB等,一般设置为0
**src_addr -**源端地址,包括IP地址和端口号,不保存源端地址时可设置为NULL
**addrlen -**源端地址长度,不保存源端地址时可设置为NULL
返回值
成功返回已接收的数据(单位:字节)
失败返回-1
4.代码以及实现图:
发送方:
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
发送者:
创建套接字socket();
设置为允许发送广播权限setsockopt()
向广播地址发送数据sendto()
*/
int main(int argc, const char *argv[])
{
if(argc < 3)
{
printf("缺少 IP 和端口号\n");
exit(-1);
}
int sockfd;
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("创建套接字失败");
exit(-1);
}
int on = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
{
perror("设置套接字选项失败");
exit(1);
}
struct sockaddr_in mysockaddr; //创建一个socket地址结构 包含服务器的地址和端口
mysockaddr.sin_family = AF_INET; //IPv4地址通信方式
mysockaddr.sin_port = htons(atoi(argv[2])); //端口号
mysockaddr.sin_addr.s_addr = inet_addr(argv[1]); // 广播地址
socklen_t addrlen = sizeof(mysockaddr); //计算并存储结构体,用于sendto函数调用
char buf[128];
while (1)
{
printf("请输入发送内容:");
fgets(buf, sizeof(buf), stdin);
//发送UDP数据
if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&mysockaddr, addrlen) == -1)
{
perror("发送数据失败");
exit(1);
}
printf("\n");
}
close(sockfd);
return 0;
}
接收方:
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
接收者:
创建套接字socket()
将套接字与广播的信息结构体绑定bind()
接收数据recvfrom()
*/
int main(int argc, const char *argv[])
{
if(argc < 2)
{
printf("缺少端口号\n");
exit(1);
}
int sockfd; //创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("创建套接字失败");
exit(1);
}
struct sockaddr_in mysockaddr; //创建一个socket地址结构 包含服务器的地址和端口
mysockaddr.sin_family = AF_INET; //IPv4地址通信方式
mysockaddr.sin_port = htons(atoi(argv[1])); //端口号
mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 服务器ip
socklen_t addrlen = sizeof(mysockaddr); //计算并存储结构体,用于recvfrom函数调用
// 绑定地址
if(bind(sockfd, (struct sockaddr *)&mysockaddr, addrlen) == -1)
// 套接字文字描述符 ip地址与端口号(强转为地址结构体) 地址长度 //
{
perror("绑定失败");
exit(1);
}
char buf[128];
struct sockaddr_in sendsockaddr;
socklen_t sendaddrlen = sizeof(sendsockaddr);//计算并存储结构体 用于recvfrom函数调用
while (1)
{
//接收UDP数据
if(recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&sendsockaddr, &sendaddrlen) == -1)
{
perror("接收数据失败");
exit(1);
}
printf("[%s - %d]: %s\n", inet_ntoa(sendsockaddr.sin_addr), ntohs(sendsockaddr.sin_port), buf);
}
close(sockfd);
return 0;
}
实现图:
发送方发送数据给接收方显示: