리눅스 C/C++ 리액터 패턴 기반 네트워크 I/O 처리

서버 측 네트워크 I/O 관리를 위한 리액터 패턴의 핵심 절차는 다음과 같습니다: - 콜백 함수 정의 및 I/O 작업 핸들러 구현
typedef int(*HandlerFunc)(int fd);
int accept_handler(int fd);
int send_handler(int fd);
int recv_handler(int fd);
- 파일 디스크립터 관리 구조체 설계
struct connection {
    int fd;
    char recv_buf[BUF_SIZE];
    int recv_len;
    char send_buf[BUF_SIZE];
    int send_len;
    HandlerFunc send_handler;
    union {
        HandlerFunc accept_handler;
        HandlerFunc recv_handler;
    } read_action;
};
- 전역 연결 배열 `connections[MAX_CONNS]` 생성 및 epoll 인스턴스 초기화 - 서버 소켓 생성 후 연결 목록에 등록, read_action을 `accept_handler`로 설정 - epoll 인스턴스에 서버 소켓 등록(EPOLLIN 이벤트 감지) - `epoll_wait()`로 이벤트 대기, 발생한 이벤트 유형에 따라 처리: - EPOLLIN: 등록된 read_action 실행 (서버 소켓-accept_handler, 클라이언트-recv_handler) - EPOLLOUT: send_handler 실행 핸들러별 동작 로직: 1. accept_handler - 클라이언트 연결 수락 - 새 소켓을 연결 목록에 추가 - epoll 인스턴스에 EPOLLIN 이벤트 등록 2. recv_handler - 데이터 수신 후 recv_buf 저장 - 연결 종료 시 소켓 정리 - 데이터 처리 후 응답을 send_buf에 저장 - 이벤트를 EPOLLOUT으로 변경 3. send_handler - send_buf 데이터 전송 - 이벤트를 EPOLLIN으로 재설정 리액터 패턴 장점: - I/O 작업을 이벤트 기반 콜백으로 추상화 - 개별 I/O 연산의 독립성 및 결합도 감소 - 네트워크 I/O와 비즈니스 로직 분리 - 명시적 send/recv 호출 대신 이벤트 제어 구현 예제:
#include <sys/epoll.h>
#include <netinet/in.h>
#define BUF_SIZE 1024
#define MAX_CONNS 1048576

int epoll_fd;
struct connection connections[MAX_CONNS];

void setup_event(int fd, int events, int op) {
    struct epoll_event ev = {.events = events, .data.fd = fd};
    epoll_ctl(epoll_fd, op ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ev);
}

void register_conn(int fd, int event) {
    connections[fd].fd = fd;
    connections[fd].read_action.recv_handler = recv_handler;
    connections[fd].send_handler = send_handler;
    setup_event(fd, event, 1);
}

int accept_handler(int fd) {
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);
    int client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
    register_conn(client_fd, EPOLLIN);
    return 0;
}

int recv_handler(int fd) {
    int bytes = recv(fd, connections[fd].recv_buf, BUF_SIZE, 0);
    if(bytes <= 0) {
        close(fd);
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
        return bytes;
    }
    connections[fd].recv_len = bytes;
    
    // 에코 서버 구현
    connections[fd].send_len = connections[fd].recv_len;
    memcpy(connections[fd].send_buf, connections[fd].recv_buf, connections[fd].send_len);
    
    setup_event(fd, EPOLLOUT, 0);
    return bytes;
}

int send_handler(int fd) {
    int sent = send(fd, connections[fd].send_buf, connections[fd].send_len, 0);
    setup_event(fd, EPOLLIN, 0);
    return sent;
}

int create_server(short port) {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_addr.s_addr = htonl(INADDR_ANY),
        .sin_port = htons(port)
    };
    bind(sock, (struct sockaddr*)&addr, sizeof(addr));
    listen(sock, 10);
    return sock;
}

int main() {
    int server_fd = create_server(2000);
    epoll_fd = epoll_create(1);
    
    connections[server_fd].fd = server_fd;
    connections[server_fd].read_action.accept_handler = accept_handler;
    setup_event(server_fd, EPOLLIN, 1);
    
    struct epoll_event events[1024];
    while(1) {
        int n = epoll_wait(epoll_fd, events, 1024, -1);
        for(int i=0; i<n; i++) {
            int fd = events[i].data.fd;
            if(events[i].events & EPOLLIN) 
                connections[fd].read_action.accept_handler(fd);
            if(events[i].events & EPOLLOUT) 
                connections[fd].send_handler(fd);
        }
    }
}

태그: epoll 리액터패턴 C네트워크프로그래밍 I/O다중화 소켓프로그래밍

8월 4일 03:54에 게시됨