I/O复用 —— select

I/O复用是一种提高服务器处理客户端请求效率的技术,通过一个线程监听多个文件描述符,减少进程或线程创建,降低CPU工作压力。主要的I/O复用系统调用包括select、poll和epoll,其中epoll为Linux特有,能更高效地处理大量文件描述符。在Linux下,select存在最大文件描述符限制和每次都需重新设置关注的描述符等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者前言:在学习i/o复用前,我们要清楚为什么要引出I/O复用,它的的作用是什么?什么是I/O复用?I/O复用是怎么实现的? -------  想一想噢~

下面我来解释一下这些问题

I/O复用的作用 

       从进程线程的学习,到多进程多线程,再到进程池线程池,我们处理事件的效率越来越高,但是却有一个问题,一直都没有解决,那就是,当服务器分配了一个线程或进程为某一个客户端服务时,该进程或线程就相当于与这个客户端绑定了,除非客户端断开连接,否则不管有没有请求事件,这个线程或进程都只能为这个客户端处理事件。我们知道,系统允许我们创建的线程或进程是有限的,那么这样就会造成资源的浪费,所以我们需要实现一套机制,在客户端没有请求事件的时候,该进程或线程就可以去处理其他客户端的事件,也就是我们需要将多个文件描述符同时监听,当有请求时,去处理发出请求的客户端的事件。由此就有了I/O复用。

什么是I/O复用

       I/O复用的本质就是一个线程监听多个文件描述符,我们可以这样理解,多线程多进程编程,就是老师对学生一对一辅导,但是不是所有时候学生都有问题,这样老师就会很闲,会浪费很大的师资力量,而I/O复用,就相当于一个老师辅导多个学生,学生有问题提出问题(发出请求事件),老师处理完就可以处理别的学生的问题了。那就有人说了,I/O理解起来太麻烦,我用循环,轮询一遍就能找到该处理哪个事件了,但是你见过有老师一个一个问学生你有问题吗,如果老师知道有人有问题就挨个问一遍,他还有力气处理问题么,同样的道理,我们的CPU那么忙碌,它是来处理事件的,又不是挨个打招呼的,所以我们就直接告知CPU处理哪个问题就ok啦。

        通过上面举例,我们可以理解,I/O复用的两个优点(划重点、面试官会问的呐)

       1. 它减少了进程或线程的创建数量,节省了我们的内存资源,使得资源更加高效的被利用。

       2. 它减少了CPU的工作压力,CPU只需要做自己最擅长的事就好。

 I/O复用的应用场景

1.客户端程序要同时处理多个socket。

2.客户端程序要同时处理用户输入和网络连接。

3.TCP服务器要同时处理监听socket和连接socket。

4.服务器要同时处理TCP请求和UDP请求。

5.服务器要同时监听多个端口,或者处理多种服务。

需要指出的是,I/O复用虽然能够同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是串行工作的。如果要实现并发,只能使用多进程或多线程等编程手段。

I/O复用的实现

      在linux下I/O复用的实现主要有三个系统调用:select,poll,epoll(epoll为linux特有)

1.select系统调用

select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常等事件。

select系统调用的原型:
#include<sys/select.h>
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval * timeout);

nfds:最大文件描述符的值+1
readfds:用户感兴趣的可读事件的文件描述符集合
writefds:可写事件的文件描述符集合
exceptfds:异常事件的文件描述符集合
timeout:设置超时时间 ,如果timeout为NULL,则select一直阻塞

返回值: >0    返回就绪文件描述符的个数
               ==0   超时
               == -1 出错

用户程序必须自己保存所有的文件描述符

select每次都会将所有的文件描述符返回,返回后还必须循环探测具体哪些是就绪的文件描述符。

每次调用select都必须重新设置resdfds、writefds、exceptfds

fd_set结构体的定义         ---------->       int fds[32];      按位表示关注的文件描述符   

每次最多可以监听1024个文件描述符,并且其最大值1023

文件描述符就绪条件:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/select.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>

//初始化维护的数组,用于保存文件描述符
void init_fd(int *fds,int len)
{
	int i = 0;
	for(; i < len ;++i)
	{
		fds[i] = -1;
	}	
}
//往维护的数组里面添加文件描述符
void insert_fd(int *fds,int fd,int len)
{
	int i = 0;
	for(;i < len ;++i)
	{
		if(fds[i] == -1)
		{
			fds[i] = fd;
			break;
		}
	}
}
//删除文件描述符
void delete_fd(int *fds,int fd,int len)
{
	int i = 0;
	for(; i < len ;++i)
	{
		if(fds[i] == fd)
		{
			fds[i] = -1;
			break;
		}
	}
}

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);
 
	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
 
	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);
 
	listen(sockfd,5);
 
	fd_set readfds;
 
	int fds[100];
	init_fd(fds,100);
	insert_fd(fds,sockfd,100);
 
	while(1)
	{
		int maxfd = -1;
		FD_ZERO(&readfds);//先将文件描述符清空
		int i = 0;
		for(; i < 100;++i)
		{
			if(fds[i] != -1)
			{
				if(fds[i] > maxfd)
				{
					maxfd = fds[i];
				}
				FD_SET(fds[i],&readfds);
			}
		}
 
		int n = select(maxfd+1,&readfds,NULL,NULL,NULL);
		if(n <= 0)
		{
			printf("select fail\n");
			continue;
		}
 
		for(i = 0;i<100;++i)
		{
			if(fds[i] != -1 && FD_ISSET(fds[i],&readfds))
			{
				 if(fds[i] == sockfd)//客户链接
				 {
					 int len = sizeof(caddr);
					 int c = accept(sockfd,(struct sockaddr *)&caddr,&len);
					 if(c < 0)
					 {
						 printf("one client over\n");
						 continue;
					 }
					 insert_fd(fds,c,100);
				 }
				 else //客户请求
				 {
					int fd = fds[i];
					char buff[128] = {0};
					int n = recv(fd,buff,127,0);
					if(n <= 0)
					{
						delete_fd(fds,fd,100);
						close(fd);
						continue;
					}
					printf("%d : %s\n",fd,buff);
					send(fd,"OK",2,0);  
				 }
			}
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值