epoll이란?
epoll은 리눅스 커널 2.5.44부터 도입된 Multiplexing IO 함수로 다수의 파일 디스크립터를 모니터링 할 때 효율적이다. epoll은 epoll_event 구조체와 다음의 세 가지 주요 함수로 구성된다.
- epoll_create : epoll 파일 디스크립터 저장소 생성
- epoll_ctl : 저장소에 파일 디스크립터 등록 및 삭제
- epoll_wait : 파일 디스크립터의 변화를 대기
select와의 차이점
select의 경우 매번 호출할 때마다 파일 디스크립터 집합을 사용자 공간에서 커널 공간으로 복사해야 하지만 epoll는 한 번 설정한 파일 디스크립터 집합을 커널 공간에서 유지하여 불필요한 복사를 방지한다. 따라서 많은 수의 파일 디스크립터를 다룰 때 epoll이 더욱 효율적이며 사용자 입장에서 다음의 차이가 있다.
- 상태변화의 확인을 위해 전체 파일 디스크립터를 대상으로 하는 반복문이 필요 없다.
- select 함수에 대응되는 epoll_wait 함수 호출 시 관찰 대상의 정보를 매번 전달할 필요가 없다.
epoll_event 구조체
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;
- events : 감지할 이벤트 (e.g, EPOLLIN, EPOLLOUT, EPOLLERR)
- data : 사용자 데이터, 보통 파일 디스크립터를 저장
epoll_event 배열을 넉넉한 길이로 선언해서 epoll_wait 함수호출 시 인자로 전달하면 이벤트가 발생한 경우 배열의 인덱스 0부터 차곡차곡 이벤트 정보가 저장된다.
한편 epoll_event는 파일 디스크립터를 epoll 인스턴스에 추가할 때 이벤트의 유형을 등록하는 용도로도 사용된다.
events에 저장 가능한 상수
OR 연산자를 이용해 둘 이상을 함께 등록할 수 있다.
- EPOLLIN : 수신할 데이터가 존재하는 상황
- EPOLLOUT : 출력버퍼가 비워져서 당장 데이터를 전송할 수 있는 상황
- EPOLLPRI : OOB 데이터가 수신된 상황
- EPOLLRDHUP : 연결이 종료되거나 Half-close가 진행된 상황
- EPOLLERR : 에러가 발생한 상황
- EPOLLET : 이벤트의 감지를 엣지 트리거 방식으로 동작시킨다.
- EPOLLONESHOT : 이 플래그가 설정된 파일 디스크립터에 이벤트가 발생하면, 커널은 해당 파일 디스크립터를 epoll 인스턴스에서 자동으로 비활성화한다. 이후 같은 파일 디스크립터의 이벤트를 다시 감지하려면 명시적으로 epoll_ctl 함수를 호출하며 EPOLL_CTL_MOD를 인자로 넘겨 재등록해야 한다.
epoll_create
epoll 인스턴스를 생성한다.
int epoll_create(int size);
- size : epoll 인스턴스의 크기 결정 시 참고로만 사용되는 파라미터였으나 현대 리눅스에서는 무시된다.
- 리턴 값 : 성공 시 epoll 파일 디스크립터를 반환하고 실패시 -1을 반환한다.
epoll_ctl
epoll 인스턴스에 파일 디스크립터를 추가, 수정 또는 삭제한다.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- epfd : epoll 파일 디스크립터
- op : 수행할 작업, EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTR_DEL
- fd : 대상 파일 디스크립터
- event : 모니터링할 이벤트의 종류를 정의하는 epoll_event 구조체
- 리턴 값 : 성공 시 0을 반환하고 실패시 -1을 반환한다.
epoll_wait
epoll 인스턴스를 사용하여 이벤트가 발생할 때까지 대기한다.
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- epfd : epoll 파일 디스크립터
- events : 이벤트가 발생한 파일 디스크립터 정보를 저장할 배열
- maxevents : 이벤트 배열의 최대 크기
- timeout : 밀리초 단위의 대기시간, -1은 무한대기한다.
- 리턴 값 : 발생한 이벤트 수를 반환하고 실패 시 -1을 반환한다.
epoll의 단계별 사용방식
1. epoll 인스턴스 생성 (epoll_create)
- 모니터링할 파일 디스크립터들을 관리할 epoll 인스턴스를 생성한다.
2. 파일 디스크립터 추가 (epoll_ctl)
- 생성된 epoll 인스턴스에 모니터링할 파일 디스크립터를 추가한다.
3. 이벤트 대기 (epoll_wait)
- 이벤트가 발생할 때까지 대기한다.
4. 이벤트 처리
- epoll_wait이 반환한 이벤트 수만큼 반복하여 각 파일 디스크립터의 이벤트를 처리한다.
epoll 사용 에코 서버 예제
echo_epollserv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *buf);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t adr_sz;
int str_len, i;
char buf[BUF_SIZE];
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
// 서버 소켓 생성
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
// 서버 소켓 바인딩
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
// 리슨
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
// epoll 인스턴스 생성
epfd=epoll_create(EPOLL_SIZE);
// epoll_event 구조체 배열 동적 할당
ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
// 서버 소켓의 읽기 이벤트 감지하도록 epoll 인스턴스에 추가
event.events=EPOLLIN;
event.data.fd=serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
while(1)
{
// 이벤트 대기
event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(event_cnt==-1)
{
puts("epoll_wait() error");
break;
}
// 이벤트 처리
for(i=0; i<event_cnt; i++)
{
// 서버 소켓인 경우 새로운 클라이언트 연결 처리
if(ep_events[i].data.fd==serv_sock)
{
adr_sz=sizeof(clnt_adr);
clnt_sock=
accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
event.events=EPOLLIN;
event.data.fd=clnt_sock;
// 클라이언트 소켓의 읽기 이벤트 감지하도록 epoll 인스턴스에 추가
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("connected client: %d \n", clnt_sock);
}
// 클라이언트 소켓인 경우 데이터 처리
else
{
str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
// 클라이언트 연결 종료 요청이 온 경우 처리
if(str_len==0) // close request!
{
epoll_ctl(
epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
close(ep_events[i].data.fd);
printf("closed client: %d \n", ep_events[i].data.fd);
}
else if(str_len > 0)
{
// 클라이언트에 데이터 에코
write(ep_events[i].data.fd, buf, str_len); // echo!
}
else
{
error_handling("read() error");
}
}
}
}
close(serv_sock);
close(epfd);
return 0;
}
void error_handling(char *buf)
{
fputs(buf, stderr);
fputc('\n', stderr);
exit(1);
}
echo_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
if(argc!=3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0);
if(sock==-1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("connect() error!");
else
puts("Connected...........");
while(1)
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
write(sock, message, strlen(message));
str_len=read(sock, message, BUF_SIZE-1);
message[str_len]=0;
printf("Message from server: %s", message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
$ gcc echo_epollserv.c -o serv
$ gcc echo_client.c -o client
$ ./serv 9190
새 터미널
$ ./client 127.0.0.1 9190
출처 : 윤성우의 열혈 TCP/IP 소켓 프로그래밍
'프로그래밍 > C' 카테고리의 다른 글
winmm API 사용하여 WAV 파일 재생하기 (0) | 2024.07.04 |
---|---|
POSIX thread, mutex 사용법 (0) | 2024.06.26 |
__DATE__ 에서 필요한 정보를 추출하여 원하는 포맷으로 출력하기 (0) | 2024.04.17 |
C언어 ] qsort (Quick Sort, 퀵정렬) 함수 사용법 (0) | 2024.01.30 |
윈도우 소켓 ] connect() 함수의 타임아웃은 21초 (0) | 2023.12.25 |