linux网络编程-多路I/O转换-I/O复用概念及select函数

刚开始都是由server处理,不管是监听还是连接建立还是读写,那么能否让内核来帮忙处理一点呢
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
select、poll、epoll都属于多路I/O转换

select()函数

select(
    _In_ int nfds,		//在windows下面没有意义  在Unix衍生的操作系统中使用  
    _Inout_opt_ fd_set FAR * readfds, 		 //_Inout_  表示这个参数传入时是有意义的 传出时也是有意义的   指向检查可读性的套接字集合的可选的指针。表示客户端有读的socket客户端需求等待处理
    _Inout_opt_ fd_set FAR * writefds,		//指向检查可写性的套接字集合的可选的指针。表示客户端有写的socket客户端需求等待处理  
    _Inout_opt_ fd_set FAR * exceptfds,	//指向检查错误的套接字集合的可选的指针。表示客户端有异常的Socket客户端的需求等待处理
    _In_opt_ const struct timeval FAR * timeout 	//函数需要等待的最长时间,需要以TIMEVAL结构体格式提供此参数,对于阻塞操作,此参数为null。 
    );

第一个参数,如下图所示,前三个空间不用,然后有一个lfd,三个cdf,最后一个cfd在6位置上,因此是6+1=7
在这里插入图片描述
第2、3、4个参数分别对应要监听的读、写、异常集合,且是传入传出参数
将要监听读的socket放到读集合里,将要监听写的socket的放到读集合里,同理异常集合,然后在监听后返回需要读、写、异常的集合,因此传入的是需要监听的,传出的是实际有事件发生的

还有一个就是返回值,返回值便是这三个集合中有事件发生的总个数

第5个参数是指针类型的timeout,如果设置为NULL就会阻塞在那,如果是设置一个时间,那么它会等待这个时间,如果设置为0那么检查一下立马返回

在这里插入图片描述

// 这里的fd 实际使用都是以 句柄 传入
FD_ZERO(fd_set *fdset);              // 清空
FD_SET(int fd, fd_set *fdset);       // 将fd加入集合
FD_CLR(int fd, fd_set *fdset);       // 将fd从集合中删除
FD_ISSET(int fd, fd_set *fdset);     // 检测fd是否在set集合中,不在则返回0

	void FD_ZERO(fd_set *set);	--- 清空一个文件描述符集合。
		fd_set rset;
		FD_ZERO(&rset);

	void FD_SET(int fd, fd_set *set);	--- 将待监听的文件描述符,添加到监听集合中
		FD_SET(3, &rset);	FD_SET(5, &rset);	FD_SET(6, &rset);

	void FD_CLR(int fd, fd_set *set);	--- 将一个文件描述符从监听集合中 移除。
		FD_CLR(4&rset);

	int  FD_ISSET(int fd, fd_set *set);	--- 判断一个文件描述符是否在监听集合中。
		返回值: 在:1;不在:0;
		FD_ISSET(4&rset);
		
	int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

		nfds:监听的所有文件描述符中,最大文件描述符+1
		readfds: 读 文件描述符监听集合。	传入、传出参数
		writefds:写 文件描述符监听集合。	传入、传出参数		NULL
		exceptfds:异常 文件描述符监听集合	传入、传出参数		NULL
		timeout: 	
		        > 0: 	设置监听超时时长。
				NULL:	阻塞监听
				0:	非阻塞监听,轮询
		返回值:
		   > 0:	所有监听集合(3个)中, 满足对应事件的总数。
			0:	没有满足监听条件的文件描述符
		   -1: 	errno

伪代码

    int maxfd = 0;
	lfd = socket() ;			创建套接字
	maxfd = lfd;
	
	bind();					绑定地址结构

	listen();				设置监听上限

	fd_set rset, allset;			创建r监听集合

	FD_ZERO(&allset);				将r监听集合清空

	FD_SET(lfd, &allset);			将 lfd 添加至读集合中。

	while1{
		rset = allset;			保存监听集合
		ret  = select(lfd+1&rset, NULLNULLNULL);		监听文件描述符集合对应事件。
		if(ret > 0{							有监听的描述符满足对应事件
			if (FD_ISSET(lfd, &rset)) {				// 1 在。 0不在。
				cfd = accept();				建立连接,返回用于通信的文件描述符
				maxfd = cfd;
				FD_SET(cfd, &allset);				添加到监听通信描述符集合中。
			}
			for (i = lfd+1; i <= 最大文件描述符; i++{
				FD_ISSET(i, &rset)				有read、write事件
				read()
				小 --write();
			}	
		}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

#include "wrap.h"

#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    
    //创建一个Listensocket
    int listenfd = Socket(AF_INET,SOCK_STREAM,0);
   
    //端口复用 避免2msl影响
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //绑定地址结构
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERV_PORT);
    Bind(listenfd,(struct sockaddr*)&server_addr,sizeof(server_addr));

   //设置监听上限
   Listen(listenfd,128);


   //设置set
   //设置一个读集合,全部fd的集合
   fd_set rset, allset;
   //当前最大句柄
   int maxfd = listenfd;  //此时listenfd为3  0、1、2分别为读 写 异常
   //清空allset
   FD_ZERO(&allset); 
   FD_ZERO(&rset);
   //将listenfd放到allset中
   FD_SET(listenfd,&allset);

   while(1){
	//由于如果有新的连接建立只会放到allset中,因此更新要监听的读集合
	rset = allset;
	int nready = select(maxfd+1,&rset,NULL,NULL,NULL);
        //如果select出错
	if(nready < 0){
 	 perr_exit("select error");
        }
	
	//新的连接请求
	if(FD_ISSET(listenfd,&rset))
	{
	  struct sockaddr_in client_addr;
	  socklen_t client_addr_len = sizeof(client_addr);
	  int connfd = Accept(listenfd, (struct sockaddr *)&client_addr,&client_addr_len);
	 
	  FD_SET(connfd, &allset);
	  
  	  if(maxfd < connfd)
             maxfd = connfd;
	  if(nready==1) //如果仅有一个listenfd出现,那么就停止进行后面的工作
             continue;          
       }
       
       int i;
       char buf[1024];
       for(i = listenfd+1; i<=maxfd; i++){
		 //发现读事件
		if(FD_ISSET(i, &rset)){
		  int n;	
		  if(( n = Read(i, buf, sizeof(buf))) == 0){
		     Close(i);
		     FD_CLR(i, &allset);
		}else if (n>0){
		     int j;
		     for (j=0; j<n; j++)
			buf[j] = toupper(buf[j]);
		    Write(i, buf, n);
		}

		}
	}

}
	
	Close(listenfd);
	return 0;


}

关于上面代码,在select有返回事件产生的时候也就是返回值大于0时
假设 如果此时返回值为2,并且此时集合中有1020个数,那么此时遍历就会从3遍历到1020个数,因此很费时,可以自定义一个数组来提高效率

select优缺点

缺点:	监听上限受文件描述符限制。 最大 1024.

	检测满足条件的fd, 自己添加业务逻辑提高小。 提高了编码难度。

优点:	跨平台。win、linux、macOS、Unix、类Unix、mips
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪睡的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值