반응형

epoll을 구현할 때 엣지 트리거와 레벨 트리거 2가지 방식으로 구현이 가능하다.  

두가지 개념을 설명하기 위해서 우선, 블록킹논블록킹 개념에 대해서 설명하겠다.

 

블로킹/논블로킹

작업을 수행할 때,

1. 자신의 작업을 하다가 다른 작업 주체가 하는 작업의 시작부터 끝까지 기다렸다가 다시 자신의 작업을 시작한다면 이는 블로킹

2. 다른 주체의 작업과 관계없이 자신의 작업을 계속한다면 이를 논블로킹

 

스레드 A가 어떤 작업을 하는 다른 대상을 호출하고, 그 대상이 가져온 결과물을 받아 다시 작업을 재개하고 있습니다. 예를 들자면 Java에서 JDBC를 사용하여 DB에 질의를 날리고 결과를 받아오는 작업을 블로킹 작업이라고 부를 수 있습니다. 이와 반대로 다른 주체에게 작업을 요청하고 그 결과를 받을 때까지 기다리지 않으며 자신의 작업을 한다면 이를 논블로킹이라고 할 수 있습니다.

 

siyoon210.tistory.com/147

 

동기 vs 비동기, 블로킹 vs 논블로킹 쉽게 이해하기

동기(sync) vs 비동기(async), 블로킹 vs 논블로킹 사전적 의미는 일단 치워두고 , 대조되는 개념들을 어떤 관점으로 봐야하는지 짧게 설명해보겠습니다. 동기 vs 비동기 : 처리해야 할 작업들을 어떠

siyoon210.tistory.com

 

deveric.tistory.com/99

 

동기/비동기와 블로킹/논블로킹

동기, 비동기 그리고 블로킹, 논블로킹은 프로그램을 개발할 때 중요한 개념 중 하나입니다. 기초 프로그래밍을 배우고 응용 파트인 병렬 프로그래밍을 익힐 때 나오는 개념이고 익히기 쉽지 않

deveric.tistory.com

blog.naver.com/n_cloudplatform/222189669084

 

결론부터 말하면 레벨 트리거와 엣지 트리거는 

 

Level-Triggered : 특정 조건이 유지되면 계속 감지

Edge-Triggered : 조건이 변할 때만 감지. ET

 

이다.

 

즉 엣지 트리거는 논블럭킹 방식이다.

 

엣지 트리거는 데이터 수신 시 딱 한번만 이벤트가 발생하기 때문에 이벤트가 발생했을 때 충분한 양의 버퍼를 마련한 다음에 모든 데이터를 다 읽어 들여야 한다. 즉, 데이터의 분량에 따라서 IO로 인한 DELAY가 생길 수 있다. 그래서 엣지 트리거에서는 넌-블로킹 IO를 이용한다. 입력 함수의 호출과 다른 작업을 병행할 수 있기 때문이다.

 

추가적으로 레벨 트리거 방식에서는 입력 버퍼에 데이터가 남아있는 동안에 게속해서 이벤트가 등록된다. 이는 비효율적이다. 하지만 엣지 트리거는 데이터가 수신되면 딱 한번 이벤트가 등록된다. 

 

논블로킹 모드로 만드는 이유는 엣지 트리거 방식의 특성상 블로킹 방식으로 동작하는 read & write 함수의 호출은 서버를 오랜 시간 멈추는 상황으로까지 이어지게 할 수 있기 때문이다.  

 

아래는 엣지 트리거 방식의 에코서버이다.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 4
#define EPOLL_SIZE 50
void setnonblockingmode(int fd);
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");

	epfd=epoll_create(EPOLL_SIZE);
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

	setnonblockingmode(serv_sock);
	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;
		}

		puts("return epoll_wait");
		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);
				setnonblockingmode(clnt_sock);
				event.events=EPOLLIN|EPOLLET;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
					while(1)
					{
						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);
							break;
						}
						else if(str_len<0)
						{
							if(errno==EAGAIN)
								break;
						}
						else
						{
							write(ep_events[i].data.fd, buf, str_len);    // echo!
						}
				}
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}

void setnonblockingmode(int fd)
{
	int flag=fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}
void error_handling(char *buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}

 

클라이언트 코드와 epoll에 대한 기본적인 설명은 아래 링크를 참고하자.

gusdnr69.tistory.com/220

 

(TCP/IP 소켓프로그래밍)select과 epoll의 차이점

TCP/IP Socket 기반 서버를 제작하는 방법은 크게 3가지가 있다. 1. 멀티 프로세싱 2. 멀티 쓰레싱 3. 멀티 플렉싱 1번과 2번은 클라이언트 마다 새로운 프로세스, 스레드를 생성하기 때문에 서버의 부

gusdnr69.tistory.com

 

반응형

'서버개발' 카테고리의 다른 글

(TCP/IP 소켓프로그래밍)select과 epoll의 차이점  (0) 2021.02.22
TCP프로토콜의 모든것  (2) 2021.02.08
HLS 란?  (0) 2021.02.05

+ Recent posts