Backend Development 34 min read

Understanding Netty's Asynchronous Model, Linux I/O Multiplexing (select, poll, epoll) and JNI Integration

This article explains Netty's high‑performance asynchronous architecture, compares classic multithread, Reactor, select, poll and epoll I/O multiplexing models, describes level‑triggered versus edge‑triggered event handling, and provides a step‑by‑step JNI example and a hand‑written epoll server implementation in C.

Top Architect
Top Architect
Top Architect
Understanding Netty's Asynchronous Model, Linux I/O Multiplexing (select, poll, epoll) and JNI Integration

Netty is a widely used asynchronous network framework that relies on the Reactor pattern and the Linux epoll mechanism to achieve high concurrency and low latency.

Classic multithread model – each client connection creates a dedicated thread, which quickly exhausts resources under heavy load.

Reactor model – a small set of threads (boss and workers) dispatches I/O events to handlers, improving scalability.

IO multiplexing models

1. select uses a fixed‑size bitmap (max 1024 descriptors) and requires copying fd sets between user and kernel space, leading to O(N) scanning overhead.

2. poll replaces the bitmap with an array of struct pollfd , removing the 1024 limit but still incurs O(N) traversal and frequent user‑kernel switches.

3. epoll (Linux 2.6+) stores monitored file descriptors in a kernel‑side data structure (red‑black tree) and returns only ready descriptors via a linked list, eliminating the O(N) scan and reducing context switches.

epoll supports two notification modes:

Level‑triggered (LT) – the kernel repeatedly notifies as long as data is available; simple but generates many wake‑ups under high load.

Edge‑triggered (ET) – the kernel notifies only when the state changes (e.g., buffer transitions from empty to non‑empty); requires the application to drain the socket completely but offers superior performance for millions of connections.

JNI example

/**
 * @author shichaoyang
 * @Description: Data synchronizer
 */
public class DataSynchronizer {
    static {
        System.loadLibrary("synchronizer");
    }
    private native String syncData(String status);
    public static void main(String... args) {
        String rst = new DataSynchronizer().syncData("ProcessStep2");
        System.out.println("The execute result from C is : " + rst);
    }
}

The corresponding C implementation:

JNIEXPORT jstring JNICALL Java_DataSynchronizer_syncData(JNIEnv *env, jobject obj, jstring str) {
    const char *inCStr = (*env)->GetStringUTFChars(env, str, NULL);
    if (NULL == inCStr) return NULL;
    printf("In C, the received string is: %s\n", inCStr);
    (*env)->ReleaseStringUTFChars(env, str, inCStr);
    char outCStr[128];
    printf("Enter a String: ");
    scanf("%s", outCStr);
    return (*env)->NewStringUTF(env, outCStr);
}

Hand‑written epoll server (C)

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
#define ENABLE_ET 0

int SetNonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void AddFd(int epoll_fd, int fd, bool enable_et) {
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if (enable_et) event.events |= EPOLLET;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
    SetNonblocking(fd);
}

void lt_process(struct epoll_event* events, int number, int epoll_fd, int listen_fd) {
    char buf[BUFFER_SIZE];
    for (int i = 0; i < number; ++i) {
        int sockfd = events[i].data.fd;
        if (sockfd == listen_fd) {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof(client_address);
            int connfd = accept(listen_fd, (struct sockaddr*)&client_address, &client_addrlength);
            AddFd(epoll_fd, connfd, false);
        } else if (events[i].events & EPOLLIN) {
            memset(buf, 0, BUFFER_SIZE);
            int ret = recv(sockfd, buf, BUFFER_SIZE - 1, 0);
            if (ret <= 0) { close(sockfd); continue; }
            printf("get %d bytes of content: %s\n", ret, buf);
        } else {
            printf("something unexpected happened!\n");
        }
    }
}

int main(int argc, char* argv[]) {
    const char* ip = "10.0.76.135";
    int port = 9999;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    int listen_fd = socket(PF_INET, SOCK_STREAM, 0);
    bind(listen_fd, (struct sockaddr*)&address, sizeof(address));
    listen(listen_fd, 5);
    int epoll_fd = epoll_create(5);
    AddFd(epoll_fd, listen_fd, true);
    struct epoll_event events[MAX_EVENT_NUMBER];
    while (1) {
        int ret = epoll_wait(epoll_fd, events, MAX_EVENT_NUMBER, -1);
        if (ret < 0) { printf("epoll failure!\n"); break; }
        lt_process(events, ret, epoll_fd, listen_fd);
    }
    close(listen_fd);
    return 0;
}

The article also discusses performance tuning for achieving million‑connection concurrency, the importance of choosing ET mode for high load, and clarifies common misconceptions such as the belief that epoll relies on mmap for user‑kernel sharing.

backendJavaNettyepollJNIIO Multiplexing
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.