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[1024];
	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[1024];
	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[1024];
	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[1024];
	while(1)
	{
		fgets(buff,sizeof(buff),stdin);
		buff[strlen(buff)-1] = '\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

#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[1024];
    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[1024];
    while(1)
    {
        fgets(buff,sizeof(buff),stdin);
        buff[strlen(buff)-1] = '\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[1024];
    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;
}

思维导图

相关推荐
qq_12498707531 分钟前
Java+SpringBoot+Vue+数据可视化的综合健身管理平台(程序+论文+讲解+安装+调试+售后)
java·开发语言·spring boot·毕业设计
煤炭里de黑猫1 分钟前
Lua C API:深入理解 lua_pushnumber 函数 — 将数字压入 Lua 栈中
开发语言·lua
哥坐11路6 分钟前
网络IP跳动问题解决详
开发语言·php
花王江不语10 分钟前
设计模式学习笔记
笔记·学习·设计模式
前端熊猫27 分钟前
CSS Grid 布局学习笔记
css·笔记·学习·grid
奔跑吧邓邓子29 分钟前
【Python爬虫(27)】探索数据可视化的魔法世界
开发语言·爬虫·python·数据可视化
code bean40 分钟前
【C# 数据结构】队列 FIFO
开发语言·数据结构·c#
Shuzi_master71 小时前
<02.21>八股文
java·开发语言
元亓亓亓1 小时前
java后端开发day18--学生管理系统
java·开发语言
梦里是谁N1 小时前
【deepseek之我问】如何把AI技术与教育相结合,适龄教育,九年义务教育,以及大学教育,更着重英语学习。如何结合,给出观点。结合最新智能体Deepseek
人工智能·学习