epoll 의 특징은 stateful 함수로서 호출할 때마다 커널 영역과 유저 영역 사이에 모든 파일 기술자 정보 리스트와 부가적인 정보를 복사하는 오버헤드가 없다. 파일 기술자를 등록, 해제, 변경 하는 함수와 이벤트를 감시하는 함수가 분리되었고 메모리 복사의 부담이 많이 줄어 들었다.
그리고 엣지 트리거의 지원이 추가 되었다. 물론 기본값으로는 레벨 트리거를 지원하고 있다
( 용어 해설 )
레벨 트리거 : 일정한 전위 수준을 넘었는지 감지
엣지 트리거 : 전위차가 발생하는 엣지 부분에서 상태 변화를 감지
epoll function
int epoll_create(int size); - > 커널 객체 공간 확보
int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event); - > 이벤트 등록 및 해제
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); - > 발생되는 이벤트를 리턴받는 함수
단계적으로 보는 epoll
1. epoll 생성 ( 인자 1 은 의미가 없다. 커널에서 관리하고, 무의미하더라도 양수값을 너주는게 원칙.
if ( (epollfd = epoll_create(1)) == -1 ) { ...error ... }
2. event 구조체 할당 ( epoll_wait에서 사용할 구조체 )
if ( ep_events = calloc(max_ep_events,sizeof(struct epoll_event) ) == NULL ) { ... error... }
3. epoll 등록
epoll_event 구조체에서 events 와 data.fd 만 설정 하여 epoll_ctl 함수를 이용해 등록한다.
랩핑된 함수를 예제로 한다.
int add_ev(int efd, int fd) {
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLPRI;
ev.data.fd = fd;
if( epoll_ctl(efd,EPOLL_CTL_ADD, fd, &ev ) == -1 ) { ..error.. }
}
efd 는 epoll 파일 기술자, fd 는 추가하려는 파일 기술자.
이후 while 문 안에서, epoll_wait 함수에 리턴 되는 이벤트 발생 fd 만 처리해주면 된다.
예제를 보면.
while( 1 ) {
if ( (ret_poll = epoll_wait(epollfd, ep_events,max_open_files, -1 ) ) == -1 ) { error } // ret_poll에 이벤트가 발생한 fd 개수가 리턴 되고.
for ( i = 0 ; i < ret_poll ; i ++ ) { // 그 개수만큼 루프를 도는데.
if (ep_events[i].events & EPOLLIN) { // 이벤트 분기 처리를 진행 한다.
if ( ep_events[i].data.fd == fd_listener ) ... // 리스너 소켓이면 accept 해주고 continue
continue
if ( ret_recv = recv(ep_events[i].data.fd, buf , sizeof(buf) , 0 ) == -1 ) { .. error .. }
else {
if ( ret_recv == 0 ) Passive close routine.
epoll_ctl(epollfd,EPOLL_CTL_DEL,ep_events[i].data.fd,NULL)
else // 정상 리시브 인 경우
ret_recv 처리.
}
}
}
}
여기서 EPOLLPRI 이벤트는 OOB ( Out of Band ) 데이터를 처리하는 루틴 이다.
EPOLL _ 소켓 통신 예제 소스
/* vim: set ts=4 sw=4: */
/* Filename : io_epoll.c
* Description : I/O multiplexing implementation with epoll
* Author : SunYoung Kim <sunyzero@gmail.com>
* Notes : when define ENABLE_MSG_OOB, it allow to receive OOB data
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include "stdalsp.h"
#define LISTEN_BACKLOG 256
#define ADD_EV(a, b) if (add_ev(a, b) == -1) { pr_err("Fail: add_ev"); exit(EXIT_FAILURE); }
#define DEL_EV(a, b) if (del_ev(a, b) == -1) { pr_err("Fail: del_ev"); exit(EXIT_FAILURE); }
const int max_ep_events = 256;
int epollfd; /* epoll fd */
int fcntl_setnb(int fd); /* set non-blocking mode */
int add_ev(int efd, int fd); /* Add epoll variable to epollfd */
int del_ev(int efd, int fd); /* delete epoll variabe from epollfd */
int main(int argc, char *argv[])
{
/* network var */
socklen_t len_saddr;
int fd, fd_listener;
int ret_recv;
char *port, buf[1024];
/* I/O multiplexing var */
struct epoll_event *ep_events;
int ret_poll;
/* logical var */
int i;
if (argc > 2) {
printf("%s [port number]\n", argv[0]);
exit(EXIT_FAILURE);
}
if (argc == 2) {
port = strdup(argv[1]);
} else {
port = strdup("0"); /* random port */
}
struct addrinfo ai, *ai_ret;
int rc_gai;
memset(&ai, 0, sizeof(ai));
ai.ai_family = AF_INET;
ai.ai_socktype = SOCK_STREAM;
ai.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
if ((rc_gai = getaddrinfo(NULL, port, &ai, &ai_ret)) != 0) {
pr_err("Fail: getaddrinfo():%s", gai_strerror(rc_gai));
exit(EXIT_FAILURE);
}
if ((fd_listener = socket(
ai_ret->ai_family,
ai_ret->ai_socktype,
ai_ret->ai_protocol)) == -1) {
pr_err("Fail: socket()");
exit(EXIT_FAILURE);
}
fcntl_setnb(fd_listener); /* set nonblock flag */
if (bind(fd_listener, ai_ret->ai_addr, ai_ret->ai_addrlen) == -1) {
pr_err("Fail: bind()");
exit(EXIT_FAILURE);
}
if (!strncmp(port, "0", strlen(port))) { /* port 번호를 알아낸다. */
struct sockaddr_storage saddr_s;
len_saddr = sizeof(saddr_s);
getsockname(fd_listener, (struct sockaddr *)&saddr_s, &len_saddr);
if (saddr_s.ss_family == AF_INET) {
pr_out("bind : IPv4 Port : #%d",
ntohs(((struct sockaddr_in *)&saddr_s)->sin_port));
} else if (saddr_s.ss_family == AF_INET6) {
pr_out("bind : IPv6 Port : #%d",
ntohs(((struct sockaddr_in6 *)&saddr_s)->sin6_port));
} else {
pr_out("getsockname : ss_family=%d", saddr_s.ss_family);
}
} else {
pr_out("bind : %s", port);
}
listen(fd_listener, LISTEN_BACKLOG);
if ((epollfd = epoll_create(1)) == -1) {
/* error */
exit(EXIT_FAILURE);
}
if ((ep_events = calloc(max_ep_events, sizeof(struct epoll_event))) == NULL) {
/* error */
exit(EXIT_FAILURE);
}
ADD_EV(epollfd, fd_listener);
while (1) {
/* waiting for data (no timeout) */
pr_out("Epoll waiting ...");
if ((ret_poll = epoll_wait(epollfd, ep_events, max_ep_events, -1)) == -1) {
/* error */
}
pr_out("EPoll return (%d)", ret_poll);
/* Are there data to read? */
for (i=0; i<ret_poll; i++) {
if (ep_events[i].events & EPOLLIN) {
/* IS New connection ? */
if (ep_events[i].data.fd == fd_listener) {
struct sockaddr_storage saddr_c;
while(1) {
len_saddr = sizeof(saddr_c);
fd = accept(fd_listener, (struct sockaddr *)&saddr_c, &len_saddr);
if (fd == -1) {
if (errno == EAGAIN) { /* no more new connection */
break;
}
pr_err("Error get connection from listen socket");
/* need error processing */
break;
}
fcntl_setnb(fd);
ADD_EV(epollfd, fd);
pr_out("accept : add socket (%d)", fd);
}
continue;
}
if ((ret_recv = recv(ep_events[i].data.fd, buf, sizeof(buf), 0)) == -1) {
/* error */
} else {
if (ret_recv == 0) {
/* closed */
pr_out("fd(%d) : Session closed", ep_events[i].data.fd);
DEL_EV(epollfd, ep_events[i].data.fd);
} else {
/* normal */
pr_out("recv(fd=%d,n=%d) = %.*s",
ep_events[i].data.fd, ret_recv, ret_recv, buf);
}
}
#ifdef ENABLE_MSG_OOB
} else if (ep_events[i].events & EPOLLPRI) {
pr_out("EPOLLPRI : Urgent data detected");
if ((ret_recv = recv(ep_events[i].data.fd, buf, 1, MSG_OOB)) == -1) {
/* error */
}
pr_out("recv(fd=%d,n=1) = %.*s (OOB)", ep_events[i].data.fd, 1, buf);
#endif
} else if (ep_events[i].events & EPOLLERR) {
/* error */
} else {
pr_out("fd(%d) epoll event(%d) err(%s)",
ep_events[i].data.fd, ep_events[i].events, strerror(errno));
}
}
}
return 0;
}
/* Name : add_ev
* Desc : add socket descriptor to epoll
* Argv : int efd, int fd
* Ret : zero(if success), negative(if fail)
*/
int add_ev(int efd, int fd)
{
struct epoll_event ev;
/* if want to use edge trigger, add EPOLLET to events */
#ifdef ENABLE_MSG_OOB
ev.events = EPOLLIN | EPOLLPRI;
#else
ev.events = EPOLLIN ;
#endif
ev.data.fd = fd;
if (epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev) == -1) {
pr_out("fd(%d) EPOLL_CTL_ADD Error(%d:%s)", fd, errno, strerror(errno));
return -1;
}
return 0;
}
/* Name : del_ev
* Desc : delete socket descriptor from epoll
* Argv : int efd, int fd
* Ret : zero(if success), negative(if fail)
*/
int del_ev(int efd, int fd)
{
if (epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL) == -1) {
pr_out("fd(%d) EPOLL_CTL_DEL Error(%d:%s)", fd, errno, strerror(errno));
return -1;
}
close(fd);
return 0;
}
/* Name : fcntl_setnb
* Desc : set non-blocking mode
* Argv : int fd
* Ret : zero(if success), negative(if fail)
*/
int fcntl_setnb(int fd)
{
/* only influence about O_ASYNC, O_APPEND, O_NONBLOCK on Linux-specific */
if (fcntl(fd, F_SETFL, O_NONBLOCK | fcntl(fd, F_GETFL)) == -1) {
return errno;
}
return 0;
}
'C 프로젝트 > 리눅스 프로그래밍' 카테고리의 다른 글
Thread Safe (0) | 2014.05.23 |
---|---|
Socket Non-blocking (0) | 2014.05.22 |
C 라이브러리 - 패턴 매칭 사용 예제 (0) | 2014.04.04 |
C 라이브러리 - 패턴 매칭 (0) | 2014.04.04 |
조건 변수 , 뮤텍스 (0) | 2013.12.26 |