Linux上可以使用不同的I/O模型,我们可以通过下图了解常用的I/O模型:同步和异步模型,以及阻塞和非阻塞模型,本文主要分析其中的异步阻塞模型。


一、select使用

这个模型中配置的是非阻塞I/O,然后使用阻塞select系统调用来确定一个I/O描述符何时有操作。使用select调用可以为多个描述符提供通知,对于每个提示符,我们可以请求描述符的可写,可读以及是否发生错误。异步阻塞I/O的系统流程如下图所示:


使用select常用的几个函数如下:

FD_ZERO(int fd, fd_set* fds) 
FD_SET(int fd, fd_set* fds) 
FD_ISSET(int fd, fd_set* fds) 
FD_CLR(int fd, fd_set* fds) 
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) 
fd_set类型可以简单的理解为按bit位标记句柄的队列。具体的置位、验证可以使用FD_SET,FD_ISSET等宏实现。在select函数中,readfds、writefds和exceptfds同时作为输入参数和输出参数,如果readfds标记了一个位置,则,select将检测到该标记位可读。timeout为设置的超时时间。

下面我们来看如何使用select:

	SOCKADDR_IN addrSrv;
	int reuse = 1;
	SOCKET sockSrv,connsock;
	SOCKADDR_IN addrClient;
	pool pool;
	int len=sizeof(SOCKADDR);
	/*创建TCP*/
	sockSrv=socket(AF_INET,SOCK_STREAM,0);
	/*地址、端口的绑定*/
	
	addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	addrSrv.sin_family=AF_INET;
	addrSrv.sin_port=htons(port);
	
	if(bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))<0)
	{
		fprintf(stderr,"Failed to bind");
		return ;
	}
	
	if(listen(sockSrv,5)<0)
	{
		fprintf(stderr,"Failed to listen socket");
		return ;
	}
	setsockopt(sockSrv,SOL_SOCKET,SO_REUSEADDR,(const char*)&reuse,sizeof(reuse));
	init_pool(sockSrv,&pool);
	while(1)
	{
		/*通过selete设置为异步模式*/
		pool.ready_set=pool.read_set;
		pool.nready=select(pool.maxfd+1,&pool.ready_set,NULL,NULL,NULL);
		if(FD_ISSET(sockSrv,&pool.ready_set))
		{
			connsock=accept(sockSrv,(SOCKADDR *)&addrClient,&len);
			//loadDeal()/*连接处理*/
			//printf("test\n");
			add_client(connsock,&pool);//添加到连接池
		}
		/*检查是否有事件发生*/
		check_client(&pool);
	}
上面是一个服务器代码的关键部分,设置为异步的模式,然后接受到连接将其添加到连接池中。监听描述符上使用select,接受客户端的连接请求,在check_client函数中,遍历连接池中的描述符,检查是否有事件发生。





二、poll使用

poll函数类似于select,但是其调用形式不同。poll不是为每个条件构造一个描述符集,而是构造一个pollfd结构体数组,每个数组元素指定一个描述符标号及其所关心的条件。定义如下:

#include <sys/poll.h>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};

每个结构体的events域是由用户来设置,告诉内核我们关注的是什么,而revents域是返回时内核设置的,以说明对该描述符发生了什么事件。这点与select不同,select修改其参数以指示哪一个描述符准备好了。在《unix环境高级编程》中有一张events取值的表,如下:

POLLIN :可读除高优级外的数据,不阻塞

POLLRDNORM:可读普通数据,不阻塞

POLLRDBAND:可读O优先数据,不阻塞

POLLPRI:可读高优先数据,不阻塞

POLLOUT :可写普数据,不阻塞

POLLWRNORM:与POLLOUT相同

POLLWRBAND:写非0优先数据,不阻塞

其次revents还有下面取值

POLLERR :已出错

POLLHUP:已挂起,当以描述符被挂起后,就不能再写向该描述符,但是仍可以从该描述符读取到数据。

POLLNVAL:此描述符并不引用一打开文件

对poll函数,nfds表示fds中的元素数,timeout为超时设置,单位为毫秒若为0,表示不等待,为-1表示描述符中一个已经准备好或捕捉到一个信号返回,大于0表示描述符准备好,或超时返回。函数返回值返回值若为0,表示没有事件发生,-1表示错误,并设置errno,大于0表示有几个描述符有事件。

poll的使用和select基本类似。在此不再介绍。poll相对于是select的优势是监听的描述符数量没有限制。

三、epoll学习

epoll有两种模式,Edge Triggered(简称ET) 和 Level Triggered(简称LT).在采用这两种模式时要注意的是,如果采用ET模式,那么仅当状态发生变化时才会通知,而采用LT模式类似于原来的select/poll操作,只要还有没有处理的事件就会一直通知.

1)epoll数据结构介绍:

typedef union epoll_data
{
  void        *ptr;
  int          fd;
  __uint32_t   u32;
  __uint64_t   u64;
} epoll_data_t;

struct epoll_event
{
  __uint32_t   events; /* Epoll events */
  epoll_data_t data;   /* User data variable */
};
常见的事件如下:

EPOLLIN:表示对描述符的可以读

EPOLLOUT:表示对描述符的可以写

EPOLLPRI:表示对描述符的有紧急数据可以读

EPOLLERR:发生错误

EPOLLHUP:挂起

EPOLLET:边缘触发

EPOLLONESHOT:一次性使用,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

2)函数介绍

epoll的三个函数

int epoll_creae(int size);
功能:该函数生成一个epoll专用的文件描述符

参数:size为epoll上能关注的最大描述符数

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:用于控制某个epoll文件描述符时间,可以注册、修改、删除

参数:epfd由epoll_create生成的epoll专用描述符

    op操作:EPOLL_CTL_ADD 注册   EPOLL_CTL_MOD修改  EPOLL_DEL删除

            fd:关联的文件描述符

    evnet告诉内核要监听什么事件

int epoll_wait(int epfd,struct epoll_event*events,int maxevents,int timeout);
功能:该函数等待i/o事件的发生。

参数:epfd要检测的句柄

    events:用于回传待处理时间的数组

    maxevents:告诉内核这个events有多大,不能超过之前的size

    timeout:为超时时间

使用方法参考:https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c


epoll支持的FD上限是最大可以打开文件的数目(select面临这样的问题),IO效率不随FD数目增加而线性下降(select、poll面临的问题)使用mmap加速内核与用户空间的消息传递。现在libevent封装了几种的实现,可以通过使用libevent来实现多路复用。


 本文参考:https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

   http://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/index.html?ca=drs-

             http://www.ibm.com/developerworks/cn/linux/l-async/









GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐