리눅스 시스템 및 네트워크 프로그래밍/네트워크 프로그래밍

논블로킹 소켓과 멀티플렉싱: select, poll, epoll 비교

ROBL 2025. 2. 2.
728x90
반응형

네트워크 프로그래밍에서 다수의 클라이언트를 효율적으로 처리하는 것은 매우 중요한 과제입니다. 기본적인 블로킹(Blocking) 방식은 클라이언트의 요청을 처리하는 동안 다른 클라이언트의 요청을 받을 수 없다는 단점이 있습니다. 이를 해결하기 위해 논블로킹(Non-blocking) 소켓과 멀티플렉싱(Multiplexing) 기법이 사용됩니다.

이번 글에서는 논블로킹 소켓의 개념을 이해하고, 이를 활용한 멀티플렉싱 기법인 select(), poll(), epoll()의 차이점을 비교하며, 각 기법을 적용한 코드 예제를 소개하겠습니다.

1. 블로킹과 논블로킹 소켓이란?

1.1 블로킹 소켓(Blocking Socket)

기본적으로 소켓은 블로킹(Blocking) 방식으로 동작합니다. 즉, recv(), send(), accept() 같은 함수 호출이 완료될 때까지 현재 스레드는 대기 상태가 됩니다.

예를 들어, 서버가 accept()를 호출하면 클라이언트가 연결 요청을 보낼 때까지 해당 함수에서 멈춰 있습니다. 이 방식은 간단하지만, 다수의 클라이언트를 처리하기 어려운 단점이 있습니다.

1.2 논블로킹 소켓(Non-blocking Socket)

논블로킹 소켓에서는 recv()나 accept() 같은 함수가 즉시 반환됩니다. 데이터가 준비되지 않았을 경우 에러를 반환하므로, 프로그램이 특정 작업을 기다리지 않고 다른 작업을 수행할 수 있습니다.

논블로킹 모드는 fcntl()을 사용하여 설정할 수 있습니다.

#include <fcntl.h>
int flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);

하지만 논블로킹 모드만으로 다수의 소켓을 효율적으로 관리하기는 어렵기 때문에 멀티플렉싱 기법을 함께 사용합니다.

2. 멀티플렉싱이란?

멀티플렉싱은 하나의 스레드에서 여러 개의 소켓을 동시에 감시하고, 데이터가 준비된 소켓만 처리하는 기법입니다. 이를 통해 다수의 클라이언트를 효율적으로 관리할 수 있습니다.

대표적인 멀티플렉싱 방식에는 select(), poll(), epoll()이 있으며, 각각의 차이를 살펴보겠습니다.

3. select(), poll(), epoll() 비교

기법 호출 방식 소켓 개수 제한 성능 이벤트 감지 방식

select 매번 전체 소켓을 조회 1024 (리눅스 기본) 낮음 비효율적인 순차 검색
poll 매번 전체 소켓을 조회 무제한 낮음 선형 탐색
epoll 이벤트 기반 처리 무제한 높음 이벤트 발생 시에만 확인

3.1 select()

select()는 감시할 소켓 목록을 전달하면, 읽기/쓰기/예외 상태가 변경된 소켓을 알려줍니다. 하지만 매번 전체 소켓 리스트를 스캔해야 하므로 비효율적입니다.

select() 사용 예제

fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
FD_SET(client_fd, &read_fds);
select(max_fd + 1, &read_fds, NULL, NULL, NULL);

3.2 poll()

poll()은 select()와 유사하지만, 감시할 소켓 정보를 구조체 배열(pollfd)로 관리하여 소켓 개수 제한이 없습니다. 하지만 여전히 모든 소켓을 순차적으로 확인하는 방식이라 성능이 좋지 않습니다.

poll() 사용 예제

struct pollfd fds[2];
fds[0].fd = server_fd;
fds[0].events = POLLIN;
poll(fds, 2, -1);

3.3 epoll()

epoll()은 이벤트 기반으로 동작하여, 이벤트가 발생한 소켓만 확인하므로 성능이 우수합니다. 대규모 네트워크 서버에서 주로 사용됩니다.

epoll() 사용 예제

int epfd = epoll_create1(0);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = server_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev);

struct epoll_event events[10];
int nfds = epoll_wait(epfd, events, 10, -1);

4. 정리

  • select(): 간단하지만 성능이 낮고, 소켓 개수 제한이 있음
  • poll(): select보다 낫지만 여전히 비효율적
  • epoll(): 가장 효율적인 방식으로 대규모 네트워크 서버에서 사용됨

대부분의 최신 리눅스 서버에서는 epoll()을 기본적으로 사용하며, 성능이 중요한 애플리케이션에서도 선호됩니다.

이제 멀티클라이언트 처리를 할 때 select() 대신 epoll()을 적극 활용해보시길 바랍니다.

5. 실행 결과

서버를 실행한 후 여러 클라이언트가 접속하면, epoll()을 사용한 경우 빠르게 다중 연결을 처리할 수 있습니다.

서버 측 출력:

Client connected: 192.168.1.10
Client connected: 192.168.1.11
Received message from client: Hello, Server!

클라이언트 측 출력:

Connected to server!
Received: Welcome!

6. 마무리

이번 글에서는 논블로킹 소켓과 멀티플렉싱(select, poll, epoll)의 차이점을 설명하고, 각 기법의 특징을 비교하였습니다. 대규모 네트워크 서버를 구현할 때 epoll()을 적극 활용하면 성능을 개선할 수 있습니다.

728x90
반응형

댓글

💲 추천 글