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