EPoll模型

epoll仅仅是一个异步事件的epoll仅仅是一个异步事件的通知机制,其本身并不作任何的IO读写操作,它只负责告诉你是不是可以读或可以写了,而具体的读写操作,还要应用程序自己来完成。epoll仅提供这种机制是非常好的,它保持了事件通知与IO操作之间彼此的独立性,使得epoll的使用更加灵活。通知机制,其本身并不作任何的IO读写操作,它只负责告诉你是不是可以读或可以写了,而具体的读写操作,还要应用程序自己来完成。epoll仅提供这种机制是非常好的,它保持了事件通知与IO操作之间彼此的独立性,使得epoll的使用更加灵活。

epoll接口介绍

int epoll_create(int size)

该函数生成一个 epoll 专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的 fd 上是否发生 的事件。 size 就是你在这个 epoll fd 上能关注的最大 fd ,这个参数不同于 select() 中的第一个参数,给出最大监听的 fd+1 的值。需要注意的是,当创建好 epoll 句柄后,它就会占用一个 fd 值,在 linux 下如果查看 /proc/ 进程 id/fd/ ,是能够看到这个 fd 的,所以在使用完 epoll 后,必须调用 close() 关闭,否则可能导致 fd 被耗尽。


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

该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。 
epfd:由 epoll_create生成的epoll专用的文件描述符; 
op:要进行的操作例如注册事件,可能的取值:
1)EPOLL_CTL_ADD 注册新的fd到epfd中;
2)EPOLL_CTL_MOD修改已经注册的fd的监听事件;
3)EPOLL_CTL_DEL 从epfd中删除一个fd;
fd:需要监听的fd 
event:指向epoll_event的指针,告诉内核需要监听的事件,常用的事件类型: 
1)EPOLLIN :表示对应的文件描述符可以读;
2)EPOLLOUT:表示对应的文件描述符可以写;
3)EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
4)EPOLLERR:表示对应的文件描述符发生错误;
5)EPOLLHUP:表示对应的文件描述符被挂断;
6)EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
7)EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个IO的话,需要再次把这个IO加入到EPOLL队列里。
如果调用成功返回0,不成功返回-1 


int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)

该函数用于轮询 I/O 事件的发生 ,如果发生则将发生的 fd 和事件类型放入到 events 数组中。 并且将注册在 epfd 上的 fd 的事件类型给清空,所以如果下一个循环你还要关注这个 fd 的话,则需要用 epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev) 来重新设置 fd 的事件类型。这时不用 EPOLL_CTL_ADD, 因为 fd 并未清空,只是事件类型清空。
epfd: epoll_create 生成的 epoll 专用的文件描述符; 
epoll_event: 用于回传 待处理事件的数组; 
maxevents: 每次能处理的事件数; 
timeout: 等待 I/O 事件发生的超时值 ,为 0 的时候表示马上返回,为 -1 的时候表示一直等下去,直到有事件 返回。

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};
typedef union epoll_data
{
      void *ptr;
      int fd;
      __uint32_t u32;
      __uint64_t u64;
} epoll_data_t;

epoll_data:保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)

epoll_event:感兴趣的事件和被触发的事件


epoll事件的两种模型


Edge Triggered (ET)  边缘触发 只有数据到来,才触发,不管缓存区中是否还有数据。

Level Triggered (LT)  水平触发 只要有数据都会触发


假如有这样一个例子:

1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符

2. 这个时候从管道的另一端被写入了2KB的数据

3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作

4. 然后我们读取了1KB的数据

5. 调用epoll_wait(2)......


Edge Triggered 工作模式:

如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。


Level Triggered 工作模式

相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。


下面以默认的level triggered为例子:


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<jni.h>
#include<signal.h>
#include<sys/time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <limits.h>
#include<android/log.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native-activity", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "native-activity", __VA_ARGS__))
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif

static char CLASS_NAME[] = "com/sailor/EPoll";
static char CB_METHOD[] = "callBack";
static int mWakeReadPipeFd = -1;
static int mWakeWritePipeFd = -1;
static int mEpollFd; // immutable
static int nret = -1;
static int EPOLL_SIZE_HINT = 8;
void DestroyUnNamePipe(JNIEnv *env, jobject thiz)
{
	if(nret == 0)
	{
		close(mWakeReadPipeFd);
		close(mWakeWritePipeFd);
		close(mEpollFd);
	}
}

jint CreateUnNamePipe(JNIEnv *env, jobject thiz)
{
	int ret = -1;
	int result = -1;
	int fd[2];
	struct epoll_event eventItem;
	ret = pipe(fd);
	
	
	
	if(ret != 0)
	{
		LOGE("create unname pipe error:%d" ,ret);
	}
	else
	{
		
		mWakeWritePipeFd = fd[1];
		mWakeReadPipeFd  = fd[0];
		// 设置为非阻塞
		result = fcntl(mWakeReadPipeFd, F_SETFL, fcntl(mWakeReadPipeFd, F_GETFD, 0)|O_NONBLOCK);
		if(result != 0)
		{
			LOGE("can not set to nonblock");
		}

		result = fcntl(mWakeWritePipeFd, F_SETFL, fcntl(mWakeWritePipeFd, F_GETFD, 0)|O_NONBLOCK);
		if(result != 0)
		{
			LOGE("can not set to nonblock");
		}
		
		LOGE("set poll mode");
		mEpollFd = epoll_create(EPOLL_SIZE_HINT);
		if(mEpollFd < 0)
		{
			LOGE("could not create epoll");
		}
		else
		{
			
    			memset(&eventItem, 0, sizeof(struct epoll_event)); // zero out unused members of data field union
    			eventItem.events = EPOLLIN;
    			eventItem.data.fd = mWakeReadPipeFd;
    			ret = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
			if(ret != 0)
			{
				LOGE("could not add wake read pipe to epoll");
			}
		}
             
		
	}
	
	nret = ret;
	return ret;
}
void ReadUNPMessage(JNIEnv *env, jobject thiz)
{
	int select_ret;
	int ret = -1;
	int i = 0;
	struct epoll_event eventItem[EPOLL_SIZE_HINT];
	/*select_ret = select(maxfds,&fd_set,NULL,NULL,&timeout);*/
	while(1)
	{
		LOGE("poll start");
		
		select_ret = epoll_wait(mEpollFd, eventItem, EPOLL_SIZE_HINT, -1);
		LOGE("epoll ret:%d" , select_ret);
		/*LOGE("POLLER:%d", POLLER);*/
		LOGE("EPOLLHUP:%d", EPOLLHUP);
		LOGE("EPOLLERR:%d", EPOLLERR);
		if(select_ret > 0  && nret == 0)
		{
			for(i = 0; i < select_ret; i++)
			{
			if(eventItem[i].data.fd == mWakeReadPipeFd)  
			{  
			if((eventItem[i].events & EPOLLIN))
			{
				//call java method
				char buffer[80+1];
				memset(buffer, 0, sizeof(buffer));
				ret = read(mWakeReadPipeFd, buffer, sizeof(buffer));
				LOGE("read ret:%d" , ret);
				if(ret == -1)
					return ;
				else
				{
					jbyteArray array = (*env)->NewByteArray(env, sizeof(buffer));
			(*env)->SetByteArrayRegion(env, array, 0, sizeof(buffer), (char *)buffer);

					jclass jhandlerClass = (*env)->GetObjectClass(env, thiz);  
    jmethodID doActionMethodId = (*env)->GetMethodID(env, jhandlerClass, CB_METHOD, "([B)V");  
    (*env)->CallVoidMethod(env, thiz, doActionMethodId, array);  
					(*env)->ReleaseByteArrayElements(env, array, buffer, 0);
					
				}
			}
			/*else if(eventItem[i].events & EPOLLERR)
			{
				LOGE("fd closed, epoll end");
				break;
			}*/
			LOGE("fds revents:%d", eventItem[i].events);
		}
		}
		}
	}
	
	return ;
	
}

jint SendUNPMessage(JNIEnv *env, jobject thiz, jstring message)
{
	int ret = -1;
	if(nret == 0)
	{
		const char *pMsg = (*env)->GetStringUTFChars(env, message, 0);

		ret = write(mWakeWritePipeFd, pMsg, strlen(pMsg));
		LOGE("send ret: %d", ret);
		
		(*env)->ReleaseStringUTFChars(env, message, pMsg);
	}
	return ret;
}

static JNINativeMethod mehods[] = {
	
		{ "createUnNamePipe", "()I", 
			(void *) CreateUnNamePipe },	
		{ "sendUNPMessage", "(Ljava/lang/String;)I", (void *) SendUNPMessage },	
		{ "readUNPMessage", "()V", (void *) ReadUNPMessage },
		{ "destroyUnNamePipe", "()V", (void *) DestroyUnNamePipe }
};


static int registerNativeMethods(JNIEnv *env, const char* className,
                                 const JNINativeMethod* methods, int numMethods)
{
    int rc;
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'\n", className);
        return -1;
    }
    if (rc = ((*env)->RegisterNatives(env, clazz, methods, numMethods)) < 0) {
        LOGE("RegisterNatives failed for '%s' %d\n", className, rc);
        return -1;
    }
    return 0;
}


static int register_jni(JNIEnv *env)
{
	return registerNativeMethods(env, CLASS_NAME, mehods, NELEM(mehods));
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
     JNIEnv* env = NULL;
     jint result = -1;    
 
     //获取JNI版本
     if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) 
     {
         LOGE("GetEnv failed!");
             return result;
     }

     if (register_jni(env) < 0) 
	{
	LOGE("register method failed!");
             return result;
     }
     return JNI_VERSION_1_4;
}


java类:

package com.sailor;

import android.util.Log;

public class EPoll {
	static {
		// 加载动态库
		System.loadLibrary("JNIEPoll");
	}
	public native int createUnNamePipe();
	public native int sendUNPMessage(String msg);
	public native void readUNPMessage();
	public native void destroyUnNamePipe();
	
	public void callBack(byte[] buffer){
		
		Log.d("jacklam", "read msg:" + new String(buffer));
	}
}


调用:

public void startEPoll3(View view){
		if(epoll != null)
			epoll.destroyUnNamePipe();
	}
	
	
	public void startEPoll2(View view){
		if(epoll != null){
			epoll.sendUNPMessage("test epoll mode");
		}
		/*mem.close(fd);*/
	}
	
	public void startEPoll1(View view){
		epoll = new EPoll();
		epoll.createUnNamePipe();
		Thread thread = new Thread(new Runnable() {

			@Override
			public void run() {
				
				epoll.readUNPMessage();
				
			}
			
		});
		thread.start();
		/*mem.close(fd);*/
	}

显示:

10-14 15:29:54.850: E/native-activity(21278): set poll mode
10-14 15:29:54.850: E/native-activity(21278): poll start
10-14 15:29:57.082: E/native-activity(21278): epoll ret:1
10-14 15:29:57.082: E/native-activity(21278): EPOLLHUP:16
10-14 15:29:57.082: E/native-activity(21278): EPOLLERR:8
10-14 15:29:57.082: E/native-activity(21278): read ret:15
10-14 15:29:57.092: D/jacklam(21278): read msg:test epoll mode????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
10-14 15:29:57.092: W/dalvikvm(21278): JNI: unpinPrimitiveArray(0x40dac570) failed to find entry (valid=1)
10-14 15:29:57.092: E/native-activity(21278): fds revents:1
10-14 15:29:57.092: E/native-activity(21278): send ret: 15
10-14 15:29:57.092: E/native-activity(21278): poll start
10-14 15:30:11.817: E/native-activity(21278): send ret: -1



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值