1、广播 broadcast 
 
    广播是指向同一个网络中所有的主机传输数据只有传输层协议为 UDP协议时,才支持广播 
     TCP是端对端,广播是一对多 ,所以无法符合其要求。
1)广播地址
        广播地址的计算: 
                 子网掩码取反 再和 ip地址 进行按位或运算 
                例子: ip : 192.168.31.104netmask : 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.104netmask : 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 );}
