当前位置: 首页 > article >正文

网络编程 day3

思维导图        以select函数模型为例

思维导图2        对应 epoll模型 应使用的函数

题目

使用epoll函数实现 两个客户端 通过服务器 实现聊天

思路

在原先代码基础上

        实现 服务器 发向 客户端                使用客户端在服务器上的 套接字描述符 

        实现 客户端 接收 服务器消息        发向服务器的描述符 也可以用于读取服务器传来数据

实现通信

【1】客户端与服务器连接后,会被分配一个唯一的、不变的id号,直到本次连接断开前,此id号不会被改变或者销毁;

【2】服务器根据 收到的 PACK包中的 pack.type 参数,确定转发目标;        

【3】通过这个唯一id,服务器可以实现数据向 指定目标 的发送;

代码

服务器

struct PACK
{
	int size;	//数据包大小
	int type;	//对数据的操作
	char buf[1500];	//数据
	int conut;	//数据已经占用多少字节
};

enum
{
	//以下参数适用于 client_id_ctl() 函数的 int cmd 参数
	insect_clinet=1,	//分配客户端id号 的指令
	get_client_id,		//获取客户端id号 的指令
	get_clinetfd,		//获取客户端描述符 的指令
	remove_clinet,		//移除客户端 的指令

	//以下参数适用于 数据包的 pack.type 值 
	send_to_server=-1,	//说明数据是发送向服务器的
};

//客户端id号 操作函数
	//根据cmd不同,实现分配id 获取id 获取描述符 移除id等功能
int client_id_ctl(int arr[],int len,int cmd,int client);
//解析数据
void unlood(struct PACK *pack);

int main(int argc, const char *argv[])
{
	int client_arr[10];	//用于存放 客户端在服务器 上的文件描述符
	memset(client_arr,-1,sizeof(client_arr));
	//创建服务器套接字
	int serverfd=socket(AF_INET,SOCK_STREAM,0);
	//创建并设置 "网络通信结构体"
	struct sockaddr_in addr;
	addr.sin_family=AF_INET;	//ipv4
	int port=atoi(argv[1]);
	addr.sin_port=htons(port);	//端口号 (大端存储)
	addr.sin_addr.s_addr=inet_addr("0.0.0.0");
	//套接字 绑定 ip 和 port
	int res=bind(serverfd,(struct sockaddr*)&addr,sizeof(addr));
	if(res==-1)
	{
		printf("服务器 绑定ip和port 失败\n");
		perror("bind");
		return 0;
	}
	printf("服务器已就绪\n");
	//监听
	listen(serverfd,10);
  	
	//创建动态的监视列表
    int epfd=epoll_create1(EPOLL_CLOEXEC);
    //设定 需要监视的描述符 以及 激活形式
    struct epoll_event event_serverfd={.events=EPOLLIN,.data.fd=serverfd};
	struct epoll_event event_stdinfd={.events=EPOLLIN,.data.fd=0};
    //添加到 [监视列表]
    epoll_ctl(epfd,EPOLL_CTL_ADD,serverfd,&event_serverfd);
	epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event_stdinfd);
	while(1)
	{
        struct epoll_event active_list[10];    //用于存放被激活的描述符
        //开始监视 [监视列表]中描述符
        int len=epoll_wait(epfd,active_list,10,-1);    //被激活的描述符 在 active_list[10]数组中
        //遍历被激活的描述符
		for(int i=0;i<len;i++)
        {
            int active_fd=active_list[i].data.fd;   //获取被激活的描述符fd
            //如果是服务器可读 说明有用户尝试连接
            if(active_fd==serverfd)
            {
				//接收客户端连接
                int clientfd=accept(serverfd,NULL,NULL);
				//为客户端 分配id号
				int id=client_id_ctl(client_arr,10,insect_clinet,clientfd);
                // //将客户端 添加到 [监视列表]
				struct epoll_event event_client={.events=EPOLLIN,.data.fd=clientfd};
                epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event_client);
				printf("服务器提示: 有新的客户端连接到服务器 分配id号:%d\n\n",id);
            }
            //如果数据来自 终端
            else if(active_fd==0)
            {
                printf("服务器提示:NULL");
				while(getchar()!=10);
                printf("\n");
            }
            //其他情况:数据来自客户端
            else
            {
				/***********************************数据读取部分***********************************/
				//获取该客户端id号
				int active_id=client_id_ctl(client_arr,10,get_client_id,active_fd);
				struct PACK pack={0};
				int res=read(active_fd,&pack.size,4);
				if(res==0)
				{
					printf("服务器提示:%d#用户断开连接\n",active_id);
                    //[监视列表] 中 删除该描述夫
                    epoll_ctl(epfd,EPOLL_CTL_DEL,active_fd,&event_serverfd);
					//移除该用户id
					client_id_ctl(client_arr,10,remove_clinet,active_id);
					close(active_fd);
					continue;
				}
				read(active_fd,(char *)&pack+4,pack.size-4);
				
				/***********************************数据处理部分***********************************/
				//根据PACK包中的 tyoe 决定对数据的操作
				if(pack.type==send_to_server)
				{
					//说明 数据发向服务器 在服务器解析
					printf("服务器提示:%d#用户发来数据\n",active_id);
					unlood(&pack);	//解析数据
				}
				else if(pack.type>=0&&pack.type<10)
				{
					//说明 数据需要转发给其他客户端
					printf("服务器提示:%d#用户发来数据 数据转发给%d#用户\n",active_id,pack.type);
					//获取 转发目标 的描述符
					int target_fd=client_id_ctl(client_arr,10,get_clinetfd,pack.type);
					//数据转发
					pack.type=active_id;
					write(target_fd,&pack,pack.size);		//数据发送给 转发目标
				}
            }
        }
	}
	return 0;
}

int client_id_ctl(int arr[],int len,int cmd,int client)
{
	static int sum_client=0;
	//分配客户端id号
	if(cmd==insect_clinet)
	{
		static int new_client_id=0;		//循环未使用的方式 给新增的客户端编号
		if(sum_client==10)
		{
			printf("连接的客户端达到上限 \n");
			return -1;
		}
		//定位到 未使用的下标 未使用则数组的值为-1
		while (arr[new_client_id]%len!=-1)
		{
			new_client_id=(new_client_id+1)%len;	//循环查找
		}
		arr[new_client_id]=client;	//将客户端描述符存入数组
		sum_client++;
		return new_client_id;
	}
	//获取客户端id号
	else if (cmd==get_client_id)
	{
		for(int i=0;i<len;i++)
		{
			if(arr[i]==client)
			{
				return i;	//返回该客户端id号
			}
		}
		return -1;
	}
	//获取指定客户端描述符	根据id号查找
	else if (cmd==get_clinetfd)
	{
		int id=client;
		if(id>=len||id<0)
		{
			printf("id号不合法\n");
			return -1;	//删除失败 返回-1
		}
		return arr[id];	//成功返回该id号的描述符
	}
	//移除客户端id号	根据id号销毁
	else if (cmd==remove_clinet)
	{
		int id=client;
		if(id>=len||id<0)
		{
			printf("%d#客户端移除失败 id号不合法\n",id);
			return -1;	//删除失败 返回-1
		}
		arr[client]=-1;
		sum_client--;
		return id;		//删除成功 返回删除的id号
	}
}

void unlood(struct PACK *pack)
{
	int len;
	while(1)
	{
		//读取2个字节
		len=*(short *)(pack->buf+pack->conut);
		if(len==0)
		{
			printf("数据解析完毕\n\n");
			return;
		}
		pack->conut+=2;
		//读取字符串
		char data[len+1];
		memset(data,0,sizeof(data));
		memcpy(data,pack->buf+pack->conut,len);
		printf("数据:%s\n",data);
		pack->conut+=len;
	}
	printf("数据解析完毕\n");
}

客户端

struct PACK
{
	int size;	//数据包大小
	int type;	//对数据的操作
	char buf[1500];	//数据
	int conut;	//数据已经占用多少字节
};

//数据附加 放入数据
void append(struct PACK* pack,const char *data);
//解析数据
void unlood(struct PACK *pack);

int main(int argc, const char *argv[])
{
	//创建套接字
	int clientfd=socket(AF_INET,SOCK_STREAM,0);	//ipv4 tcp形式 自动选择协议
	//ip地址 和 potr端口号 放入"网络通信结构体"
	struct sockaddr_in addr;
	addr.sin_family=AF_INET;	//ipv4
	int port=atoi(argv[1]);
	addr.sin_port=htons(port);	//端口号
	addr.sin_addr.s_addr=inet_addr("127.0.0.1");	//服务器ip地址

	//结构体数据 写入 套接字
	int res=connect(clientfd,(struct sockaddr*)&addr,sizeof(addr));
	if(res==-1)
	{
		printf("套接字 绑定ip和port 失败\n");
		perror("bind");
		return 0;
	}

	int epfd=epoll_create1(EPOLL_CLOEXEC);  //创建动态的监视列表

    //设定 需要监视的描述符 以及 激活形式
    struct epoll_event event_serverfd={.events=EPOLLIN,.data.fd=clientfd};
	struct epoll_event event_stdinfd={.events=EPOLLIN,.data.fd=0};
    //添加到 [监视列表]
    epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event_serverfd);
	epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event_stdinfd);

	while(1)
	{
		printf("开始监视\n");
        struct epoll_event active_list[10];    //用于存放被激活的描述符
        //开始监视
        int len=epoll_wait(epfd,active_list,10,-1);    //被激活的描述符 在 active_list[10]数组中
        //遍历被激活的描述符
		for(int i=0;i<len;i++)
        {
            int active_fd=active_list[i].data.fd;   //获取被激活的描述符fd
            if(active_fd==0)
			{
				//如果是终端可读
				struct PACK pack={0};
				pack.type=-1;	//默认发往终端
				
				char buf[20]="";
				scanf("%19s",buf);
				printf("\n客户端数据:%s\n",buf);
				while(getchar()!=10);
				append(&pack,buf);
				
				printf("-----------------------------------\n");
				printf("对该数据的操作:\n");
				printf("  >= 0	转发到指定客户端\n");
				printf("  ==-1	数据发向服务器 服务器显示数据\n");
				printf("  ==-2	向服务器分配给自己的id号\n");
				printf("-----------------------------------\n");
				printf("该数据需要:");
				scanf("%d",&pack.type);
				while(getchar()!=10);
		
				pack.size=pack.conut+8;	//包实际大小
				write(clientfd,&pack,pack.size);		//发送给服务器 将接受多少字节
				pack.conut=0;	//发送完毕 conut 重置
			}
			if(active_fd==clientfd)
			{
				
				//如果是描述符可读	说明服务器发来消息
				struct PACK pack={0};
				int res=read(clientfd,&pack.size,4);
				if(res==0)
				{
					printf("与服务器断开连接\n");
					return 0;
				}
				read(clientfd,(char *)&pack+4,pack.size-4);
				printf("服务器传来一条数据,数据转发自%d#用户\n",pack.type);
				unlood(&pack);	//解析数据
			}
		}
	}
	return 0;
}

//数据附加 放入数据
void append(struct PACK* pack,const char *data)
{
	int len=strlen(data);
	*(short *)(pack->buf+pack->conut)=len;
	pack->conut+=2;
	memcpy(pack->buf+pack->conut,data,len);
	pack->conut+=len;
}
//解析数据
void unlood(struct PACK *pack)
{
	int len;
	while(1)
	{
		//读取2个字节
		len=*(short *)(pack->buf+pack->conut);
		if(len==0)
		{
			printf("数据解析完毕\n\n");
			return;
		}
		pack->conut+=2;
		//读取字符串
		char data[len+1];
		memcpy(data,pack->buf+pack->conut,len);
		printf("数据:%s\n",data);
		pack->conut+=len;
	}
	printf("数据解析完毕\n");
}

 效果

           


http://www.kler.cn/a/540331.html

相关文章:

  • C++ ——从C到C++
  • Spring Boot牵手Redisson:分布式锁实战秘籍
  • golang 版 E签宝请求签名鉴权方式
  • 微信小程序案例2——天气微信小程序(学会绑定数据)
  • 常用的python库-安装与使用
  • 了解“/linux-5.4.31/drivers/of/device.c”中的of_device_get_match_data()
  • Orange 开源项目介绍
  • Mp4视频播放机无法播放视频-批量修改视频分辨率(帧宽、帧高)
  • Docker 一文学会快速搭建ollama环境及运行deepseek-r1
  • bat命令 启动java jar 和停止 jar
  • 指定路径安装Ollama
  • WebRtc07: 音视频录制实战
  • 人岗匹配为核,打造精确高效招聘 “高速路”
  • 多模态识别和自然语言处理有什么区别
  • Tomcat添加到Windows系统服务中,服务名称带空格
  • 81页精品PPT | 华为流程与信息化实践与架构规划分享
  • 多头自注意力中的多头作用及相关思考
  • 《我在技术交流群算命》(三):QML的Button为什么有个蓝框去不掉啊(QtQuick.Controls由Qt5升级到Qt6的异常)
  • 深入理解QT的View-Model-Delegate机制和用法
  • 开发指南098-logback-spring.xml说明
  • C# 学习目录
  • 海外直播场景下的AWS技术架构设计与实践
  • 【医院管理会计专题】2.管理会计:医院运营管理的隐形引擎
  • AutoMQ 如何实现没有写性能劣化的极致冷读效率
  • 11g ADG主备切换步骤
  • 【JAVA使用Aes加密报错:Illegal key size or default parameters,如何解决?】