Chapter 17 - select보다 나은 epoll
Chapter 17 - select보다 나은 epoll
17-1 : epoll의 이해와 활용
select()
기반 멀티플렉싱은 오래 전에 개발되어 최적화한다 해도 성능이 그리 좋지 못함
select()
함수 호출이후 항상 모든 파일 디스크립터를 대상으로 반복문이 실행됨select()
함수를 호출할 때마다 관찰대상에 대한 정보들을 인자로 매번 전달해야하며, 이는 운영체제에 전달되기 때문에 부담이 따름
따라서 웹 기반 서버개발이 주를 이루는 오늘날 개발환경에서는 대안으로 epoll
을 활용(리눅스에서, 윈도우에서는 IOCP
)
epoll
은 운영체제에게 관찰대상에 대한 정보를 한번만 알려주고, 변경 사항이 있을때만 알려줌
단, epoll
이나 IOCP
과 같은 개선된 IO 멀티플렉싱 모델은 운영체제별로 호환되지 않기 때문에 대부분의 운영체제에서 지원되는 select()
역시 서버의 접속자 수가 많지 않은 경우에는 쓰임새가 있음
epoll
의 장점
- 전체 파일 디스크립터들의 상태변화를 확인하기 위한 반복문이 필요하지 않음
select()
함수에 대응하는epoll_wait()
함수 호출시 관찰대상의 정보를 매번 전달할 필요가 없음
epoll
방식에서는 관찰대상인 파일 디스크립터의 저장을 운영체제가 담당
이 때 파일 디스크립터의 저장을 위한 저장소의 생성에 epoll_creat()
함수를 사용
#include <sys/epoll.h>
int epoll_create(int size);
// 성공시 epoll 파일 디스크립터, 실패시 -1 반환
epoll_create()
함수 호출로 생성되는 파일 디스크립터의 저장소를 ‘epoll 인스턴스’라고 함
매개변수로는 epoll 인스턴스의 크기를 결정하는 정보로 사용되나, 정확히 해당 크기로 생성되는 것이 아닌 참고로만 사용됨(리눅스 커널 2.6.8 이후부터는 완전히 무시)
반환값인 파일 디스크립터는 epoll 인스턴스를 구분하는 목적으로 사용되며 소멸시에는 close()
함수의 호출이 필요
생성된 epoll 인스턴스에 관찰대상이 되는 파일 디스크립터를 등록하는데는 epoll_ctl()
함수를 사용
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 성공시 0, 실패시 -1 반환
struct epoll_event
{
__uint32_t events;
epoll_data_t data;
}
typedef union epoll_data
{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
두번째 인자에는 관찰대상을 어떻게 조작할 것인지를 지정
EPOLL_CTL_ADD
: 파일 디스크립터를 epoll 인스턴스에 등록EPOLL_CTL_DEL
: 파일 디스크립터를 epoll 인스턴스에서 삭제EPOLL_CTL_MOD
: 등록된 파일 디스크립터의 이벤트 발생상황을 변경
네번째 인자는 epoll_event
구조체의 포인터로 이벤트가 발생한 파일 디스크립터를 묶는 용도로도 사용되고, 이벤트의 유형을 등록하는 용도로도 사용됨
이벤트 유형을 나타내는 상수들은 비트 OR 연산자로 다수 등록 가능
EPOLLIN
: 수신할 데이터가 존재하는 상황EPOLLOUT
: 출력버퍼가 비워져서 당장 데이터를 전송할 수 있는 상황EPOLLPRI
: OOB 데이터가 수신된 상황EPOLLRDHUP
: 연결이 종료되거나 Half-clsoe가 진행된 상황, 엣지 트리거 방식에서 유용하게 사용EPOLLERR
: 에러가 발생한 상황EPOLLET
: 이벤트의 감지를 엣지 트리거 방식으로 동작EPOLLONESHOT
: 이벤트가 한번 감지되면 해당 파일 디스크립터는 더이상 이벤트를 발생시키지 않음 - 추후EPOLL_CTL_MOD
를 전달하여 이벤트를 재설정해야함
epoll_ctl()
함수 동작 예시
struct epoll_event event;
...
event.events = EPOLLIN;
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
epoll
에서 epoll_wait()
함수는 select()
함수에 해당되며, 가장 마지막에 호출됨
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// 성공시 이벤트가 발생한 파일 디스크립터의 수, 실패시 -1 반환
epoll_wait()
함수 동작 예시
int event_cnt;
struct epoll_event *ep_events;
...
ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
...
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
...
epoll
기반의 에코 서버 예시
// echo_epollserv.cpp
// 클라이언트는 이전의 ehoc_client.cpp를 사용
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
using namespace std;
#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;
char buf[BUF_SIZE];
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
if(argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
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, (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);
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)
{
error_handling("epoll_wait() error");
break;
}
for (int 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_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
cout << "connected client : " << clnt_sock << endl;
}
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);
cout << "closed client : " << ep_events[i].data.fd << endl;
}
else
write(ep_events[i].data.fd, buf, str_len); // echo
}
}
}
close(serv_sock);
close(epfd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
17-2 : 레벨 트리거(Level Trigger)와 엣지 트리거(Edge Trigger)
레벨 트리거 : 입력버퍼에 데이터가 남아있는 동안 계속해서 이벤트가 등록됨
엣지 트리거 : 입력버퍼로 데이터가 수신된 상황에 단 한번만 이벤트가 등록됨
레벨 트리거의 이벤트 등록방식 확인 예제
// echo_EPLTserv.cpp
// 클라이언트는 이전의 ehoc_client.cpp를 사용
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
using namespace std;
#define BUF_SIZE 4
#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;
char buf[BUF_SIZE];
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
if(argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
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, (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);
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)
{
error_handling("epoll_wait() error");
break;
}
cout << "return epoll_wait" << endl;
for (int 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_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
cout << "connected client : " << clnt_sock << endl;
}
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);
cout << "closed client : " << ep_events[i].data.fd << endl;
}
else
write(ep_events[i].data.fd, buf, str_len); // echo
}
}
}
close(serv_sock);
close(epfd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
echo_epollserv.cpp
에서 버퍼의 크기를 축소하고, while
문 내에 epoll_wait()
함수의 호출횟수를 확인하기 위한 문장이 삽입됨
엣지 트리거의 이벤트 등록방식 확인 예제
// echo_EPLTserv.cpp 에서 아래의 일부분만 변경
...
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 | EPOLLET;
...
}
...
이벤트는 한번만 등록됨을 확인할 수 있으나, 클라이언트의 실행결과에 문제 발생
errno.h
헤더파일에 있는 errno
전역변수를 통해 오류발생시의 정보를 확인할 수 있음
read()
함수의 경우 입력버퍼가 비어있을 때 -1을 반환하면서 errno
에 ENGAIN
상수를 저장
소켓을 논블로킹 모드로 변환하는데는 fcntl()
함수를 사용
- 두 번째 인자로
F_GETFL
을 사용하여 인자로 전달된 파일 디스크립터에 설정되어있는 특성정보를 획득 F_SETFL
을 사용하여 특성정보를 변경fcntl(fd, F_SETFL, flag | O_NONBLOCK);
형태로 사용
엣지 트리거 방식은 데이터 수신시 단 한번 이벤트가 등록되기 때문에 입력 이벤트 발생시 대부분 입력버퍼에 저장된 데이터 전부를 읽어들어야함
따라서 read()
함수의 반환값 및 errno
를 확인하여 입력버퍼가 비어있는지를 확인
이 경우 입출력 함수 호출시 서버를 오랜 시간 멈추는 상황을 만들지 않기 위해 반드시 논블로킹 소켓을 기반으로 해야함
레벨 트리거의 이벤트 등록방식 확인 예제
// echo_EPETserv.cpp
// 클라이언트는 이전의 ehoc_client.cpp를 사용
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
using namespace std;
#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;
char buf[BUF_SIZE];
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
if(argc != 2)
{
cout << "Usage : " << argv[0] << " <port>\n";
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, (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)
{
error_handling("epoll_wait() error");
break;
}
cout << "return epoll_wait" << endl;
for (int 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);
cout << "connected client : " << clnt_sock << endl;
}
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);
cout << "closed client : " << ep_events[i].data.fd << endl;
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 error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
void setnonblockingmode(int fd)
{
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}
엣지 트리거는 데이터의 수신과 데이터가 처리되는 시점을 분리할 수 있다는 장점이 있음
내용 확인문제
-
select
함수를 기반으로 서버를 구현할 때 코드상에서 확인할 수 있는 단점 두가지는 무엇인가?select()
함수의 호출 이후 모든 파일 디스크립터를 대상으로 하는 반복문이 실행됨
select()
함수를 호출할 때마다 매번 관찰대상에 대한 정보들을 인자로 전달해야함 -
select
방식이나epoll
방식이나, 관찰의 대상이 되는 파일 디스크립터의 정보를 함수호출을 통해서 운영체제에 전달해야한다. 그렇다면 이들 정보를 운영체제에게 전달하는 이유는 어디에 있는가?관찰이 대상이 되는 파일 디스크립터들이 소켓을 가리키고, 소켓은 운영체제에서 관리가 되기 때문
-
select
방식과epoll
방식의 가장 큰 차이점은 관찰의 대상이 되는 파일 디스크립터를 운영체제에게 전달하는 방식에 있다. 어떻게 차이가 나는지, 그리고 그러한 차이를 보이는 이유는 무엇인지 설명해보자.select
는select()
함수 호출시마다 파일 디스크립터의 정보를 전달해야하지만,epoll
은 운영체제에서 직접 파일 디스크립터를 관리하기때문에 해당 저장소에 등록시 한번만 전달하면 됨 -
select
방식을 개선시킨 것이epoll
방식이긴 하지만select
방식도 나름의 장점이 있다. 어떠한 상황에서select
방식을 선택하는 것이 보다 현명한 선택이 될 수 있는가?서버의 접속자 수가 많지 않고, 여러 운영체제에서 운용해야 하는 경우
-
epoll
은 레벨 트리거 방식, 또는 엣지 트리거 방식으로 동작한다. 그렇다면 이 둘이 어떻게 차이가 나는지 이벤트의 발생시점을 입력버퍼 기준으로 설명해보자.레벨 트리거는 입력버퍼가 남아있을 경우 계속해서 이벤트 발생
엣지 트리거는 입력버퍼에 데이터가 수신된 상황에 한번만 이벤트 발생 -
엣지 트리거 방식을 사용하면 데이터의 수신과 데이터의 처리시점을 분리할 수 있다고 하였다. 그 이유는 무엇이고, 이는 어떠한 장점이 있는가?
엣지 트리거 방식을 사용할시 데이터 수신시에만 이벤트가 발생하기 때문에 데이터가 수신된 이후 원하는 시점에 데이터를 처리할 수 있음
데이터의 수신과 처리 시점 분리시 서버 구현에 엄청난 유연성이 생김 -
서버에 접속한 모든 클라이언트들 사이에서 메시지를 주고받는 형태의 채팅 서버를 레벨 트리거 방식의
epoll
기반으로, 엣지 트리거 방식의epoll
기반으로 각각 구현해보자(참고로 채팅 서버는 레벨 트리거로 구현하건, 엣지 트리거로 구현하건 크게 차이를 보이지 않는다). 물론 서버의 실행을 위해서는 채팅 클라이언트가 필요한데 이는 Chapter 18에서 소개하는 예제chat_clnt.cpp
를 그대로 활용하기로 하자(컴파일 방법은 Chapter 18에서 별도로 참조해야 한다). 비록 Chapter 18을 공부한 상태는 아니나, 단순히 예제를 활용하는 정도이니 어려움은 없을 것이다. 만약에 이것이 부담스럽다면 Chapter 18을 먼저 공부한 다음에 이 문제를 해결해도 된다.// conv_EPLTserv.cpp // 클라이언트는 ch18의 chat_clnt.cpp를 사용 #include <iostream> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h> using namespace std; #define BUF_SIZE 100 #define MAX_CLNT 256 #define EPOLL_SIZE 50 void error_handling(char *buf); void setnonblockingmode(int fd); int clnt_cnt = 0; int clnt_socks[MAX_CLNT]; 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; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if(argc != 2) { cout << "Usage : " << argv[0] << " <port>\n"; 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, (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) { error_handling("epoll_wait() error"); break; } for (int 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); clnt_socks[clnt_cnt++] = clnt_sock; event.events = EPOLLIN; event.data.fd = clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); cout << "connected client : " << clnt_sock << endl; } else { str_len = read(ep_events[i].data.fd, buf, BUF_SIZE); if (str_len == 0) { epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); close(ep_events[i].data.fd); for (int i = 0; i < clnt_cnt; i++) { if (ep_events[i].data.fd == clnt_socks[i]) { while (i++ < clnt_cnt - 1) clnt_socks[i] = clnt_socks[i + 1]; break; } } clnt_cnt--; cout << "closed client : " << ep_events[i].data.fd << endl; break; } else if (str_len < 0) { if (errno == EAGAIN) break; } else { for (int i = 0; i < clnt_cnt; i++) write(clnt_socks[i], buf, str_len); } } } } close(serv_sock); close(epfd); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } void setnonblockingmode(int fd) { int flag = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag | O_NONBLOCK); }
// conv_EPETserv.cpp // 클라이언트는 ch18의 chat_clnt.cpp를 사용 #include <iostream> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h> using namespace std; #define BUF_SIZE 100 #define MAX_CLNT 256 #define EPOLL_SIZE 50 void error_handling(char *buf); void setnonblockingmode(int fd); int clnt_cnt = 0; int clnt_socks[MAX_CLNT]; 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; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if(argc != 2) { cout << "Usage : " << argv[0] << " <port>\n"; 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, (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) { error_handling("epoll_wait() error"); break; } for (int 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); clnt_socks[clnt_cnt++] = clnt_sock; setnonblockingmode(clnt_sock); event.events = EPOLLIN | EPOLLET; event.data.fd = clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); cout << "connected client : " << clnt_sock << endl; } else { while (1) { str_len = read(ep_events[i].data.fd, buf, BUF_SIZE); if (str_len == 0) { epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); close(ep_events[i].data.fd); for (int i = 0; i < clnt_cnt; i++) { if (ep_events[i].data.fd == clnt_socks[i]) { while (i++ < clnt_cnt - 1) clnt_socks[i] = clnt_socks[i + 1]; break; } } clnt_cnt--; cout << "closed client : " << ep_events[i].data.fd << endl; break; } else if (str_len < 0) { if (errno == EAGAIN) break; } else { for (int i = 0; i < clnt_cnt; i++) write(clnt_socks[i], buf, str_len); } } } } } close(serv_sock); close(epfd); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } void setnonblockingmode(int fd) { int flag = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag | O_NONBLOCK); }