海哥 发表于 2024-12-31 20:15:35

2024.12.30(多点通信)

作业:
1、将广播发送和吸收端实现一遍,完成一个发送端发送信息,对应多个吸收端吸收信息实验。
发送端
#include <myhead.h>

#define PORT 8888
#define IP "192.168.124.255"

int main(int argc, const char *argv[])
{
        //1、将广播发送和接收端实现一遍,
        //完成一个发送端发送信息,对应多个接收端接收信息实验。

        //1.创建套接字
        int oldfd = socket(AF_INET,SOCK_DGRAM,0);
        if(oldfd == -1)
        {
                perror("socket");
                return -1;
        }
        //设置允许广播
        int k = 11;
        if(setsockopt(oldfd,SOL_SOCKET,SO_BROADCAST,&k,sizeof(k))==-1)
        {
                perror("setsockopt");
                return -1;
        }
        printf("允许广播设置成功\n");

        struct sockaddr_in broadcast = {
                .sin_family = AF_INET,
                .sin_port = htons(PORT),         //发送到该端口
                .sin_addr.s_addr = inet_addr(IP)
        };

        char buff;
        while(1)
        {
                fgets(buff,sizeof(buff),stdin);
                sendto(oldfd,buff,sizeof(buff),0,(struct sockaddr *)&broadcast,sizeof(broadcast));
                if(strcmp(buff,"quit")==0)
                {
                        printf("发送端退出\n");
                        break;
                }
        }
        close(oldfd);
        return 0;
}
吸收端
#include <myhead.h>
#define PORT 8888
#define IP "192.168.124.255"

int main(int argc, const char *argv[])
{
        //1.创建套接字
        int oldfd = socket(AF_INET,SOCK_DGRAM,0);
        if(oldfd == -1)
        {
                perror("socket");
                return -1;
        }

        //填充广播地址信息结构体
        struct sockaddr_in send = {
                .sin_family = AF_INET,
                .sin_port = htons(PORT),
                .sin_addr.s_addr = inet_addr(IP)
        };

        if(bind(oldfd,(struct sockaddr *)&send,sizeof(send))==-1)
        {
                perror("bind");
                return -1;
        }

        //接收消息
        char buff;
        while(1)
        {
                recvfrom(oldfd,buff,sizeof(buff),0,NULL,NULL);
                printf("%s",buff);
        }
        return 0;
} 2、利用多线程基于TCP协议的并发执行,一个服务器对应多个客户端实现通信实验。
服务器
#include <myhead.h>
#define PORT 8888
#define IP "192.168.124.123"

void *fun(void *fd)
{
        int newfd = *(int *)fd;
        char buff;
        while(1)
        {
                int res = recv(newfd,buff,sizeof(buff),0);
                if(res == 0)
                {
                        printf("客户端下线\n");
                        break;
                }
                strcat(buff,"霜之哀伤");
                printf("%s\n",buff);
                send(newfd,buff,sizeof(buff),0);
                bzero(buff,sizeof(buff));

        }
        //循环结束(客户端下线)子线程退出
        pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
        //2、使用多线程基于TCP协议的并发执行,
       
        //一个服务器对应多个客户端实现通信实验。
       
        //1.创建套接字
        int oldfd = socket(AF_INET,SOCK_STREAM,0);
        if(oldfd == -1)
        {
                perror("socket");
                return -1;
        }

        //绑定
        struct sockaddr_in aaa = {
                .sin_family = AF_INET,
                .sin_port = htons(PORT),
                .sin_addr.s_addr = inet_addr(IP)
        };
        if(bind(oldfd,(struct sockaddr *)&aaa,sizeof(aaa))==-1)
        {
                perror("bind");
                return -1;
        }

        //监听
        if(listen(oldfd,20)==-1)
                {
                        perror("listen");
                        return -1;
                }
        struct sockaddr_in client;
        int client_len = sizeof(client);
        pthread_t tid;
        int newfd;
        while(1)
        {
                //接收新客户端连入请求
                newfd = accept(oldfd,(struct sockaddr *)&client,&client_len);
                if(newfd == -1)
                {
                        perror("accept");
                        return -1;
                }
                //创建子线程与客户端通话
                if(pthread_create(&tid,NULL,fun,&newfd)==-1)
                {
                        perror("pthread_create");
                        return -1;
                }

        }
        pthread_join(tid,NULL);         //回收子线程资源
        close(newfd);
        close(oldfd);

       
        return 0;
}
客户端
#include <myhead.h>
#define IP "192.168.124.123"
#define PORT 8888

int main(int argc, const char *argv[])
{
        //1.创建套接字
        int oldfd = socket(AF_INET,SOCK_STREAM,0);
        if(oldfd == -1)
        {
                perror("socket");
                return -1;
        }
        //连接服务器
        struct sockaddr_in aaa = {
                .sin_family = AF_INET,
                .sin_port = htons(PORT),
                .sin_addr.s_addr = inet_addr(IP)
        };

        if(connect(oldfd,(struct sockaddr *)&aaa,sizeof(aaa))==-1)
        {
                perror("connect");
                return -1;
        }

        //收发消息
        char buff;
        while(1)
        {
                fgets(buff,sizeof(buff),stdin);
                buff = '\0';
                send(oldfd,buff,strlen(buff),0);
                if(strcmp(buff,"quit")==-1)
                {
                        printf("服务器退出成功\n");
                }
                bzero(buff,sizeof(buff));
                recv(oldfd,buff,strlen(buff),0);                 //阻塞接收服务器消息
                printf("服务器发来消息:%s\n",buff);
        }
        return 0;
}
笔记

1、对于套接字而言,在不同的层中,可以设置不同的属性,如端标语快速重用、超时时间、设置广播、加入多播组等等
2、关于网络属性,有两个函数,分别是 setsockopt、getsockopt
https://i-blog.csdnimg.cn/direct/c8c8f3b243284d279ee777bc2a8870e2.png
           #include <sys/types.h> /* See NOTES */
        #include <sys/socket.h>
        int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
        功能:获取套接字某些选项的属性。
        参数1:套接字描述符
        参数2:要获取的层级
        参数3:要获取的操作名称
        参数4:获取的值为         0:表示禁用,
                                                非0表示启用。
        参数5:参数4的巨细。
        返回值:乐成返回0,失败返回-1,并置位错误码
        int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
        功能:设置套接字某些选项的属性。
        参数1:套接字描述符
        参数2:要设置的层级
        参数3:要设置的操作名称
        参数4:设置的值, 0:表示禁用,
                                        非0表示启用。
        参数5:参数4的巨细。
        返回值:乐成返回0,失败返回-1,并置位错误码
eg:获取当前端标语是否能快速复用属性:
        int n;
        int len = sizeof(n);
        getsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&n,&len);
        如果n==0表示端标语快速复用未启动。
        如果n!=0表示端标语快速复用启动。
        eg:设置当前套接字端标语能快速复用
        int n =999;
        setsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(n));
        启动端标语快速复勤奋能。
#include <myhead.h>

int main(int argc, const char *argv[])
{
    int oldfd = socket(AF_INET,SOCK_STREAM,0);

    int n;
    int len = sizeof(n);
    getsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&n,&len);
    printf("端口号复用状态:%d\n",n);
   
n = 1;
    setsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(n));
    printf("端口号修改为可复用状态后:%d\n",n);
    return 0;
} 2.单播
1>.单播发生在主机之间一对一的通信模式,交换机大概路由器只对数据举行转发,不做复制
2> 每次只有两个实体之间举行相互通信,发送端和吸收端都是唯一确定的
3.广播
1>主机之间的一对多的通信模式,网络对此中的每一台主机发出的信息都举行复制并转发
2>全部主机都可以收到广播消息(无论你是否乐意吸收),所以,广播是基于UDP通信模式
3> 广播地址:网络号 + 255
比方:主机地址为192.168.125.171 ---> 192.168.125.255
4> 广播消息是不能穿过路由器的,也就是说广播消息禁止在外网上举行传播,所以广播只能完成局域网内的多点通信
1、广播的发送端模子 ----> 类似于UDP的客户端
   模式
1> socket 创建套接字
2> setsockopt 设置网络属性,允许广播
3> bind 非必须绑定(绑定的话每次发送端口都是固定的)
4> 填广播地址信息结构体
ip:填广播地址(192.168.125.255)
port:与吸收端保持同等
5> sendto 发送消息
6> close 关闭套接字
#include <myhead.h>


#define PORT 6666
#define IP "192.168.124.255"
int main(int argc, const char *argv[])
{
    //1、创建套接字
    int oldfd = socket(AF_INET,SOCK_DGRAM,0);
    if(oldfd==-1)
    {
      perror("socket");
      return -1;
    }

    //2、设置允许广播
    int k = 999;
    if(setsockopt(oldfd,SOL_SOCKET,SO_BROADCAST,&k,sizeof(k))==-1)
    {
      perror("setsockopt");
      return -1;
    }
    printf("允许广播设置成功\n");
   
    struct sockaddr_in broadcast = {
      .sin_family = AF_INET,
      .sin_port = htons(PORT),//发送到该端口
      .sin_addr.s_addr = inet_addr(IP)
    };
      
    char buff;
    while(1)
    {
      fgets(buff,sizeof(buff),stdin);
      sendto(oldfd,buff,sizeof(buff),0,(struct sockaddr *)&broadcast,sizeof(broadcast));
      if(strcmp(buff,"quit")==0)
      {
            printf("发送端退出\n");
            break;
      }
    }
    close(oldfd);
    return 0;
}
2、广播的吸收端模子 ----> 类似于UDP的服务器端
   模式
1> socket 创建套接字
2> 填充地址信息结构体
ip:广播地址(192.168.125.255)
port:与发送端保持同等
3> bind 绑定端标语与ip地址
4> recvfrom 吸收消息
5> close 关闭套接字
发送源的负担:广播 > 组播 > 单播
特点:
1、广播地址选取broadcast对应的IP
2、端标语固定
3、发送端不需要绑定固定的IP和端标语,只需要向固定的IP和端标语(广播地址)发送信息即可,
不需要关注谁来吸收。
4、吸收端必须绑定IP和端标语(广播地址)
5、如果发送端绑定了IP和端标语之后,吸收端就无法再次绑定,也无法吸收信息。
4、组播(多播)
1、 组播也是实现主机之间一对多的通信模子,跟广播不同的是,组播发送的消息,只有加入多播组的成员才气收到,没有加入的就无法收到,不会占用柜台的网络带宽。
   
2> 组播也是利用UDP实现。
3> 组播地址:就是D类网络,224.0.0.0 -- 239.255.255.255
2、 组播的发送端模子 --->类似于UDP的客户端
   模式
1> socket 创建套接字
2> bind 非必须绑定
3> 填充吸收端地址信息结构体
ip:组播地址,与吸收端保持同等(224.0.0.0 -- 239.255.255.255)
port:与吸收端保持同等
4> sendto 发送组播消息
5> close 关闭套接字
组播发送端:
特点:
1、发送端发送给组播组IP和端标语。
2、发送端不需要绑定本身的IP和端标语,只需要发送到组播地址即可。
#include <myhead.h>
#define PORT 6666
#define IP "192.168.124.172"
int main(int argc, const char *argv[])
{
    //1、创建套接字
    int oldfd = socket(AF_INET,SOCK_DGRAM,0);
    if(oldfd==-1)
    {
      perror("socket");
      return -1;
    }
#if 0
    //2、绑定
    struct sockaddr_in send = {
      .sin_family = AF_INET,
      .sin_port = htons(PORT),
      .sin_addr.s_addr = inet_addr(IP)
    };
    if(bind(oldfd,(struct sockaddr *)&send,sizeof(send))==-1)
    {
      perror("bind");
      return -1;
    }
#endif
    //3、发送接收
    struct sockaddr_in group = {
    .sin_family = AF_INET,
    .sin_port = htons(8888),//组播端口号
    .sin_addr.s_addr = inet_addr("224.1.2.3")//组播IP地址
    };
    char buff;
    while(1)
    {
      fgets(buff,sizeof(buff),stdin);
      buff = '\0';
      sendto(oldfd,buff,sizeof(buff),0,(struct sockaddr *)&group,sizeof(group));
    }
    return 0;
} 3、组播的吸收端模子 ---> 类似于UDP的服务器
   1> socket 创建套接字
2> setsockopt 设置网络属性(加入多播组)
设置层级:IPPROTO_IP
设置属性:IP_ADD_MEMBERSHIP
struct ip_mreqn {
        struct in_addr imr_multiaddr; /* 组播IP*/
        struct in_addr imr_address; /* 本机IP*/
        int imr_ifindex; /* 网卡索引 */ };
3> 填充地址信息结构体然后bind
ip:组播IP,与发送端保持同等
port :与发送端保持同等
4> 界说发送方结构体,吸收发送方信息
5> recvfrom 吸收消息
6> close 关闭套接字
特点:
1、设置允许加入组播组(发送端的IP),设置加入组播组时需要(组播组IP,本身的IP,本身网卡ens33的索引号一样平常都是2)
2、查找本身网卡的索引号:ip addr show大概ip ad。
3、绑定时绑定的是组播组IP和发送端IP保持同等。
4、吸收端也可以选择不吸收发送方信息,recvfron最后两个参数填NULL即可。
#include <myhead.h>
#define ZIP "224.1.2.3"
#define IP "192.168.60.66"
#define PORT 7777
int main(int argc, const char *argv[])
{
    //1、创建基于UDP的套接字
    int oldfd = socket(AF_INET,SOCK_DGRAM,0);
    if(oldfd==-1)
    {
      perror("socket");
      return -1;
    }
   
    //2、设置允许加入组播组
    struct ip_mreqn recv = {
    .imr_multiaddr.s_addr = inet_addr(ZIP),//组播组IP
    .imr_address.s_addr = inet_addr(IP),//本机IP
    .imr_ifindex = 2 //ens33网卡索引号
    };
    if(setsockopt(oldfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&recv,sizeof(recv))==-1)
    {
      perror("setsockopt");
      return -1;
    }
    //3、绑定
    struct sockaddr_in recv2 = {
    .sin_family = AF_INET,
    .sin_port = htons(PORT),
    .sin_addr.s_addr = inet_addr(ZIP)//绑定组播组IP
    };
    if(bind(oldfd,(struct sockaddr *)&recv2,sizeof(recv2))==-1)
    {
      perror("bind");
      return -1;
    }
    //4、接收信息
    struct sockaddr_in send;
    socklen_t send_len = sizeof(send);
    char buff;
    while(1)
    {
      recvfrom(oldfd,buff,sizeof(buff),0,(struct sockaddr *)&send,&send_len);
                //recvfrom(oldfd,buff,sizeof(buff),0,NULL,NULL);不接收发送方的信息
      printf("接收到%s发送来的信息:%s\n",inet_ntoa(send.sin_addr),buff);
      if(strcmp(buff,"quit")==0)
      {
            printf("接收端也退出\n");
            break;
      }
    }
    close(oldfd);
    return 0;
}

思维导图
https://i-blog.csdnimg.cn/direct/3c8e4bfca06b44bc80c8f91e6711bfc5.png
https://i-blog.csdnimg.cn/direct/9f10d7167a894cb18dd2218fc5bd0920.png
https://i-blog.csdnimg.cn/direct/0dbd155dd7e5473d86fed159734d7503.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 2024.12.30(多点通信)