Taehwan Dev Blog

쿠버네티스, eBPF, 그리고 머신러닝 이야기

View My GitHub Profile

9 April 2026

I/O Multiplexing Threadpool로 구현한 Echo Server

by Yang Taehwan

프로젝트 소개

깃헙 레포지토리

개발스택

컴파일

gcc -O2 -Wall -Wextra -pthread echo_epoll_threadpool.c -o echo_server

## 실행

 ./echo_server

테스트

nc 127.0.0.1 8080

I/O Multiplexing Threadpool

구현 상세

struct conn {
    int fd;
    worker_t *owner;

    // pending write buffer
    char *wbuf;
    size_t wbuf_len;
    size_t wbuf_sent;
};

struct fd_node {
    int fd;
    fd_node_t *next;
};

struct worker {
    int id;
    int epfd;
    int notify_fd;
    pthread_t thread;

    pthread_mutex_t queue_lock;
    fd_node_t *queue_head;
    fd_node_t *queue_tail;

    conn_t *conns[MAX_FDS];
};
static int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return -1;
    return 0;
}
static int mod_epoll_events(worker_t *w, int fd, uint32_t events) {
    struct epoll_event ev;
    memset(&ev, 0, sizeof(ev));
    ev.events = events;
    ev.data.fd = fd;
    return epoll_ctl(w->epfd, EPOLL_CTL_MOD, fd, &ev);
}
static int add_epoll_fd(worker_t *w, int fd, uint32_t events) {
    struct epoll_event ev;
    memset(&ev, 0, sizeof(ev));
    ev.events = events;
    ev.data.fd = fd;
    return epoll_ctl(w->epfd, EPOLL_CTL_ADD, fd, &ev);
}
static int handle_payload(conn_t *c, const char *buf, size_t len) {
    if (c->wbuf_len == 0) {
        // ...
        while (sent < len) {
            ssize_t n = send(c->fd, buf + sent, len - sent, 0);
            if (n > 0) {
                sent += (size_t)n;
                continue;
            }
            // ...
        }
    // ...
}
static void handle_readable(worker_t *w, conn_t *c) {
    // ...
    for (;;) {
        ssize_t n = recv(c->fd, buf, sizeof(buf), 0);
        if (n > 0) {
            int rc = handle_payload(c, buf, (size_t)n);
            // ...
            // epoll 이벤트 재설정
            // EPOLLIN 읽기 가능 상태(readable) 감시
            // EPOLLET Edge Trigger
            // EPOLLRDHUP peer가 FIN 보냄을 감지(half-close)
            uint32_t events = EPOLLIN | EPOLLET | EPOLLRDHUP;
            // ...

            if (mod_epoll_events(w, c->fd, events) == -1) {
                // ...
            }
            continue;
        }
        if (n == 0) {
            // peer closed
            // ...
        }
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            // ET mode: drained all readable data
            break;
        }
        close_connection(w, c->fd);
        return;
    }
}
static void handle_writable(worker_t *w, conn_t *c) {
    // ...
    int rc = flush_write_buffer(c);
    // ...
    uint32_t events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    if (c->wbuf_len > 0) {
        events |= EPOLLOUT;
    }
    if (mod_epoll_events(w, c->fd, events) == -1) {
        // ...
    }
}
static void enqueue_fd(worker_t *w, int fd) {
    fd_node_t *node = (fd_node_t *)malloc(sizeof(fd_node_t));
    // ...
    node->fd = fd;
    node->next = NULL;
    // 링크드 리스트에 Insert 연산
    pthread_mutex_lock(&w->queue_lock);
    if (w->queue_tail) {
        w->queue_tail->next = node;
        w->queue_tail = node;
    } else {
        w->queue_head = w->queue_tail = node;
    }
    pthread_mutex_unlock(&w->queue_lock);
    // worker thread를 깨운다.
    uint64_t one = 1;
    if (write(w->notify_fd, &one, sizeof(one)) == -1) {
        // best effort
    }
}
static void drain_new_connections(worker_t *w) {
    for (;;) {
        // 링크드리스트에서 아직 epoll에 등록되지 않은 소켓을 하나 가져온다.
        fd_node_t *node = NULL;
        pthread_mutex_lock(&w->queue_lock);
        if (w->queue_head) {
            node = w->queue_head;
            w->queue_head = node->next;
            if (!w->queue_head) {
                w->queue_tail = NULL;
            }
        }
        pthread_mutex_unlock(&w->queue_lock);
        // ...
        // calloc은 malloc과 달리 메모리를 할당하면서 동시에 0으로 초기화한다.
        // conn 객체 생성
        conn_t *c = (conn_t *)calloc(1, sizeof(conn_t));
        // ...
        c->fd = fd;
        c->owner = w;
        w->conns[fd] = c;
        // epoll 등록
        if (add_epoll_fd(w, fd, EPOLLIN | EPOLLET | EPOLLRDHUP) == -1) {
            // ...
        }
        // ...
    }
}
static void *worker_main(void *arg) {
    // ...
    while (!stop_flag) {
        // 이벤트가 발생할 때까지 잠들어 있는다.
        int n = epoll_wait(w->epfd, events, MAX_EVENTS, 1000);
        // ...
        for (int i = 0; i < n; i++) {
            int fd = events[i].data.fd;
            uint32_t ev = events[i].events;
            // main thread가 새로운 커넥션이 있다고 알림
            if (fd == w->notify_fd) {
                // ...
                drain_new_connections(w);
                continue;
            }
            // ...
            // readable 이벤트 처리
            if (ev & EPOLLIN) {
                handle_readable(w, c);
                // ...
            }
            // writable 이벤트 처리
            if (ev & EPOLLOUT) {
                // ...
                handle_writable(w, c);
            }
        }
    }
    // ...
}
static void init_worker(worker_t *w, int id) {
    // ...
    // worker 전용 이벤트 감시 객체 생성
    w->epfd = epoll_create1(0);
    // ...
    // worker thread 생성
    if (pthread_create(&w->thread, NULL, worker_main, w) != 0) {
        // ...
    }
}
static int create_server_socket(uint16_t port) {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    // ...
    int opt = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        // ...
    }
    // ...
    if (set_nonblocking(listen_fd) == -1) {
        // ...
    }
    // ...
    if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        // ...
    }
    if (listen(listen_fd, BACKLOG) == -1) {
        // ...
    }
    // ...
}

static void accept_loop(int listen_fd) {
    while (!stop_flag) {
        // ...
        int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);
        // ...
        if (set_nonblocking(client_fd) == -1) {
            // ...
        }
        // ...
        // round robin 방식으로 할당
        worker_t *w = &workers[next_worker_idx];
        next_worker_idx = (next_worker_idx + 1) % worker_count;
        enqueue_fd(w, client_fd);
    }
}
int main(int argc, char **argv) {
    // ...
    // 코어 수를 가져온다.
    long cores = sysconf(_SC_NPROCESSORS_ONLN);
    // ...
    worker_count = (int)cores;
    // ...
    // worker 초기화
    for (int i = 0; i < worker_count; i++) {
        init_worker(&workers[i], i);
    }
    // 서버 소켓 생성
    int listen_fd = create_server_socket(port);
    // ...
    // accpet 루프 시작
    accept_loop(listen_fd);
    // ...
}
tags: