十一、C++的IO | 知识要点 | C++的IO框架

alt

I/O 复用

概述

当TCP客户端同时处理两个输入:标准输入和TCP套接字时,会遇到以下问题:

  • 问题描述:在客户端阻塞于标准输入上的fgets调用时,如果服务器进程突然终止,服务器TCP虽然正确地给客户端发送了一个FIN,但是客户端正阻塞于从标准输入读入的过程,因此无法立即看到这个EOF(文件结束符)。直到从标准输入读到数据后,客户端才能看到EOF,但此时可能已经错过了及时处理服务器关闭的时机。
  • 解决方案需求:这样的进程就需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(如TCP连接关闭、标准输入有数据可读等),就通知进程。这种能力就被称为I/O复用(multiplexing)。

I/O 模型

存在5种主要的I/O模型:

  1. 阻塞式I/O
  2. 非阻塞式I/O
  3. I/O 复用
  4. 信号驱动式I/O
  5. 异步I/O

一个输入操作的两个阶段

一个输入操作通常包括两个不同的阶段:

  • 等待数据准备好:在这个阶段,进程需要等待,直到所需的数据到达内核的缓冲区。
  • 从内核向进程复制数据:一旦数据准备好,内核就会将数据从内核空间复制到用户空间,供进程使用。

这些阶段和模型的不同之处在于它们处理等待数据准备好这一阶段的方式。

select 函数

概述

该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时间后才唤醒它。主旨思想在于不再由应用程序自己监视客户端连接,而是由内核替应用程序监视文件描述符的状态。

详细解析

#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdpl, fd_set *readset, fd_set *writeset,
			fd_set *exceptset, struct timeval *timeout);

	maxfdpl: 	监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
	readset: 	监控有读数据到达文件描述符集合,传入传出参数
	writeset: 	监控写数据到达文件描述符集合,传入传出参数
	exceptset: 	监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
	timeout: 	定时阻塞监控时间,3种情况
				1.NULL,永远等下去
				2.设置timeval,等待固定时间
				3.设置timeval里时间均为0,检查描述字后立即返回,这称为轮询
	struct timeval {
		long tv_sec; /* seconds */
		long tv_usec; /* microseconds */
	};

    void FD_ZERO(fd_set *set); 			 //把文件描述符集合里所有位清0
    void FD_SET(int fd, fd_set *set); 	 //把文件描述符集合里fd位置1
	void FD_CLR(int fd, fd_set *set); 	 //把文件描述符集合里fd清0
	int  FD_ISSET(int fd, fd_set *set);  //测试文件描述符集合里fd是否置1

函数内容详解

  • 注意事项:
    • select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024。单纯改变进程打开的文件描述符个数并不能改变select监听的文件个数。
  • 描述符就绪条件:
    • 当某个套接字上发生错误时,该套接字将被select标记为既可读又可写。
  • 接收低水位标记和发生低水位标记的目的:
    • 允许应用进程控制select返回可读或可写条件之前有多少数据可读或有多大空间可用于写。例如,可以设置至少存在64字节的数据准备好读时,select才唤醒应用进程,从而避免频繁打断应用进程处理其他工作。

select总结

  • 优点:

    • select遵循POSIX标准,跨平台移植性较好。
    • select监控的超时等待时间可以精细到微秒。
  • 缺点:

    • select所能监控的描述符数量有最大上限,取决于宏_FD_SETSIZE,默认大小为1024。
    • select的监控原理是在内核中进行轮询遍历,这种遍历会随着描述符的增多而性能下降。
    • select返回时会移除所有未就绪的描述符,只给用户返回就绪的描述符集合,但没有直接告诉用户哪个描述符就绪,需要用户自己遍历才能获知。
    • select每次监控都需要重新将集合拷贝到内核中才能进行监控。
    • select每次返回都会移除所有未就绪的描述符,因此每次监控都要重新向集合中添加描述符。

poll 函数

概述

该函数提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。select函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时间后才唤醒它。

详细解析

#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

	struct pollfd {
		int fd; 		/* 文件描述符 */
		short events; 	/* 监控的事件 */
		short revents;  /* 监控事件中满足条件返回的事件 */
	};

	events标志以及测试revents标志的一些常量:
	—————————————处理输入—————————————————————
	POLLIN			普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND
	POLLRDNORM		数据可读
	POLLRDBAND		优先级带数据可读
	POLLPRI 		高优先级可读数据
	—————————————处理输出—————————————————————
	POLLOUT			普通或带外数据可写
	POLLWRNORM		数据可写
	POLLWRBAND		优先级带数据可写
	—————————————处理错误————————————————————
	POLLERR 		发生错误
	POLLHUP 		发生挂起
	POLLNVAL 		描述字不是一个打开的文件

	nfds 			监控数组中有多少文件描述符需要被监控

	timeout 		毫秒级等待
		-1:阻塞等,#define INFTIM -1 				Linux中没有定义此宏
		0:立即返回,不阻塞进程
		>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值



函数内容详解

  • 注意事项:
    • 如果不再监控某个文件描述符时,可以把pollfd结构体中的fd成员设置为-1,这样poll将不再监控此pollfd,下次poll返回时,会把对应的revents成员设置为0。
    • pollfd结构包含了要监视的事件(通过events成员)和发生的事件(通过revents成员),不再使用select的"值-结果"传递方式。
    • 同时,poll函数对pollfd数组的大小并没有最大数量限制(但监控的文件描述符数量过大后,性能也会有所下降)。
    • select函数一样,poll返回后,需要轮询pollfd数组来获取就绪的描述符。

epoll 函数

概述

epoll是Linux下多路复用I/O接口(select/poll)的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果,而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合。获取事件时,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合即可。目前,epoll是Linux大规模并发网络程序中的热门首选模型。epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

基础API

  • 创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,与内存大小有关。
#include <sys/epoll.h>
int epoll_create(int size)		size: 监听数目

  • 控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
		epfd: epoll_creat的句柄
		op: 表示动作,用3个宏来表示: 
			EPOLL_CTL_ADD (注册新的fd到epfd),
			EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
			EPOLL_CTL_DEL (从epfd删除一个fd);
		event: 	告诉内核需要监听的事件

		struct epoll_event {
			__uint32_t events

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C/C++面试必考必会 文章被收录于专栏

【C/C++面试必考必会】专栏,直击面试核心,精选C/C++及相关技术栈中面试官最爱的必考点!从基础语法到高级特性,从内存管理到多线程编程,再到算法与数据结构深度剖析,一网打尽。助你快速构建知识体系,轻松应对技术挑战。希望专栏能让你在面试中脱颖而出,成为技术岗的抢手人才。

全部评论

相关推荐

1.&nbsp;解释C++中的内存管理机制(如堆和栈)。2.&nbsp;描述C++中的拷贝构造函数和赋值运算符的作用及其区别。3.&nbsp;什么是RAII?如何在C++中实现?4.&nbsp;解释虚函数和虚表的原理。5.&nbsp;描述C++中的构造函数和析构函数的作用和特点。6.&nbsp;说明C++中的多重继承及其可能带来的问题。7.&nbsp;什么是模板?如何在C++中定义和使用模板?8.&nbsp;解释C++11中的`auto`关键字的使用场景和优势。9.&nbsp;描述C++11中的`nullptr`的作用以及它与`NULL`的区别。10.&nbsp;解释C++11中的智能指针`unique_ptr`的使用方法及其优缺点。11.&nbsp;解释C++11中的`shared_ptr`的工作原理及其常见用法。12.&nbsp;如何在C++中实现自定义异常处理?13.&nbsp;描述C++中的`std::move`和`std::forward`的用途及其区别。14.&nbsp;解释C++中的`enum`类与传统`enum`的区别。15.&nbsp;什么是C++中的“函数对象”?如何定义和使用它们?16.&nbsp;描述C++中的`std::function`和`std::bind`的功能和应用。17.&nbsp;解释C++中的类型推导(type&nbsp;deduction)机制及其使用方法。18.&nbsp;解释`static`关键字在类中的作用。19.&nbsp;什么是C++中的“完美转发”?如何实现?20.&nbsp;解释C++中的`constexpr`关键字及其用法。21.&nbsp;描述C++中的`inline`函数及其优化作用。22.&nbsp;什么是C++中的“左值引用”和“右值引用”?如何使用?23.&nbsp;解释C++中的“移动构造函数”和“移动赋值运算符”。24.&nbsp;描述C++中的“异常安全性”及其分类。25.&nbsp;什么是C++中的“类模板”与“函数模板”?有什么区别?26.&nbsp;解释C++中的“非类型模板参数”及其应用。27.&nbsp;如何在C++中实现和使用“虚继承”?28.&nbsp;描述C++中的“动态多态”和“静态多态”的区别。29.&nbsp;什么是“C++中的拷贝控制”?如何自定义拷贝控制?30.&nbsp;解释C++中的“友元函数”及其用途。31.&nbsp;描述C++中的“析构函数”以及如何避免“资源泄漏”。32.&nbsp;如何使用C++中的“`std::thread`”类进行多线程编程?33.&nbsp;解释C++中的“`std::mutex`”和“`std::lock_guard`”的作用及其用法。34.&nbsp;什么是C++中的“C++17”新特性?举例说明。35.&nbsp;解释C++中的“`decltype`”关键字及其用途。问题答案已经整理到专栏中了,关注我分享更多知识。&nbsp;&nbsp;c++/嵌入式面经专栏-牛客网 https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
查看35道真题和解析
点赞 评论 收藏
分享
2 7 评论
分享
牛客网
牛客企业服务