码迷,mamicode.com
首页 > 其他好文 > 详细

epoll 和select 的区别,epoll和select的实例

时间:2015-07-28 21:07:42      阅读:108      评论:0      收藏:0      [点我收藏+]

标签:epoll和select用法   epoll和select对比   

    epoll是Linux内核为处理高并发而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本。这里主要讲epoll和另外两个的区别,另外再把epoll的一个简单运用实例说说。

(一)epoll 有select,poll的主要区别:

一、相比于select与poll, epoll最大的好处在于它不会随着监听fd数目的增长而降低效率;

二、内核中的select与poll的实现是采用轮询来处理的,轮询的fd数据越多,自然耗时也越多;

三、epoll的实现是基于回调的,如果fd有期望的事件发生就通过回调函数将其加入epoll的就绪对了当中,也就是说它只关心活跃的fd

四、内核/用户空间拷贝问题,当内核把fd消息通知给用户空间时,selec和poll采用了内存拷贝的方法,而epoll采用了共享内存的方式。


(二)epoll 相关系统调用

epoll的接口非常简单,一共就三个函数:
一、(1)int epoll_create(int size);
    创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这里其实是指定hash表的容量。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽

    (2)int epoll_create(int flags);

      在比较新的Linux的版本中,出现了 int epoll_create(int flags);  这里的flags 一般选EPOLL_CLOEXEC,表示当进程被替换的时候文件描述符会被关闭。

二、 int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
   epoll的事件注册函数,即注册要监听的事件类型。
   第一个参数是epoll_create()的返回值,
   第二个参数表示动作,用三个宏来表示:
   EPOLL_CTL_ADD:注册新的fd到epfd中;
   EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
   EPOLL_CTL_DEL:从epfd中删除一个fd;
   第三个参数是需要监听的fd,
   第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {
  __uint32_t events; 
  epoll_data_t data; 
};

typedef union epoll_data {
void *ptr;    //这里是个指针,主要用来存放复杂的文件描述符
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

  events可以是以下几个宏的集合:

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


三、int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
   等待事件的产生。参数events 用来从内核得到事件的集合,maxevents 告之内核这个events 有多大,这个maxevents 的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1一直等待,即阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

(三)epoll 实例

  这里以一个简单的客户端回射为例,程序的运行环境是debian(linux的一个发行版本),从上到下分别是头文件"net.h",epoll_server.cpp,select_client.cpp

服务器端:epoll实现的,干两件事分别为:1.等待客户端的链接,2.接收来自客户端的数据并且回射;

客户端:select实现,干两件事为:1.等待键盘输入,2.发送数据到服务器端并且接收服务器端回射的数据;

/*****************
@author:shaosli
@data: 2015/7/28
@funtions:network file
***************************/
#include <stdio.h>


#ifndef _NET_L
#define _NET_L

#include <iostream>
#include <vector>
#include <algorithm>

#include <stdio.h>
#include <sys/types.h>
#include <sys/epoll.h>  //epoll ways file
#include <sys/socket.h>
#include <fcntl.h>    //block and noblock

#include <stdlib.h>
#include <error.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>

using namespace std;


#define hand_error(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
#endif

服务器端代码:

/****************
@data: 2015/7/28
@authour:shaosli
@function: this cpp is used to test epoll 
**************************/

#include "../public/net.h"
#define MAX_EVENTS 10000

int setblock(int sock)
{
	int ret =  fcntl(sock, F_SETFL, 0);
	if (ret < 0 )
		hand_error("setblock");
	return 0;
}
int setnoblock(int sock)  //设置非阻塞模式
{
	int ret = fcntl(sock,  F_SETFL, O_NONBLOCK );
	if(ret < 0)
		hand_error("setnoblock");
	return 0;
}

int main()
{

    int listenfd;
	listenfd = socket( AF_INET, SOCK_STREAM,0 );   //create a socket stream
	if( listenfd < 0 )
		hand_error( "socket_create");
	setnoblock(listenfd);
	int on = 1;
	if( setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))< 0)
		hand_error("setsockopt");
		
	struct sockaddr_in my_addr;
	memset(&my_addr, 0, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(5188);   //here is host  sequeue
	my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	if( bind( listenfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)
		hand_error("bind");
	
	int lisId = listen(listenfd, SOMAXCONN);
	if( lisId < 0)   //LISTEN
		hand_error("listen");
		
	struct sockaddr_in peer_addr;   //用来 save client addr
	socklen_t peerlen;	
	//下面是一些初始化,都是关于epoll的。
	vector<int> clients;
	int count = 0;
	int cli_sock = 0;
	int epfd = 0;  //epoll 的文件描述符
	int ret_events;  //epoll_wait()的返回值
    struct epoll_event ev_remov, ev, events[MAX_EVENTS];  //events 用来存放从内核读取的的事件
	ev.events = EPOLLET | EPOLLIN;   //边缘方式触发
	ev.data.fd = listenfd;
	
	epfd = epoll_create(MAX_EVENTS);   //create epoll,返回值为epoll的文件描述符
	//epfd = epoll_create1(EPOLL_CLOEXEC);  //新版写法
	if(epfd < 0)
		hand_error("epoll_create");
	int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);   //添加时间
	if(ret < 0)
		hand_error("epoll_ctl");
	
	
	while(1)
	{
		ret_events = epoll_wait(epfd, events, MAX_EVENTS, -1);   //类似于select函数,这里是等待事件的到来。
		if(ret_events == -1)
		{
			cout<<"ret_events = "<<ret_events<<endl;
			hand_error("epoll_wait");
		}
		
		if( ret_events == 0)
		{
			cout<<"ret_events = "<<ret_events<<endl;
			continue;
		}
			
		cout<<"ret_events = "<<ret_events<<endl;
		for( int num = 0; num < ret_events; num ++)
		{
			cout<<"num = "<<num<<endl;
			if(events[num].events == listenfd)   //client connect;
			{
				cout<<"listen sucess and listenfd = "<<listenfd<<endl;
				cli_sock = accept(listenfd, (struct sockaddr*)&peer_addr, &peerlen);
				if(cli_sock < 0)
					hand_error("accept");
				cout<<"count = "<<count++;
				printf("ip=%s,port = %d\n", inet_ntoa(peer_addr.sin_addr),peer_addr.sin_port);
				clients.push_back(cli_sock);
				setnoblock(cli_sock);   //设置为非阻塞模式
				ev.data.fd = cli_sock;
				ev.events = EPOLLIN | EPOLLET ;
				if(epoll_ctl(epfd, EPOLL_CTL_ADD, cli_sock, &ev)< 0)
					hand_error("epoll_ctl");
			}
		
			else if( events[num].events & EPOLLIN)
			{
				cli_sock = events[num].data.fd;
				if(cli_sock < 0)
					hand_error("cli_sock");
				char recvbuf[1024];
				memset(recvbuf, 0 , sizeof(recvbuf));
				int num = read( cli_sock, recvbuf, sizeof(recvbuf));
				if(num == -1)
					hand_error("read have some problem:");
				if( num == 0 )  //stand of client have exit
					{
						cout<<"client have exit"<<endl;
						close(cli_sock);
						ev_remov = events[num];
						epoll_ctl(epfd, EPOLL_CTL_DEL, cli_sock, &ev_remov);
						clients.erase(remove(clients.begin(), clients.end(), cli_sock),clients.end());
					}
					fputs(recvbuf,stdout);
					write(cli_sock, recvbuf, strlen(recvbuf));
			}
		}
	}
		
	return 0;
}

客户端代码:

/****************
@data: 2015/7/18
@authour:shaosli
@function: this is a client
**************************/

#include "../public/net.h"

int main()
{
    int sock;
	sock = socket( AF_INET, SOCK_STREAM,0 );   //create a socket stream
	if( sock< 0 )
		hand_error( "socket_create");
	
	struct sockaddr_in my_addr;
	
	//memset my_addr;
	memset(&my_addr, 0, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(5188);   //here is host sequeue
	//my_addr.sin_addr.s_addr = htonl( INADDR_ANY );
	my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	int conn = connect(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) ;
	if(conn != 0)
		hand_error("connect");
	
	char recvbuf[1024] = {0};
	char sendbuf[1024] = {0};
	fd_set rset;
	FD_ZERO(&rset);     
	
	int nready = 0;
	int maxfd;
	int stdinof = fileno(stdin);
	if( stdinof > sock)
		maxfd = stdinof;
	else
		maxfd = sock;

	while(1)
	{
		//select返回后把原来待检测的但是仍没就绪的描述字清0了。所以每次调用select前都要重新设置一下待检测的描述字
		FD_SET(sock, &rset);  
		FD_SET(stdinof, &rset);
		nready = select(maxfd+1, &rset, NULL, NULL, NULL); 
		cout<<"nready = "<<nready<<endl;
		if(nready == -1 )
			break;
		else if( nready == 0)
			continue;
		else
		{
			if( FD_ISSET(sock, &rset) )  //检测sock是否已经在集合rset里面。
			{
				int ret = read( sock, recvbuf, sizeof(recvbuf));  //读数据
				if( ret == -1)
					hand_error("read");
				else if( ret == 0)
				{
					cout<<"sever have close"<<endl;
					close(sock);
					break;
				}
				else
				{
					fputs(recvbuf,stdout);    //输出数据
					memset(recvbuf, 0, strlen(recvbuf));
				}	
			}
		
			if( FD_ISSET(stdinof, &rset))   //检测stdin的文件描述符是否在集合里面
			{	
				if(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
				{
					int num = write(sock, sendbuf, strlen(sendbuf));   //写数据
					cout<<"sent num = "<<num<<endl;
					memset(sendbuf, 0, sizeof(sendbuf));
				}
			}
		}
	}
	return 0;
}



代码在我自己的系统上测试过。


版权声明:本文为博主原创文章,未经博主允许不得转载。

epoll 和select 的区别,epoll和select的实例

标签:epoll和select用法   epoll和select对比   

原文地址:http://blog.csdn.net/stefan1240/article/details/47106201

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!