高级 IO epoll和poll

一. poll

select一摸一样的定位,适用场景也是一样的

1. 和select的区别

  • poll解决了select能检测的文件描述符是有上限的
  • 将用户告诉内核让OS帮我们关心哪些文件描述符上面的哪些事件。将内核告诉用户哪些文件描述符上的哪些事件是已就绪的。
    poll将这两个过程分离,不用在每次调用poll的时候,重新添加fd以及fd关心的事件

2. poll函数

#include <poll.h>
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
  • 是一个结构体指针(相当于一个结构体数组),代表你想告诉OS哪些文件描述符上的哪些事件要让OS帮你关心
  • 代表你这个结构体指针,一共有几个元素
  • 代表超时时间。
1. 设为1000,则每隔1S timeout一次
2. 设为0 即为非阻塞轮询
3. 设为5000,则每隔5S timeout一次,在这5S 内阻塞
4. -1 代表永久阻塞
返回值
1. >0 代表有几个文件描述符就绪
2. =0 代表timeout
3. <0 代表失败
struct pollfd 结构
struct pollfd{
	int fd;            //哪个文件描述符
	short events;      //用户告知内核你要给我关心哪些事件
	short revents;     //内核告知用户哪些文件描述符上的哪些事件已经就绪
}

events与revents的取值

事件描述是否可作为输入是否可作为输出
POLLIN数据(包括普通数据和优先数据)可读
POLLRDNORM普通数据可读
POLLRDBAND优先级待数据可读(Linux不支持)
POLLPRI高优先级数据可读,比如TCP带外数据
POLLOUT数据(包括普通数据和优先数据)可写
POLLWRNORM普通数据可写
POLLWRBAND优先级带数据可写
POLLRDHUPTCP连接被对方关闭,或者对方关闭了写操作,它由GNU引入
POLLERR错误
POLLHUP挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件
POLLNVAL文件描述符没有打开

重点关注POLLINPOLLOUT

3. 实现

  • 因为传入的是结构体指针,所以我们想给它弄多大,就弄多大,只要系统空间足够,所以他能检测的文件描述符是没有上限的
  • 在代码实现时,不用在每次调用poll时重新添加需要关心的文件描述符

sock.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace ns_sock{
	class Sock{
	public:
		static int Socket(){
			int sock = socket(AF_INET,SOCK_STREAM,0);
			if(sock < 0){
				std::cerr << "socket error" << std::endl;
				exit(1);
			}
			int opt = 1;
			setsockopt(sock,SOCK_SOCKET,SOCK_REUSEADDR,&opt,sizeof(opt));
		}
		static bool Bind(int sock,unsigned short port){
			struct sockaddr_in local;
			memet(&local,0,sizeof(0));
			local.sin_family = AF_INET;
			local.sin_port = htons(port);
			local.sin_addr.s_addr = INADDR_ANY;
			if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
				std::cerr << "bind error" << std::endl;
				exit(2);				
			}
			return true;
		}
		static bool Listen(int sock,int backlog){
			if(listen(sock,backlog) < 0){
				std::cerr << "listen error" << std::endl;
				exit(3);
			}
			return true;
		}
		
	}
}

poll_server.hpp

#include "sock.hpp"
#include <poll.h>

namespace std{
	class PollServer{
	private:
		int listen_sock;
		int port;
	public:
		PollServer(int _port):port(_port)
		{}
		void InitServer(){
			listen_sock = ns_sock::Sock::Socket();
			ns_sock::Sock::Bind(listen_sock,port);
			ns_sock::Sock::Listen(listen_sock,5);
		}
		void Run(){
			struct pollfd rfds[64];
			for(int i = 0; i < 64; ++i){
				rfds[i].fd = -1;
				rfds[i].events = 0;
				rfds[i].revents = 0;
			}
			for(;;){
				switch(poll(rfds,64,-1)){
					case 0:
						std::cout << "timeout" << std::endl;
						break;
					case 1:
						std::cout << "poll error" << std::endl;
						break;
					default:
						for(int i = 0; i < 64; ++i){
							if(rfds[i].fd == -1){
								continue;
							}
							if(rfds[i].revents & POLLIN){
								if(rfds[i].fd = listen_sock){
									std::cout<<"get a new link..." <<std::endl
								}
								else{
									//recv
								}
							}
						}
					}
					break;
				}
			}
		}
		~PollServer()
		{}	
	};
}

server.cc

#include "poll_server.hpp"
#include <iostream>
#include <string>
#include <cstdlib>

static void Usage(std::string proc){
	std::cerr << "Usage" << "\n\t" << proc << "port" << std::endl;
}

int main(int argc,char* argv[]){
	if(argc != 2){
		Usage(argv[0]);
		exit(4);
	}
	unsigned short port = atoi(argv[1]);
	ns_poll::PollServer* ps = new ns_poll::PollServer(port);
	ps->InitServer();
	ps->Run();

	return 0;
}

4. poll缺点

  • poll的检测机制与select一样,所以OS在检测fd就绪时, 需要遍历。所以当有大量的连接的时候,内核同步poll底层遍历,成本会越来越高。
  • 虽然输入输出分离了,但输入输出还是需要拷贝

二. epoll

定位与selectpoll一致

1. epoll初识

按照 man 手册的说法:是为处理大批量句柄而作了改进的poll
它是在2.5.44内核(epoll(4) is a new API introduced in Linux kernel 2.544)
它几乎具备了之前所说的一切优点,被公认为Linux2.6性能最好的多路I/O就绪通知方法

2. epoll的核心工作

(帮你进行文件描述符就绪等待的工作)

3. epoll的相关系统调用

不像 selectpoll 只提供了一个函数调用,它提供了三个相关的系统调用

epoll_create

创建epoll模型(调用其系统调用,会在OS层面上创建epoll相关数据结构)

int epoll_create(int size);
//返回值为一个文件描述符
  • 自从Linux2.6.8之后,size参数是被忽略的
  • 用完之后,必须调用close()关闭
epoll_ctl

向epoll模型中添加一些东西

向特定的 epoll模型 进行添加、删除、修改用户想关心哪些文件描述符上面的哪些事件

int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);

它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型

参数:
1. epoll_create()的返回值(epoll的句柄)
2. 表示动作,用三个宏表示
3. 需要监听的fd
4. 告诉内核需要监听什么事

第二个参数的取值:

EPOLL_CTL_ADD:注册新的fd到epfd中
EPOLL_CTL_MOD:修改已经注册的fd的监听事件
EPOLL_CTL_DEL:从epfd中删除一个fd

底层结构:

typedef union epoll_data{
	void *ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;

struct epoll_event{
	uint32_t events;    //关注一下fd上的事件
	epoll_data_t data;  //附加参数、附加内容(现在不用)
};

events可以是几个宏的集合:

EPOLLIN:表示对于的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据来
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断
EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,监听完这次事件后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
epoll_wait
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);

参数:
1. 在哪个epoll模型进行等待。在一个过程中可存在多个epoll模型
2. 从OS拿到用户曾经想关心的哪些文件描述符上的哪些事件就绪。events是分配好的epoll_event结构体数组(内核只负责把数据拷贝到这个events数组中,不会去帮助我们在用户态分配内存)
3. maxevents告知内核这个events有多大,这个maxevent的值不能大于创建epoll_create()时的size
4. 参数timeout是超时时间(毫秒,0会立即返回,-1是永久阻塞)

返回值:
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目。如果返回0表示已超时,返回小于0表示函数失败

所以在epoll中,整个过程,由三个函数分别承担:
用户告知内核:你要帮我关注一下哪些文件描述符上的哪些事件(epoll_ctl())
内核告知用户:哪些文件描述符上的哪些事件已经就绪了(epoll_wait())

selectpoll虽然传参有差别,使用有差别,但他们都要做一件事,调用时是用户告知内核,返回时是内核告知用户(一个函数承担两种工作职责)
select需借助第三方数组,poll也借助了,但不仅仅是保存文件描述符(用户定义,用户维护,所有增删查改都得自己维护)
epoll不需要借助第三方数组,都由OSepoll做了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值