linux编程:多路转接复用IO之select模型

本文详细介绍了Linux编程中多路转接IO的概念,重点阐述了select模型的工作原理,包括其在处理大量描述符、网络通信服务器中的应用,以及select的优点和缺点。通过实例演示如何在TCP服务器中利用select实现并发连接处理。
摘要由CSDN通过智能技术生成

linux编程:多路转接复用IO之select模型

多路转接IO概念

描述的是转接IO,就是对大量的描述符的IO状态进行监控,能够让程序知道哪个描述符IO是就绪的,可以进行IO操作,直接让程序针对就绪描述符进行IO操作,提高效率(更多用于网络通信服务器)。

IO就绪状态

  • 可读状态:套接字接收缓冲区中,数据大小大于低水位标记(默认-1字节)
  • 可写状态:套接字发送缓冲区中,剩余空间大小大于低水位标记(默认-1字节)
  • 异常状态

select模型

  • 实现思路就是在内核中轮询遍历所有监控的描述符,判断IO就绪状态
  • 接口:int select(int nfds,fd_set *readfds,fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • 操作流程
  1. 程序员定义描述监控集合(可读,可写,异常),在fd_set结构体中有个数组当做位图使用,默认的比特位个数1024
  2. 初始化集合后,将对应监控状态的描述符添加到对应集合中
  3. 将集合中的数据拷贝到内核中进行遍历判断监控,等到监控超时或者有描述符就绪了,监控返回,在返回前将集合中没有就绪的描述符移除
  4. 监控返回后,select返回0表示没有就绪的描述符,监控超时了,大于0表示有描述符就绪;这时候只需要判断哪个描述符还在集合中,哪个描述符就是就绪的描述符,对应就绪了哪个状态;然后对描述符进行对应操作。

多路转接IO应用场景

  1. 当用户处理多个描述符时(一般用于网络套接字),一般要使用I/O复用
  2. 当一个tcp服务器既要处理监听套接字,又要处理通信套接字时,一般要使用I/O复用
  3. 如果一个服务器既要处理tcp协议的请求,又要处理udp协议的请求,一般要使用I/O复用
  4. 如果一个服务器要处理多个服务或多个协议的请求,一般要使用I/O复用

select的应用场景

select不一定非要大量描述符状态监控,单个描述符更适合,因为select有超时控制。所以针对单个描述符如果需要进行超时等待控制也可以使用select

select优缺点分析

优点: select遵循POSIX标准,跨平台移植性良好
缺点:

  1. select监控描述符数量有最大上限(取决于位图的比特位个数默认1024个–由_FD_SETSIZE宏控制)
  2. select监控每次都需要向集合中重新添加描述符,并且每次都需要将集合拷贝到内核(因为select每次返回时都会移除未就绪的描述符)
  3. select监控原理是在内核中进行轮训遍历判断监控,性能会随着描述符的增多而降低
  4. select返回后,依然需要完成一次集合遍历才能获取到就绪的描述符进行操作(select都是通过集合进行操作)

select编程

  • 这里将其应用在tcp服务器中,让tcp服务器可以同时处理多个客户端的请求,并实现多次通信。
  • 具体过程如下
  1. 先创建一个描述符的监控集合,清空它,并将监听描述符添加进集合,此时监听描述符最大
  2. 然后对集合中就绪状态的描述符进行遍历,如果集合中的监听描述符就绪了就创建新的通信套接字,并将新的通信套接字也放入集合中进行监控
  3. 此时集合中最大的描述符是通信描述符,然后继续遍历,等轮询到集合中的通信描述符时,再进行tcp服务器和客户端的通信
  4. 此后每当有新的客户端要与服务器通信时,都会将新创建的通信套接字放入集合中,进行监控,并修改集合中描述符的最大值,使得集合中所有描述符全部能够被遍历到,然后每次再对集合中处于就绪状态的描述符进行操作
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>//地址结构
#include<arpa/inet.h>//字节序转换接口
#include<sys/socket.h>//套接字接口
#include<sys/select.h>
#define MAX_LISTEN_NUM 5

int main()
{
	//1.创建套接字
	int sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sockfd<0){
		perror("socket error");
		return -1;
	}
	//2.绑定地址信息
	struct sockaddr_in addr;
	addr.sin_family=AF_INET;
	addr.sin_port=htons(9000);
	addr.sin_addr.s_addr=inet_addr("0.0.0.0");
	socklen_t len=sizeof(addr);
	int ret = bind(sockfd,(struct sockaddr*)&addr,len);
	if(ret<0){
		perror("bind error");
		return -1;
	}
	//3.开始监听
	ret =listen(sockfd,MAX_LISTEN_NUM);
	if(ret<0){
		perror("listen error");
		return -1;
	}
	fd_set set;//监控集合
	FD_ZERO(&set);//清空集合
	FD_SET(sockfd,&set);//将监听描述符添加到监控集合中,监控是否有新连接到来
	int max_fd=sockfd;
	//4.获取新连接
	while(1){
		struct timeval tv;
		tv.tv_sec=3;
		tv.tv_usec=0;
		fd_set rfds;
		memcpy(&rfds,&set,sizeof(fd_set));
		//select每次返回会移除集合中未就绪的描述符,因此需要用set备份,用于下一次监控描述符时用
		int ret=select(max_fd+1,&rfds,NULL,NULL,&tv);
		if(ret<0){
			perror("select error");
			continue;
		}else if(ret==0){
			printf("No descriptor ready,wait timeout\n");
			continue;
		}
		for(int i=0;i<=max_fd;i++){
			if(FD_ISSET(i,&rfds)){
				//第i个描述符就绪
				if(i==sockfd){//监听描述符就绪
					struct sockaddr_in cliaddr;
					int newfd=accept(sockfd,(struct sockaddr*)&cliaddr,&len);
					FD_SET(newfd,&set);//将新连接添加监控
					max_fd=newfd>max_fd?newfd:max_fd;
					printf("new connect \n");
				}else{//通信描述符就绪
					char buf[1024]={0};
					//接收数据
					ret=recv(i,buf,1023,0);
					if(ret<=0){
						perror("recv error");
						close(i);
						FD_CLR(i,&set);//解除监控
						continue;
					}
					printf("client say:%s\n",buf);
					//发送数据
					ret=send(i,buf,strlen(buf),0);
					if(ret<0){
						perror("send error");
						close(i);
						FD_CLR(i,&set);//解除监控
						continue;
					}
				}
			}
		}	
	//5.使用新连接与客户端通信(收发数据)
	}
	//6.关闭套接字
	close(sockfd);
	return 0;
}

通信演示

TCP服务端
在这里插入图片描述
TCP客户端
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值