广播和多播就是发送方向多个接收方的主机发送消息,也就是一对多,广播是给所有的主机发送消息,只能用在局域网中;多播是给一个多播组中的所有主机发送消息,既可以用于广域网,也可以用于局域网;由于都是一对多,所以TCP的端对端的单播协议明显不适用,而只能用无连接不可靠的UDP协议
向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1。
a.只能在局域网中使用。
b.客户端需要绑定服务器广播使用的端口,才可以接收到广播消息。
// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
- sockfd : 文件描述符
- level : SOL_SOCKET
- optname : SO_BROADCAST
- optval : int类型的值,为1表示允许广播
- optlen : optval的大小
// server.cpp
#include
#include
using namespace std;
#include
#include
#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16
// 广播的IP地址
const char* Broadcast_IP = "127.255.255.255";
int main() {
// 1.创建通信的socket套接字
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == socket_fd) {
perror("socket");
return -1;
}
// 开启广播设置
int _optval = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &_optval, sizeof(_optval));
// 2.绑定IP和端口,其实在这里我们不接受数据,帮不绑定其实无所谓
struct sockaddr_in server_addr;
// 地址族
server_addr.sin_family = AF_INET;
// IP
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);
// 端口
server_addr.sin_port = htons(9999);
int ret = bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (-1 == socket_fd) {
perror("bind");
return -1;
}
printf("server has initialized.\n");
// 封装广播客户端的socket地址
struct sockaddr_in All_Client_addr;
All_Client_addr.sin_family = AF_INET;
All_Client_addr.sin_port = htons(10000);
inet_pton(AF_INET, Broadcast_IP, &All_Client_addr.sin_addr.s_addr);
// 3.开始通信
static int num = 0;
char buf[MAX_BUF_SIZE] = {0};
while (1) {
// 服务端向所有的客户端广播数据
bzero(buf, sizeof(buf));
sprintf(buf, "hello , i am server , num = %d\n", num++);
printf("send : %s", buf);
sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&All_Client_addr, sizeof(All_Client_addr));
sleep(1);
}
// 4.关闭套接字
close(socket_fd);
return 0;
}
// client.cpp
#include
#include
using namespace std;
#include
#include
#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16
int main() {
// 1.创建通信的socket套接字
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == socket_fd) {
perror("socket");
return -1;
}
// 2.绑定端口信息,让发送方能够正确找到
struct sockaddr_in client_addr;
// 地址族
client_addr.sin_family = AF_INET;
// IP
// inet_pton(AF_INET, "127.0.0.2", &client_addr.sin_addr.s_addr); // 这行代码会出问题,但是我也不知道为什么
client_addr.sin_addr.s_addr = INADDR_ANY;
// 端口
client_addr.sin_port = htons(10000);
int ret = bind(socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
if (-1 == ret) {
perror("bind");
return -1;
}
char buf[MAX_BUF_SIZE] = {0};
// 2.开始通信
while (1) {
// 读数据
recvfrom(socket_fd, buf, sizeof(buf) - 1, 0, nullptr, nullptr);
printf("recv : %s", buf);
}
// 4.关闭套接字
close(socket_fd);
return 0;
}
我们的代码需要做的功能是服务端启动后,即可开始向局域网内的所有主机广播信息,当有客户端连接进来的时候可以收到客户端的信息
我们先来解释bind()函数,为什么这里服务端和客户端都使用了bind()?
bind()函数可以给我们socket()创建出来的文件描述符绑定我们自己设定的IP和端口信息,比如这里我就给服务端绑定了"127.0.0.1"和9999的信息,客户端绑定了任意IP(局域网内)和10000端口,IP是次要的,bind()函数绑定socket的时候应该首先考虑到给优先接受数据的一方绑定,比如这里就是客户端,为什么呢?因为我发送方一定需要知道一个具体的端口号我才能发送,在UDP中IP倒不一定必须,因为有可能是广播或者组播,这就不是一个具体的IP了,但是端口号是标识不同主机的进程的,所以发送方一定是根据这个端口号找到你对应的进程的,然后如果我得客户端不绑定,就由系统给我自动分配,那就找不到了,所以这里其实服务端的绑定其实没有必要,但是为了习惯我还是加上了;在TCP中也是一样的,我客户端先向服务端发送数据,在这之前需要建立连接,我也是通过人为指定的端口连接服务端,所以服务端绑定了端口,也就调用了bind()
但是这里我不明白我给客户端指定IP为 127.0.0.2 收不到服务端广播的消息,必须是局域网内的任意IP,也就是INADDR_ANY才行,这里我不明白
另外还有一点就是广播的发送方要给socket()设置广播属性,就像这样
// 开启广播设置
int _optval = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &_optval, sizeof(_optval));
单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。 单播和广播是寻址方案的两个极端(要么单个要么全部),多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收,也就是说由运行相应多播会话应用系统的主机上的接口接收。另外,广播一般局限于局域网内使用,而多播则既可以用于局域网,也可以跨广域网使用。
a.组播既可以用于局域网,也可以用于广域网
b.客户端需要加入多播组,才能接收到多播的数据
IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它的范围从 224.0.0.0 到 239.255.255.255 , 并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:
多播的API用的比较少,需要用的时候来查询就可以了,但是要知道工作原理
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
// 服务器设置多播的信息,外出接口
- level : IPPROTO_IP
- optname : IP_MULTICAST_IF
- optval : struct in_addr
// 客户端加入到多播组:
- level : IPPROTO_IP
- optname : IP_ADD_MEMBERSHIP
- optval : struct ip_mreq
struct ip_mreq {
/* IP multicast address of group. */
struct in_addr imr_multiaddr; // 组播的IP地址
/* Local IP address of interface. */
struct in_addr imr_interface; // 本地的IP地址
};
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
// server.cpp
#include
#include
using namespace std;
#include
#include
#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16
// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";
int main() {
// 1.创建通信的socket套接字
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == socket_fd) {
perror("socket");
return -1;
}
// 设置多播属性,设置外出接口
struct in_addr _optval;
// 初始化多播地址
inet_pton(AF_INET, Multicast_IP, &_optval.s_addr);
setsockopt(socket_fd, IPPROTO_IP, IP_MULTICAST_IF, &_optval, sizeof(_optval));
// 发送方,这里我就不绑定端口了
printf("server has initialized.\n");
// 封装广播客户端的socket地址
struct sockaddr_in All_Client_addr;
All_Client_addr.sin_family = AF_INET;
All_Client_addr.sin_port = htons(10000);
inet_pton(AF_INET, Multicast_IP, &All_Client_addr.sin_addr.s_addr);
// 3.开始通信
static int num = 0;
char buf[MAX_BUF_SIZE] = {0};
while (1) {
// 服务端向所有的客户端广播数据
bzero(buf, sizeof(buf));
sprintf(buf, "hello , i am server , num = %d\n", num++);
printf("send : %s", buf);
sendto(socket_fd, buf, strlen(buf), 0, (struct sockaddr*)&All_Client_addr, sizeof(All_Client_addr));
sleep(1);
}
// 4.关闭套接字
close(socket_fd);
return 0;
}
// client.cpp
#include
#include
using namespace std;
#include
#include
#define MAX_BUF_SIZE 1024
#define MAX_IPV4_STRING 16
// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";
int main() {
// 1.创建通信的socket套接字
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == socket_fd) {
perror("socket");
return -1;
}
// 加入多播组
struct ip_mreq _optval;
// 初始化
_optval.imr_interface.s_addr = INADDR_ANY;
inet_pton(AF_INET, Multicast_IP, &_optval.imr_multiaddr.s_addr);
setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &_optval, sizeof(_optval));
// 2.绑定端口信息,让发送方能够正确找到
struct sockaddr_in client_addr;
// 地址族
client_addr.sin_family = AF_INET;
// IP
// inet_pton(AF_INET, "127.0.0.2", &client_addr.sin_addr.s_addr); // 和之前一样的问题
client_addr.sin_addr.s_addr = INADDR_ANY;
// 端口
client_addr.sin_port = htons(10000);
int ret = bind(socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
if (-1 == ret) {
perror("bind");
return -1;
}
char buf[MAX_BUF_SIZE] = {0};
// 2.开始通信
while (1) {
// 读数据
recvfrom(socket_fd, buf, sizeof(buf) - 1, 0, nullptr, nullptr);
printf("recv : %s", buf);
}
// 4.关闭套接字
close(socket_fd);
return 0;
}
同样的客户端也能收到服务端发送而来的数据
我们同样注意服务端和客户端对于设置多播和加入多播的设置方法
服务端
// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";
// 设置多播属性,设置外出接口
struct in_addr _optval;
// 初始化多播地址
inet_pton(AF_INET, Multicast_IP, &_optval.s_addr);
setsockopt(socket_fd, IPPROTO_IP, IP_MULTICAST_IF, &_optval, sizeof(_optval));
客户端
// 多播的IP地址
const char* Multicast_IP = "239.0.0.10";
// 加入多播组
struct ip_mreq _optval;
// 初始化
_optval.imr_interface.s_addr = INADDR_ANY;
inet_pton(AF_INET, Multicast_IP, &_optval.imr_multiaddr.s_addr);
setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &_optval, sizeof(_optval));