1、广播 broadcast
广播是指向同一个网络中所有的主机传输数据只有传输层协议为 UDP协议时,才支持广播
TCP是端对端,广播是一对多 ,所以无法符合其要求。
1)广播地址
广播地址的计算:
子网掩码取反 再和 ip地址 进行按位或运算
例子:
ip : 192.168.31.104
netmask : 255.255.255.0
===>
广播地址: 192.168.31.255
ip : 172.4.0.1
netmask :255.255.254.0
===>
广播地址: 172.4.1.255
ip : 192.168.31.104
netmask : 255.255.128.0
===>
广播地址: 192.168.127.255
全网广播地址: 255.255.255.255
这个没有意义 --> 会造成网络风暴
2)广播的编程流程 (与UDP编程流程类似)
广播的发送者
广播接接收者
3)注意:设置套接字选项
谁需要用到广播, 谁就调用 setsockopt() 函数,设置套接字选项 (详细见表)
使能广播:
级别: SOL_SOCKET
选项: SO_BROADCAST
类型: int
0 禁用
非0 使能
写一个程序,实现 广播的发送和接收
broadcast_sender.c / broadcast_receiver.c
broadcast_sender.c 发送者
int main( int argc, char *argv[] )
{
//创建套接字 UDP
int sock_fd = socket( AF_INET, SOCK_DGRAM, 0 );
if( sock_fd == -1 )
{
perror( "socket error " );
return -1;
}
printf("sock_fd = %d\n", sock_fd );
//设置套接字的选项 --> 使能广播
int n = 1;
setsockopt( sock_fd, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n) );
//设置广播的ip地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET; //协议族 ipv4
addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &addr.sin_addr ); //广播地址
//发送广播数据
while( 1 )
{
//输入要发送的数据
char buf[128] = {0};
printf("input data : ");
fgets( buf, sizeof(buf), stdin );
int re = sendto( sock_fd, buf, strlen(buf), 0, (struct sockaddr*)&addr, sizeof(addr) );
if( re == -1 )
{
perror( "sendto error " );
break;
}
//人为定义退出条件
if( buf[0] == '#' )
{
break;
}
}
//关闭套接字
close( sock_fd );
}
broadcast_receiver.c 接收者(服务器)
int main( int argc, char *argv[] )
{
//创建套接字 UDP
int sock_fd = socket( AF_INET, SOCK_DGRAM, 0 );
if( sock_fd == -1 )
{
perror( "socket error " );
return -1;
}
printf("sock_fd = %d\n", sock_fd );
//设置套接字的选项 --> 使能广播
int n = 1;
setsockopt( sock_fd, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n) );
//设置接收广播的ip地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET; //协议族 ipv4
addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &addr.sin_addr ); //广播地址
//绑定套接字
int re = bind( sock_fd, (struct sockaddr*)&addr, sizeof(addr) );
if( re == -1 )
{
perror( "bind error " );
close( sock_fd );
return -1;
}
printf("bind success\n");
//接收广播数据
while( 1 )
{
//接收数据
char buf[128] = {0};
struct sockaddr_in from_addr;
socklen_t len = sizeof(from_addr);
re = recvfrom( sock_fd, buf, sizeof(buf), 0, (struct sockaddr*)&from_addr, &len );
if( re > 0 )
{
printf("%s : %s\n", inet_ntoa(from_addr.sin_addr), buf );
}
else
{
perror( "recvfrom error " );
break;
}
//人为定义退出条件
if( buf[0] == '#' )
{
break;
}
}
//关闭套接字
close( sock_fd );
}
2、组播 (多播) multicast
组播是指 将数据发送给 加入到某个组中的主机上
特点:
1)只有传输层协议为 UDP协议时,才支持组播功能
2)组播地址 ipv4 D类地址
D类地址: 1110 + 多播组号(28bits)
224.0.0.0 ~ 239.255.255.255
3)广播方式 占用带宽,会造成网络风暴
组播是一种折中的方式,只有加入到特定的 某个多播组的主机 才能收到数据
1)多播的代码实现
多播的发送者
多播的接收者
2)加入多播组
设置套接字的选项:
级别: IPPROTO_IP
选项: IP_ADD_MEMBERSHIP
类型: struct ip_mreqn {}
========================================
man 7 ip 进行查看
struct ip_mreqn
{
struct in_addr imr_multiaddr; /* 多播组地址(D类地址) (类似于qq群号)IP multicast group address */
struct in_addr imr_address; /* 接口(网卡)的地址,多播的数据实际走哪个网卡(类似于qq号) IP address of local interface */
...
};
struct in_addr
{
uint32_t s_addr; /* 32位IP地址 */
};
例子:
把 本机ip 加入多播组 224.0.0.1
struct ip_mreqn mreq;
mreq.imr_multiaddr.s_addr = inet_addr( "224.0.0.1" ); //多播组地址
mreq.imr_address.s_addr = htonl( INADDR_ANY ); //接口地址
//mreq.imr_address.s_addr = inet_addr( argv[1] );
setsockopt( sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq) );
注意:
要支持多播,需要设置路由表,让数据包从正确的网卡出去,而不是从默认网卡出去
首先 ifconfig 查看本机ip和网卡名字 (如: eth0, eth1, ens33, ... )
sudo route add -net 224.0.0.0 netmask 240.0.0.0 ens33 //加入路由表
sudo route add default gw 172.4.1.1 dev ens33 //设置默认网关
查看内核的IP路由标
route -n
练习:
写一个程序,实现多播的功能
multicast_sender.c / multicast_receiver.c
multicast_sender.c 发送者
multicast_receiver.c 接收者
multicast_sender.c 发送者
int main( int argc, char *argv[] )
{
//创建套接字 UDP
int sock_fd = socket( AF_INET, SOCK_DGRAM, 0 );
if( sock_fd == -1 )
{
perror( "socket error " );
return -1;
}
printf("sock_fd = %d\n", sock_fd );
//设置 多播的ip和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET; //协议族 ipv4
addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &addr.sin_addr ); //多播地址 224.0.0.1
//发送数据
while(1)
{
//发送数据
char buf[128] = {0};
fgets( buf, sizeof(buf), stdin );
int re = sendto( sock_fd, buf, strlen(buf), 0, (struct sockaddr*)&addr, sizeof(addr) );
if( re == -1 )
{
perror( "sendto error " );
break;
}
if( buf[0] == '#' )
{
break;
}
}
//关闭套接字
close( sock_fd );
}
multicast_receiver.c 接收者
int main( int argc, char *argv[] )
{
//创建套接字 UDP
int sock_fd = socket( AF_INET, SOCK_DGRAM, 0 );
if( sock_fd == -1 )
{
perror( "socket error " );
return -1;
}
printf("sock_fd = %d\n", sock_fd );
//设置套接字的选项 --> 把本机ip加入到多播组 224.0.0.1
struct ip_mreqn mreq;
mreq.imr_multiaddr.s_addr = inet_addr( argv[1] ); //多播组地址 224.0.0.1
mreq.imr_address.s_addr = htonl( INADDR_ANY ); //接口地址 (本机地址)
int re = setsockopt( sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq) );
if( re == -1 )
{
perror("add membership error ");
close( sock_fd );
return -1;
}
printf("add membership success\n");
//设置 多播的ip和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET; //协议族 ipv4
addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &addr.sin_addr ); //多播地址 224.0.0.1
//设置端口号重用
int n = 1;
setsockopt( server_sock, SOL_SOCKET, SO_REUSEPORT, &n, sizeof(n) );
//绑定套接字
re = bind( sock_fd, (struct sockaddr*)&addr, sizeof(addr) );
if( re == -1 )
{
perror( "bind error " );
close( sock_fd );
return -1;
}
printf("bind success\n");
//接收数据
while(1)
{
//接收数据
char buf[128] = {0};
struct sockaddr_in from_addr;
socklen_t len = sizeof(from_addr);
re = recvfrom( sock_fd, buf, sizeof(buf), 0, (struct sockaddr*)&from_addr, &len );
if( re > 0 )
{
printf("%s : %s\n", inet_ntoa(from_addr.sin_addr), buf );
}
else
{
perror( "recvfrom error " );
break;
}
//人为定义退出条件
if( buf[0] == '#' )
{
break;
}
}
//关闭套接字
close( sock_fd );
}