网络编程中网络IO与select、poll、epoll原理及使用

1.什么是网络IO

网络IO就是数据到达网卡后,网卡不能直接将数据送到程序内存,需要经过操作系统(kernel)中转保存到一个buffer中,应用程序调用操作系统的函数,从对应的buffer取出数据。在网络编程中,从用户态调用IO操作时,内核系统会经历两个阶段:等待数据到来,将数据拷贝到进程(或线程)中,在这两个阶段中遇到的不同情况,就出现了五种网络IO模型,分别是:阻塞IO(blocking IO)、非阻塞IO(non-blocking IO)、多路复用IO(IO multiplexing)、异步IO(Asynchronous I/O)、信号驱动IO(signal driven I/O、SIGIO),而select、poll、epoll就用到了网络IO模型中的多路复用IO,也称这种IO方式为事件驱动IO,

2.网络IO

1.阻塞IO(blocking IO)

在linux中,默认情况下所有的socket都是阻塞的,对于一个数据包,当kernel还没收到完整的数据包时,整个进程会被阻塞,数据就绪后进行拷贝操作,直到拷贝完成这一段也都是阻塞的,这就带来一个问题:一个服务器建立多个连接时候,服务端会被一个客户端占用,新的连接进不来,一个简单的解决方案就是使用多线程(或多进程),多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
在这里插入图片描述

2.非阻塞IO(non-blocking IO)

非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回,如果没有就绪就需要用户态一直进行询问,直到数据就绪,这样看似解决了一个服务器对多个客户端连接时候的问题,但这种循环调用会大幅提高cpu占用率。
在这里插入图片描述

3.多路复用IO(IO multiplexing)

1.select

select的好处就在于单个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select 这个 function会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。
select的接口原型有:

FD_ZERO(int fd, fd_set* fds)		//用来清除描述词组set的全部位。
FD_SET(int fd, fd_set* fds)		//用来设置描述词组set中相关fd的位
FD_ISSET(int fd, fd_set* fds)	//用来测试描述词组set中相关fd 的位是否为真
FD_CLR(int fd, fd_set* fds)		//用来清除描述词组set中相关fd 的位
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout)
ndfs:select监视的文件描述符数,视进程中打开的文件数而定,一般设置为监视各文件中的最大文件描述符值加1。
readfds:这个文件描述符集合监视文件集中的任何文件是否有数据可读,当select函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符。
writefds:这个文件描述符集合监视文件集中的任何文件是否有数据可写,当select函数返回的时候,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符。
exceptfds:这个文件集将监视文件集中的任何文件是否发生错误。
timeout:本次select()的超时结束时间,使得select处于三种不同的状态

select也有缺点:单个进程可以监听的描述符的个数限制;当需要检测描述符比较多的时候,select接口本身需要消耗大量时间轮询各个句柄,这是比较耗时的,时间复杂度为O(n)。

2.poll

解决了select的上限问题,一次可以询问任意个数的fd,真正做到了批量。但是,即使减少了运行态切换的成本,针对每次传来的fd,操作系统依然需要逐个遍历,复杂度依然是O(n),只是每次操作的损耗降低了。
poll函数:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd{
	int fd;			//文件描述符
	short events;	//等待的事件
	short revents;	//实际发生的事件
};

revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回.revents域是返回时内核设置的。

2.epoll

epoll对于select和poll缺点进行了改进,没有最大并发连接的限制,时间复杂度降为.O(1);epoll_ctl函数中EPOLL_CTL_ADD会把所有fd拷贝进内核进行维护,有数据到达,只把这些有状态的fd返回,这样就保证了fd在整个过程只会拷贝一次,提升效率。
epoll函数:

int epoll_create (int size);
// 返回内核事件表的文件描述符,参数只是提示内核事件表需要多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//op 指定操作类型:
//EPOLL_CTL_ADD 往内核事件表中注册 fd 上的事件
//EPOLL_CTL_MOD 修改 fd 上的注册事件
//EPOLL_CTL_DEL 删除 fd 上的注册事件

struct epoll_event {
	_unit32_t events; 		//描述时间类型
	epoll_data_t data;
};
typedef union epoll_data {
	void *ptr;
	int fd;	//指定事件从属的目标文件描述符
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;
int epoll_wait( int epfd, struct epoll_event *events, int maxevents, int timeout);
//常用的events的几个宏:
EPOLLIN 	//对应文件描述符可读
EPOLLOUT	//对应文件描述符可写
EPOLLET	//设置epoll为边沿触发,epoll默认为水平触发,边沿触发和水平触发区别是:边沿触发只触发一次,水平触发会一直触发

4.异步IO(Asynchronous I/O)

异步IO用在磁盘IO读写操作,在Linux2.6版本开始引入网络IO,它的流程是用户程序操作IO后,立刻就去做别的事,内核这边收到操作信号会立刻返回,不对进程产生block,之后内核等待数据准备完成,拷贝到用户内存,这一系列动作完成后,给用户进程发信号告知操作完成。

5.信号驱动IO(signal driven I/O、SIGIO)

信号驱动IO需要允许套接口进行驱动IO,并安装信号处理函数,进程继续运行并不阻
塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。当数据报准备好读取时,内核就为该进程产生一个 SIGIO 信号。我们随后既可以在信号处理函数中调用 read 读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它来读取数据报。无论如何处理 SIGIO 信号,这种模型的优势在于等待数据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。免去了 select 的阻塞与轮询,当有活跃套接字时,由注册的 handler 处理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值