epoll EPOLLONESHOT
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里,这样IN事件会被保留。EPOLLONESHOT区别就是,每次IN事件都是串行进行,而不是多个in事件并行。
ET模式与LT模式EPOLLONESHOT处理相同
(1)设置EPOLLONESHOT,创建线程读取,线程中不在将socket放入epoll队列,后续再发数据不触发in事件,无法读取。代码如下:
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <iostream>
using namespace std;
#define MAX_EVENTS 10000
int epfd = 0;
void* process(void* ptr)
{
int sockfd = *(int*)ptr;
char buf;
while(read(sockfd, &buf, 1) > 0)
{
printf("%c\n",buf);
sleep(5);
}
cout << "thread out " << endl;
//event->events = EPOLLIN | EPOLLET;
//epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, event);
}
int main(int argc, char **argv)
{
short port = 6002; // default port
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
int reuse = 1;
setsockopt(listenFd , SOL_SOCKET , SO_REUSEADDR , &reuse , sizeof(reuse));
fcntl(listenFd, F_SETFL, O_NONBLOCK); // 设置非阻塞方式
sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
bind(listenFd, (const sockaddr*) &sin, sizeof(sin));
listen(listenFd, 5);
struct epoll_event ev, events[MAX_EVENTS];
//生成用于处理accept的 epoll专用的文件描述符
epfd = epoll_create(MAX_EVENTS);
//设置与要处理的事件相关的文件描述符
ev.data.fd = listenFd;
//设置要处理的事件类型
ev.events = EPOLLIN | EPOLLET;
//注册epoll事件
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenFd, &ev) < 0)
{
printf("worker epoll_ctl error = %s.", strerror(errno));
exit(1);
}
while (true)
{
// 等待epoll事件的发生
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
// 处理所发生的所有事件
for (int i = 0; i < nfds; ++i) // for循环中可以修改为线程池处理epoll事件
{
if (events[i].data.fd == listenFd)
{
socklen_t clilen;
struct sockaddr_in clientaddr;
int sockfd = accept(listenFd, (sockaddr *) &clientaddr,
&clilen);
if (sockfd < 0)
{
continue;
}
// 设置非阻塞
if (fcntl(sockfd, F_SETFL,
fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK) == -1)
{
continue;
}
ev.data.fd = sockfd;
//设置用于注测的读操作事件
ev.events = EPOLLIN | EPOLLET |EPOLLONESHOT;
//注册ev
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
}
else if (events[i].events & EPOLLIN)
{
cout << "epollin--->" << endl;
int sockfd = events[i].data.fd;
pthread_t tid;
pthread_create(&tid, NULL, process, &sockfd);
pthread_detach(tid);
usleep(1000);
}
}
}
return 0;
}
(2)设置EPOLLONESHOT,创建线程读取,线程返回前将socket放入epoll队列,后续再次发送数据会触发in事件。代码如下:
/*
* main.cpp
*
* Created on: 2012-12-27
* Author: root
*/
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <iostream>
using namespace std;
#define MAX_EVENTS 10000
int epfd = 0;
void* process(void* ptr)
{
int sockfd = *(int*)ptr;
struct epoll_event ev;
char buf;
while(read(sockfd, &buf, 1) > 0)
{
printf("%c\n",buf);
sleep(5);
}
cout << "thread out " << endl;
ev.data.fd = sockfd;
//设置用于注测的读操作事件
ev.events = EPOLLIN | EPOLLET |EPOLLONESHOT;
//注册ev
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
int main(int argc, char **argv)
{
short port = 6002; // default port
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
int reuse = 1;
setsockopt(listenFd , SOL_SOCKET , SO_REUSEADDR , &reuse , sizeof(reuse));
fcntl(listenFd, F_SETFL, O_NONBLOCK); // 设置非阻塞方式
sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
bind(listenFd, (const sockaddr*) &sin, sizeof(sin));
listen(listenFd, 5);
struct epoll_event ev, events[MAX_EVENTS];
//生成用于处理accept的 epoll专用的文件描述符
epfd = epoll_create(MAX_EVENTS);
//设置与要处理的事件相关的文件描述符
ev.data.fd = listenFd;
//设置要处理的事件类型
ev.events = EPOLLIN | EPOLLET;
//注册epoll事件
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenFd, &ev) < 0)
{
printf("worker epoll_ctl error = %s.", strerror(errno));
exit(1);
}
while (true)
{
// 等待epoll事件的发生
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
// 处理所发生的所有事件
for (int i = 0; i < nfds; ++i) // for循环中可以修改为线程池处理epoll事件
{
if (events[i].data.fd == listenFd)
{
socklen_t clilen;
struct sockaddr_in clientaddr;
int sockfd = accept(listenFd, (sockaddr *) &clientaddr,
&clilen);
if (sockfd < 0)
{
continue;
}
// 设置非阻塞
if (fcntl(sockfd, F_SETFL,
fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK) == -1)
{
continue;
}
ev.data.fd = sockfd;
//设置用于注测的读操作事件
ev.events = EPOLLIN | EPOLLET |EPOLLONESHOT;
//注册ev
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
}
else if (events[i].events & EPOLLIN)
{
cout << "epollin--->" << endl;
int sockfd = events[i].data.fd;
pthread_t tid;
pthread_create(&tid, NULL, process, &sockfd);
pthread_detach(tid);
usleep(1000);
}
}
}
return 0;
}
(3)设置EPOLLONESHOT,创建线程读取,线程返回前,sleep几秒,在这几秒内,在将socket放入epoll队列前,发送数据,后续再次发送数据会触发in事件。
备忘:
边界触发的效率高,但程序实现上要小心,漏掉没处理的不会得到提醒了。
而电平触发的程序实现上方便,效率要低些,似乎libevent和lighttpd都用的它。