网络编程9——实现select多路IO转接❀、⭐在原始select中添加自定义数组提高效率的实现

思路

	lfd = socket();    //创建套接字
	bind();   //绑定地址结构
	listen();   //设置监听上限
	————
	fd_set rset;   //创建读(r)监听集合
	fd_set allset;//区分读监听集合和后添加进去的总的监听集合
	FD_ZERO(&allset);     //将r监听集合清空
    FD_SET(lfd, &allset);        //将lfd添加至集合中
    while(1)
    {
    	rset = allset;    //保存监听集合
		ret = select(lfd+1,&rset,NULL, NULL, NULL);   //监听文件描述符集合对应事件
		if(ret>0)    //有监听的描述符满足对应事件
		{
			if(FD_ISSET(lfd, &rset))     //lfd有响应表示有新连接,给新cfd
			{
				//1在表示有客户端进行连接了
				cfd = accept();//建立连接,返回用于通信的文件描述符
				FD_SET(cfd, &allset);//添加到监听通信描述符集合中
			}
			for(i = lfd+1; i<= 最大文件描述符; i++)//挨个去对比,谁发生事件了(读写了
			{
				if(FD_ISSET(i, &rset);)   //有读写操作
					{
						read();
						toupper();
						write;
					}
					continue;			
			}
		}
	}

代码实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arap/inet.h>
#include<ctype.h>

#include "wrap.h"

#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	int listenfd;//socket
	struct sockaddr_in serv_addr;//bind
	int nready;//select返回的发生事件的值
	int connfd;//accept的返回值
	struct sockaddr_in clie_addr;//accept的第二参数
	socklen_t clie_addr_len;//accept的第三参数,注意类型
	char buf[BUFSIZ];//读写需要的缓冲区
	int i,j;//找集合中事件时以及读写时
	int n;//读写的长度
	bzero(&serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htol(INADDR_ANY);
	serv_addr.sin_port = htos(SERV_PORT);
	listenfd = Socket(AF_INET, SOCK_STREAM,0);//①socket,lfd接收
	//设置端口复用
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	//②bind,参数2为结构体地址,所以先定义再清空最后赋值去绑定
	Bind(listenfd, (struct sockaddr*)serv_addr, sizeof(serv_addr));
	//③listen
	Listen(listen,128);
	//----------
	//----------用select去监听
	//①构造文件描述符集合,以及维护一个最大文件描述符号用于方便循环找事件
	fd_set rset, allset;//一开始主要是读(r)集合
	macfd = listenfd;
	FD_ZERO(&allset);//集合清空
	FD_SET(listenfd, &allset);//首先往集合中将lfd加入
	while(1)
	{
		rset = allset;//每次循环时都重新设置select监控信号集
		nready = select(maxfd+1, &rset, NULL,NULL,NULL);//select去监听(工作),根据返回值nready知道有几个事件,rset是传入传出参数也可以利用函数去知道谁发生了事件
		if(nready < 0)//返回值-1,出错
			perr_exit("select error");
		if(FD_ISSET(listenfd, &rset))//listenfd在事件中,表示有新的客户端链接请求
		{
			//有新的请求,就给一个cfd去连接
			clie_addr_len = sizeof(clie_addr);
			connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
			//并将新的cfd也加入到监听集合中
			FD_SET(connfd, &allset);
			if(maxfd<connfd)
				maxfd = connfd;//维护最大文件描述符号
			if(0 == --nready)//表示只有一个监听文件描述符有事件,那后续的循环就不用了
				continue;
		}
			
			//去检测哪个clients有数据就绪,要做读写
		for(i = listenfd+1; i<= maxfd; i++)
		{
			if(FD_ISSET(i, &rset))//在集合中表示有事件,就读进来
			{
				if((n = Read(i, buf, sizeof(buf))) == 0)
				{//表示读到最后,可理解为客户端client关闭链接
					close(i);//则服务器也可以关闭链接
					FD_CLR(i, &allset);//并从集合中删除
				}else if(n > 0)//还在读
				{
					for(j = 0; j < n; j++)
					{
						buf[j] = toupper(buf[j]);
					}
					Write(i, buf, n);
				}
			}
		}
	}
	Close(listenfd);
	return 0;
}

select优缺点

以上述代码的缺点:
①万一监听的是lfd(3号),C3(6号)以及CN(1023号),那么虽然只监听了3个却还是轮询了1024个,效率就很低
====> 解决办法:自定义一个数组👇,即当检测满足条件的id时,需要自己添加业务逻辑提高效率,提高编码难度。但是性能不比poll、epoll低
②文件符集合最大上限是1024,所以我们客户端数目也是有限的,最大也就1024个

优点:唯一一个可以跨平台完成文件描述符监听

添加一个自定义数组提高效率(进阶版xX👇

定义一个client数组

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>

#include "wrap.h"
#definr SERV_PORT 6666

int main(int argc, char *argv[])
{
	int i, j, n, maxi;
	int nready;
	int client[FD_SETSIZE];//自定义client数组,防止遍历1024个文件描述符,FD_SETSIZE默认值为1024
	int maxfd, listenfd,connfd, sockfd;
	char buf[BUFSIZ], str[INET_ADDRSTRLEN];//INET_ADDRSTRLEN默认值为16
	struct sockaddr_in clie_addr, serv_addr;
	socklen_t clie_addr_len;
	fd_set rset, allset;//rset读事件文件描述符集合,allset用来暂缓

	listenfd = Socket(AF_INET, SOCK_STREAM,0);
	//端口复用
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR,&opt, sizeof(opt));
	//bind之前地址结构清零
	bzero(&serv_addr, sizeof(serv_addr));
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(SERV_PORT)Bind(listenfd, (struct sockaddr*)serv_addr, sizeof(serv_addr));
	Listen(listenfd,128);
	maxfd = listenfd;//起初listenfd即为最大文件描述符
	maxi = -1;//将来用作client[]的下标,初始值指向0个元素之前下标位置
	for(i = 0; i<FD_SETSIZE; i++)
	{
		client[i] = -1;//用-1初始化client[]
	}
	FD_ZERO(&allset);//清空文件描述符
	FD_SET(listenfd, &allset);//将监听lfd加入

	while(1)
	{
		rset = allset;//⭐每次循环时都重新设置select监控信号集
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		if(nready < 0)
			perr_exit("select error");
		if(FD_ISSET(listenfd, &rset))//lfd有事件,表示有新的客户端链接请求
		{
			clie_addr_len = sizeof(clie_addr);
			connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
			printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),ntohs(clie_addr.sin_port));//地址结构转换的函数要再去理解记忆一下⭐
			//把这个新的cfd加入到数组中
			for(i = 0; i<FD_SETSIZE; i++)
			{
				if(client[i] < 0)//第一个空位就填上去
				{
					client[i] = connfd;
					break;//很秒,跳出这个循环,下面的i就都是这个i
				}
			}
				if(i == FD_SETSIZE)//达到select能监控的文件个数上限1024
				{
					fputs("too many clients\n", stderr);
					exit(1);
				}
				//也要把这个新的cfd加入到监控集合中
				FD_SET(connfd, &allset);
				
				if(connfd > maxfd)
					maxfd = connfd;
				
				if(i > maxi)
					maxi = i;//保证maxi存的总是client[]最后一个元素的下标
				
				if(--nready == 0)//表示刚刚就只有lfd工作了,那accept给出cfd就ok
					continue;
				//还有别的cfd有请求
				for(i = 0; i <= maxi; i++)//依次检测哪个clients有数据就绪
				{
					if((sockfd = client[i]) <0 )//没动静不是它,下一个
						continue;
					//这个client[i]有事件,则👇
					if((n = Read(sockfd, buf, sizeof(buf))) == 0)
					{
						//当client关闭链接时,服务器端也关闭对应链接
						Close(sockfd);
						FD_CLR(sockfd, &allset);
						client[i] = -1;//别忘了数组
					}else if(n>0)//读写
					{
						for(j = 0; j<n; j++)
							buf[j] = toupper(buf[j]);
						Write(sockfd, buf, n);
						Write(STDOUT_FILENO, buf, n);
					}
					if(--nready == 0)//没有别的cfd有请求了,退出找对比找cfd的循环
						break;
				}
			
		}
	}
	Close(listenfd);
	return 0;
}
❀❀ 好开心❀❀
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值