I/O复用----select

本文深入探讨I/O复用的概念及其在Linux系统中的实现,包括select、poll和epoll等机制。通过对比分析,解释了如何利用这些机制提高网络编程的效率,特别是在处理大量并发连接时的优势。

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

在之前的进程池、线程池中,一个进程或线程同一时刻只能处理一个客户端,也就是说进程或者线程经常阻塞在recv,是对进程或者线程的浪费,因此有了I/O复用

一、概念
I/O复用:将N个文件描述法统一监视,当其中某些文件描述符上有事件发生,则程序只处理有事件发生的文件描述符

有三种:select、poll、epoll(linux独有)

二、select
头文件:#include<sys/select.h>

int select (int maxfd,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

(1)maxfd: 监听的文件描述符中 最大的文件描述符+1

(2)readfds writefds exceptfds 分别记录关注的可读、可写、异常三种事件的文件描述符,

在调用select时,三个结构体变量向内核传递传递用户关注的文件描述符
select返回时,三个结构图变量向用户传递有事件发生的文件描述符

对于readfds、writefds、exceptfds 内核会在线修改三个结构体,每次调用select,都必须重新设置

(3)fd_set结构

typedef struct
{
long int fds_bits[32];
}fd_set;
由此定义可见,fd_set仅包含一个长整型的数组,该数组的每一个元素的每一位标记一个文件描述符,fd_set所能容纳的文件描述符数量是1024个

(4)操作fd_set结构的四个宏函数
FD_ZERO(fd_set *fds); 清空fds的所有位
FD_SET(int fd,fd_set *fds); 将fd 设置到 fds上
FD_CLR(int fd,fd_set *fds); 清除fds上的 fd
FD_ISSET(int fd, fd_set *fds); 判断fds上的fd文件描述符是否有事件发生

(5) timeout:设置select一次监听的时间,当时间到达,如果没有文件描述符就绪,则为超时,select 返回 0

(6)返回值 >0 就绪的文件描述符个数
== -1 select调用出错

三、使用----下面是使用select的服务器端代码

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

#define FDMAX 100
typedef struct ClientInfo
{
	int fd;
	struct sockaddr_in addrInfo;
}ClientInfo;

ClientInfo fdall[FDMAX];
int maxfd = -1;

void InitFd()//初始化
{
	int i = 0;
	for(; i < FDMAX; ++i)
	{
		fdall[i].fd = -1;
	}
}

void AddFd(int fd, struct sockaddr_in cli)//插入文件描述符及客户端的信息
{
	int i = 0;
	for(; i < FDMAX; ++i)
	{
		if(fdall[i].fd == -1)
		{
			fdall[i].fd = fd;
			fdall[i].addrInfo = cli;
			break;
		}
	}
}

void DelFd(int fd)
{
	int i = 0;
	for(; i < FDMAX; ++i)
	{
		if(fdall[i].fd == fd)
		{
			fdall[i].fd = -1;
			break;
		}
	}
}

void SetFd(fd_set *fds)//将数组中的文件描述符设置到fd_set结构上上
{
	int i = 0;
	for(; i < FDMAX; ++i)
	{
		if(fdall[i].fd != -1)
		{
			if(fdall[i].fd > maxfd)
				maxfd = fdall[i].fd;

			FD_SET(fdall[i].fd, fds);
		}
	}
}

void GetClientLink(int fd)//处理客户端链接
{
	struct sockaddr_in cli;
	int len = sizeof(cli);

	int c = accept(fd, (struct sockaddr*)&cli, &len);
	if(c  == -1)
	{
		return;
	}

	AddFd(c, cli);
}

void DealClientData(int fd, struct sockaddr_in cli)//处理客户端数据
{
	char buff[128] = {0};
	int n = recv(fd, buff, 127, 0);
	if(n  <= 0)
	{
		DelFd(fd);
		return;
	}

	printf("%s:%d  %s\n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port),
		buff);

	send(fd, "OK", 2, 0);
}

void DealFinshEvent(int listenfd, fd_set *fds)//处理就绪事件
{
	int i = 0;
	for(; i < FDMAX; ++i)
	{
		if(fdall[i].fd != -1)
		{
			int fd = fdall[i].fd;
			if(FD_ISSET(fd, fds))//fd是否被设置,则是否有事件发生
			{
				//  listenfd     c
				if(fd == listenfd)
				{
					GetClientLink(fd);
				}
				else
				{
					DealClientData(fd, fdall[i].addrInfo);
				}
			}
		}
	}
}

int main()
{
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(listenfd != -1);

	struct sockaddr_in ser;
	memset(&ser, 0, sizeof(ser));
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6000);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = bind(listenfd, (struct sockaddr*)&ser, sizeof(ser));
	assert(res != -1);

	listen(listenfd, 5);

	InitFd();
	AddFd(listenfd, ser);

	fd_set  read;

	while(1)
	{
		SetFd(&read);//
		int n = select(maxfd + 1, &read, NULL, NULL, NULL);
		if(n <= 0)
		{
			exit(0);
		}

		DealFinshEvent(listenfd, &read);
	}
}

四、缺点
1、只能关注三种事件类型
2、select 的三个 fd_set 参数是在线修改,每次都必须重新设置
3、fd_set 最多纪录1024个文件描述符 最大值是1023
4、仅仅返回了就绪文件描述符个数,用户探测就绪的文件描述符的事件复杂度O(n)
5、内核使用轮询方式检测文件描述符,内核时间复杂度为O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值