嵌入式养成计划-30-网络编程----多点通信--单播--广播--组播

news/2024/7/7 7:45:59

六十六、多点通信

66.1 网络属性相关函数

  • getsockopt
  • setsockopt
功能:
	获取/设置网络属性;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
       int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数:  
    int sockfd:
    	指定要设置哪个套接字的网络属性;
    int level:
    	指定要控制的层次等级;
		SOL_SOCKET:指定的是应用层 ,通用套接字选项;
		IPPROTO_TCP:指定要控制的是TCP;
		IPPROTO_UDP:UDP
		IPPROTO_IP:  IP
    int optname:
    	指定操作名称,选项名称。
    	在第二个参数指定要层次后,该参数用于指定控制该层次的什么;
    	
       ---- SOL_SOCKET	: man 7 socket ----
        1. SO_BROADCAST     是否允许广播(只有UDP允许广播) ---》optval: int*类型
        2. SO_REUSEADDR     是否允许端口快速重用            ---》optval: int*类型,且是个bool类型;
       ---- IPPROTO_TCP	: man 7 TCP ----
       ---- IPPROTO_UDP	: man 7 UDP ----
       ---- IPPROTO_IP	: man 7 IP ----
        1. IP_ADD_MEMBERSHIP     加入组播(多播组)  ----》optval : struct ip_mreqn*类型
           struct ip_mreqn {
               struct in_addr imr_multiaddr; /* IP multicast group address */
               struct in_addr imr_address;   /* IP address of local interface */
               int            imr_ifindex;   /* interface index */
           };
        
    void *optval:
    	除非有另外说明,否则optval都是int*类型
    	
    socklen_t *optlen/socklen_t optlen:
    	optval指针指向的真实的数据类型大小;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

在这里插入图片描述

  • 示例 :
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        perror("socket");
        return -1;
    }
    printf("socket create success \n");

    socklen_t len;
    int reuse = 100;

    //设置允许端口快速重用
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
    {
        perror("setsockopt");
        return -1;
    }
    printf("设置允许端口快速重用\n");

    //判断默认情况下是否允许端口快速重用
    len = sizeof(reuse);
    if(getsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, &len) < 0)
    {
        perror("getsockopt");
        return -1;
    }
    printf("%d\n", reuse);


    //设置允许广播
    int broad = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_BROADCAST, &reuse, sizeof(reuse)) < 0)
    {
        perror("setsockopt");
        return -1;
    }
    printf("设置允许广播\n");

    //判断默认情况下是否允许广播
    len = sizeof(broad);
    if(getsockopt(sfd, SOL_SOCKET, SO_BROADCAST, &broad, &len) < 0)                
    {
        perror("getsockopt");
        return -1;
    }
    printf("broad = %d\n", broad);

    //获取接收缓冲区的大小
    int rcvbuf;
    len = sizeof(rcvbuf);
    if(getsockopt(sfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) < 0)
    {
        perror("getsockopt");
        return -1;
    }
    printf("rcvbuf = %dbytes %dkb\n", rcvbuf, rcvbuf/1024);

    struct timeval tm;
    len = sizeof(tm);
    if(getsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &tm, &len) < 0)
    {
        perror("getsockopt");
        return -1;
    }
    printf("%lds %ldus\n", tm.tv_sec, tm.tv_usec);

    close(sfd);
    return 0;
}

66.2 多点通信

66.2.1 单播

  • 主机之间一对一的通讯模式,
  • 每次只有两个实体相互通讯,发送方和接收方都是唯一确定。

66.2.2 广播

66.2.2.1 概念

  1. 主机之间一对多的通信模式。
  2. 同一个局域网内的所有主机都可以接收到广播信息,
  3. 禁止广播数据穿过路由器,即限制在当前局域网内。防止影响大面积主机。
  4. 只有UDP才能广播
  5. 广播地址:有效网络号+全是1的主机号。
    1. 192.168.114.83 ----》C类IP地址—》网络号前24bit —》192.168.114.255
    2. 192.168.125.229 ----》C类IP地址—》网络号前24bit —》192.168.125.255
    3. 144.1.2.3 ----》B类IP地址—》网络号前16bit —》144.1.255.255
    4. 255.255.255.255

255.255.255.255:给所有网络中的所有网段发送广播数据,但是由于不能穿过路由器,所以只能发送给当前局域网内主机。

66.2.2.2 广播的发送方流程 ----》类似UDP客户端

  1. socket 创建报式套接字
  2. setsockopt 设置允许广播 level: SOL_SOCKET optname: SO_BROADCAST
  3. bind 非必须绑定
  4. 填充接收方的地址信息,给sendto函数使用
    1. IP广播IP:有效网络号+全是1的主机号 或者 255.255.255.255(需要与接收方绑定时候指定的一致)
      (PS:不允许填0.0.0.0,若接收方发送方均填0.0.0.0,会默认使用127.0.0.1通信)
    2. .PORT:需要与接收方绑定的一致。
  5. sendto 发送数据
  6. close 关闭套接字

66.2.2.3 广播的接收方流程 ----》类似UDP服务器

  1. socket 创建报式套接字
  2. 填充接收方自身的地址信息,给bind函数使用
    1. IP广播IP:有效网络号+全是1的主机号 或者 255.255.255.255 或者 0.0.0.0(任意IP地址)
      1. 0.0.0.0:当调用bind函数绑定后,会将本机所有可用IP地址绑定到套接字上。
      2. 所有可用IP:本机IP(192.168.125.5), 127.0.0.1本地环回IP, 广播IP, 组播IP
    2. PORT:1024~49151范围;
  3. bind 必须绑定
  4. recvfrom 接收数据
  5. close 关闭套接字

66.2.2.4 广播示例代码

  • 发送方
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>                                                                
    #include <unistd.h>
    #include <string.h>
    
    #define ERR_MSG(msg) do{\
        fprintf(stderr, "__%d__ ", __LINE__);\
        perror(msg);\
    }while(0)
    
    #define SER_PORT 8888           //接收方绑定的端口号
    #define SER_IP "192.168.125.255"  //广播IP
    
    int main(int argc, const char *argv[])
    {
        //创建报式套接字
        int cfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(cfd < 0)
        {
            ERR_MSG("socket");
            return -1;
        }
        printf("socket create success cfd=%d\n", cfd);
    
        //设置允许广播
        int broad = 1;
        if(setsockopt(cfd, SOL_SOCKET, SO_BROADCAST, &broad, sizeof(broad)) < 0)
        {
            ERR_MSG("setsockopt");
            return -1;
        }
        printf("设置允许广播成功\n");
    
        //绑定客户端自身的地址信息---》非必须绑定
        //若不绑定则操作系统会给客户端绑定本机IP及随机端口
    
        //填充地址信息结构体给sendto函数使用,想发给谁就填谁的地址信息
        //真实的地址信息结构体根据地址族指定 AF_INET: man 7 IP
        struct sockaddr_in sin;
        sin.sin_family      = AF_INET;          //必须填AF_INET;
        sin.sin_port        = htons(SER_PORT);      //接收方绑定的端口号
        sin.sin_addr.s_addr = inet_addr(SER_IP);    //接收方绑定的IP
    
        char buf[128] = "";
        ssize_t res = 0;
        while(1)
        {
            bzero(buf, sizeof(buf));
            printf("请输入>>> ");
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf)-1] = 0;
    
            //发送数据, 主动发送给指定接收放,例如这里可以主动发给接收方
            if(sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
            {
                ERR_MSG("sendto");
                return -1;
            }
            printf("sendto success\n");
        }
        //关闭套接字
        close(cfd);
    
        return 0;
    }
    
  • 接收方
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>                                                            
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <string.h>
    
    #define ERR_MSG(msg) do{\
        fprintf(stderr, "__%d__ ", __LINE__);\
        perror(msg);\
    }while(0)
    
    #define PORT 8888               //端口号的网络字节序,1024~49151
    #define IP "192.168.125.255"  //广播
    
    int main(int argc, const char *argv[])
    {
        //创建报式套接字
        int sfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sfd < 0)
        {
            ERR_MSG("socket");
            return -1;
        }
        printf("socket create success sfd=%d\n", sfd);
    
        //填充地址信息结构体给bind函数使用
        //真实的地址信息结构体根据地址族指定 AF_INET: man 7 IP
        struct sockaddr_in sin;
        sin.sin_family      = AF_INET;          //必须填AF_INET;
        sin.sin_port        = htons(PORT);      //端口号的网络字节序,1024~49151
        sin.sin_addr.s_addr = inet_addr(IP);    //广播IP
    
        //绑定接收方自身的地址信息
        if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
        {
            ERR_MSG("bind");
            return -1;
        }
        printf("bind success\n");
    
        struct sockaddr_in cin;     //存储发送放的地址信息
        socklen_t addrlen =sizeof(cin);
    
        char buf[128] = "";
        ssize_t res = 0;
        while(1)
        {
            bzero(buf, sizeof(buf));
            //接收数据 --->同时存储这个数据包是从哪里来的,即发送的地址
            res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);
            if(res < 0)
            {
                ERR_MSG("recvfrom");
                return -1;
            }
            printf("[%s:%d] : %s\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
        }
        //关闭套接字
        close(sfd);
        return 0;
    }
    

66.2.3 组播

66.2.3.1 概念

  1. 广播的方式是给同一网段下的所有主机发送数据,过多的广播会占用大量带宽,影响正常通信
  2. 主机之间一对一组的通信方式,只有加入了同一个小组的主机可以接收到此组内的所有数据。
  3. 组播IP:D类IP地址:224.0.0.0~239.255.255.255

66.2.3.2 组播的发送方流程 —》类似UDP客户端

  1. socket 创建报式套接字
  2. bind 非必须绑定
  3. 填充接收方的地址信息,给sendto函数使用
    1. IP:组播IP:224.0.0.0~239.255.255.255
      PS:不允许填0.0.0.0,若接收方发送方均填0.0.0.0,会默认使用127.0.0.1通信)
    2. PORT:需要与接收方绑定的一致。
  4. sendto 发送数据
  5. close 关闭套接字

66.2.3.3 组播的接收方流程 —》类似UDP服务器

  1. socket 创建报式套接字
  2. setsockopt 加入小组(加入多播组)加入的小组与绑定的小组必须一致
    level:IPPROTO_IP
    optname: IP_ADD_MEMBERSHIP
  3. 填充接收方自身的地址信息,给bind函数使用
    1. .IP:组播IP:224.0.0.0-239.255.255.255 或者 0.0.0.0(任意IP地址)
      1. 0.0.0.0:当调用bind函数绑定后,会将本机所有可用IP地址绑定到套接字上。
      2. 所有可用IP:本机IP(192.168.125.5), 127.0.0.1本地环回IP, 广播IP, 组播IP
    2. .PORT:1024~49151范围;
  4. bind 必须绑定
  5. recvfrom 接收数据
  6. close 关闭套接字

66.2.3.4 加入多播组

功能:
	设置网络属性;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数:  
    int sockfd:
    	指定要设置哪个套接字的网络属性;
    int level:
    	指定要控制的层次等级;
        IPPROTO_IP:  IP
    int optname:
    	指定操作名称,选项名称。
    	在第二个参数指定要层次后,该参数用于指定控制该层次的什么;
    	
       ---- IPPROTO_IP : man 7 IP ----
       
        1. IP_ADD_MEMBERSHIP     加入组播(多播组)  ----》optval : struct ip_mreqn*类型
           struct ip_mreqn {
				struct	in_addr imr_multiaddr; /* IP multicast group address */     指定要加入的组播IP的网络字节序
				struct	in_addr imr_address;   /* IP address of local interface */  本机IP的网络字节序
				int		imr_ifindex;   /* interface index */      网络设备索引号(网卡编号)
					1. ifconfig查看本机的网卡名: ens33。终端输入ip ad找到ens33对应的编号:2
					2. if_nametoindex("网卡名"),返回值就是编号。if_nametoindex("ens33")
					3.0,自动(默认网卡)
           };

        
    void *optval:除非有另外说明,否则optval都是int*类型
    socklen_t *optlen/socklen_t optlen:optval指针指向的真实的数据类型大小;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

在这里插入图片描述

66.2.3.5 组播示例代码

  • 发送方
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <string.h>
    
    #define ERR_MSG(msg) do{\
        fprintf(stderr, "__%d__ ", __LINE__);\
        perror(msg);\
    }while(0)                                                                                
    
    #define SER_PORT 8888           //接收方绑定的端口号
    #define SER_IP "224.1.2.3"  //组播IP : 224.0.0.0-239.255.255.255
    
    int main(int argc, const char *argv[])
    {
        //创建报式套接字
        int cfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(cfd < 0)
        {
            ERR_MSG("socket");
            return -1;
        }
        printf("socket create success cfd=%d\n", cfd);
    
        //绑定客户端自身的地址信息---》非必须绑定
        //若不绑定则操作系统会给客户端绑定本机IP及随机端口
    
        //填充地址信息结构体给sendto函数使用,想发给谁就填谁的地址信息
        //真实的地址信息结构体根据地址族指定 AF_INET: man 7 IP
        struct sockaddr_in sin;
        sin.sin_family      = AF_INET;              //必须填AF_INET;
        sin.sin_port        = htons(SER_PORT);      //组播端口
        sin.sin_addr.s_addr = inet_addr(SER_IP);    //组播IP
    
        char buf[128] = "";
        ssize_t res = 0;
        while(1)
        {
            bzero(buf, sizeof(buf));
            printf("请输入>>> ");
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf)-1] = 0;
    
            //发送数据, 主动发送给指定接收放,例如这里可以主动发给接收方
            if(sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
            {
                ERR_MSG("sendto");
                return -1;
            }
            printf("sendto success\n");
        }
        //关闭套接字
        close(cfd);
        return 0;
    }
    
  • 接收方
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>                                                                   
    #include <netinet/in.h>
    #include <unistd.h>
    #include <string.h>
    
    #define ERR_MSG(msg) do{\
        fprintf(stderr, "__%d__ ", __LINE__);\
        perror(msg);\
    }while(0)
    
    #define PORT 8888               //端口号的网络字节序,1024~49151
    #define GRP_IP "224.1.2.3"          //组播IP 224.0.0.0-239.255.255.255
    #define LOL_IP "192.168.125.5"      //ifconfig 的本机IP
    
    int main(int argc, const char *argv[])
    {
        //创建报式套接字
        int sfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sfd < 0)
        {
            ERR_MSG("socket");
            return -1;
        }
        printf("socket create success sfd=%d\n", sfd);
    
        //加入多播组
        struct ip_mreqn mq;
    
        mq.imr_multiaddr.s_addr = inet_addr(GRP_IP);    //组播IP
        mq.imr_address.s_addr   = inet_addr(LOL_IP);    //本机IP,ifconfig
        mq.imr_ifindex = 2;         //网络设备索引号
    
        if(setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mq, sizeof(mq)) < 0)
        {
            ERR_MSG("setsockopt");
            return -1;
        }
        printf("加入多播组 [%s] 成功\n", GRP_IP);
    
        //填充地址信息结构体给bind函数使用
        //真实的地址信息结构体根据地址族指定 AF_INET: man 7 IP
        struct sockaddr_in sin;
        sin.sin_family      = AF_INET;          //必须填AF_INET;
        sin.sin_port        = htons(PORT);      //端口号的网络字节序,1024~49151
        sin.sin_addr.s_addr = inet_addr(GRP_IP);    //组播IP 
    
        //绑定接收方自身的地址信息
        if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
        {
            ERR_MSG("bind");
            return -1;
        }
        printf("bind success\n");
    
        struct sockaddr_in cin;     //存储发送放的地址信息
        socklen_t addrlen =sizeof(cin);
    
        char buf[128] = "";
        ssize_t res = 0;
        while(1)
        {
            bzero(buf, sizeof(buf));
            //接收数据 --->同时存储这个数据包是从哪里来的,即发送的地址
            res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);
            if(res < 0)
            {
                ERR_MSG("recvfrom");
                return -1;
            }
            printf("[%s:%d] : %s\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
        }
        //关闭套接字
        close(sfd);
        return 0;
    }
    

http://lihuaxi.xjx100.cn/news/1601023.html

相关文章

微信小程序:实现列表单选

效果 代码 wxml <view class"all"><view class"item_all" wx:for"{{info}}" wx:key"index"><view classposition {{item.checked?"checked_parameter":""}} data-id"{{item.employee_num}}…

Redis AOF重写原原理

重写aof之前 appendonly.aof.1.base.aof appendonly.aof.1.incr.aof appendonly.aof.manifest 重写aof 一次 appendonly.aof.2.base.aof 大小变化 appendonly.aof.2.incr.aof 大小o appendonly.aof.manifest 大小不变 AOF文件重写并不是对原文件进行重新整理&#xff0c;而是直…

linux单机部署kafka

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、环境准备1.jdk版本2.kafka安装包3.可视化工具 二、安装部署1.配置文件修改2.启动 结尾 前言 Kafka是一个分布式的流处理平台。kafka主要是作为一个分布式的…

串联起深度学习的整体,以及其他领域

1、从模型拟合&#xff08;收敛&#xff09;数据关系出发&#xff1a; 2、f从简单的一层和两层连接开始&#xff0c;发展&#xff1b;被表示成 3、如何判断收敛&#xff1a;,即目标函数 4、如何界定任务&#xff1a;&#xff0c;表示什么&#xff1f;表示什么&#xff1f;&a…

大屏设计器项目部署详细步骤

一.项目效果图 二.部署步骤 1.nginx配置前端配置 #gzip on;server {listen 48009;server_name analyse;location / {root /home/designer/dist;index index.html;try_files $uri

百度将在世界大会上发布AI大模型文心4.0;OpenAI考虑自主开发AI芯片

&#x1f989; AI新闻 &#x1f680; 百度将在世界大会上发布AI大模型文心4.0 摘要&#xff1a;百度将于10月17日在北京首钢园举办的百度世界大会上发布AI大模型文心4.0。据消息人士透露&#xff0c;文心4.0将是基础模型的大升级&#xff0c;核心能力将在逻辑推理、代码和数学…

爱国者的润学日记-十月

首先需要科学的准备面试和润。如何进行科学的准备工作呢&#xff1f; 高效的按照面试考察内容进行针对性训练&#xff0c;按 Machine-learning-interview准备保证处于专注的心态&#xff0c;如今互联网娱乐发达&#xff0c;之前即使比赛时我也是一边比赛一边看视频。之后准备面…

Android原生实现控件Ripple方案(API28及以上)

Android控件的水波纹效果的实现方式有很多种&#xff0c;比如使用ripple文件&#xff0c;这里介绍一下另一种Android原生的水波纹实现方案&#xff08;API28及以上&#xff09;。 我们利用RippleDrawable来实现一个带Ripple的Button。RippleDrawable可以通过xml 中定义 ripple…