首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux epoll 高性能 I/O 多路復用完整指南與服務端/客戶端實戰代碼

     編輯:關於C語言

核心 API

頭文件:#include <sys/epoll.h>

  1. int epoll_create(int size):創建 epoll 實例,返回句柄。size 為建議監聽上限(內核 2.6.8+ 忽略此值)。
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event):註冊/修改/刪除監聽事件。
    • op:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL。
    • event.events:事件掩碼(見下)。
    • event.data.fd:關聯的文件描述符。
  3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout):等待事件發生,返回就緒事件數。timeout:-1 永久阻塞,0 立即返回,>0 毫秒超時。
  4. close(epfd):關閉 epoll 句柄。

epoll_event 結構與事件類型

typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
struct epoll_event { uint32_t events; epoll_data_t data; };

events 宏:

  • EPOLLIN:可讀(含對端正常關閉)。
  • EPOLLOUT:可寫。
  • EPOLLPRI:帶外數據可讀。
  • EPOLLERR:錯誤。
  • EPOLLHUP:掛起。
  • EPOLLET:邊緣觸發(ET),默認水平觸發(LT)。
  • EPOLLONESHOT:單次觸發,需重新註冊。

完整服務端代碼(ET 模式 + 非阻塞)

#include <sys/socket.h>, <sys/epoll.h>, <netinet/in.h>, <arpa/inet.h>,
       <fcntl.h>, <unistd.h>, <stdio.h>, <errno.h>, <string.h>

#define MAXLINE 5, OPEN_MAX 100, LISTENQ 20, SERV_PORT 5000, INFTIM 1000

void setnonblocking(int sock) {
    int opts = fcntl(sock, F_GETFL);
    if (opts < 0) { perror("fcntl GETFL"); return; }
    opts |= O_NONBLOCK;
    if (fcntl(sock, F_SETFL, opts) < 0) { perror("fcntl SETFL"); return; }
}
void CloseAndDisable(int sockid, struct epoll_event *ee) {
    close(sockid); ee->data.fd = -1;
}

int main() {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr = {0};
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(SERV_PORT);

    setnonblocking(listenfd);
    bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);

    int epfd = epoll_create(256);
    struct epoll_event ev, events[20];
    ev.data.fd = listenfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

    for (;;) {
        int nfds = epoll_wait(epfd, events, 20, -1);
        printf("\nepoll_wait returns %d\n", nfds);
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == listenfd) {  // 新連接
                struct sockaddr_in cliaddr; socklen_t clilen = sizeof(cliaddr);
                int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
                if (connfd < 0) { perror("accept"); continue; }
                printf("accept from %s\n", inet_ntoa(cliaddr.sin_addr));
                setnonblocking(connfd);
                ev.data.fd = connfd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }
            else if (events[i].events & EPOLLIN) {  // 讀事件
                int sockfd = events[i].data.fd;
                if (sockfd < 0) continue;
                char line[MAXLINE]; int count = 0, recvNum; bool bReadOk = false;
                while (1) {
                    recvNum = recv(sockfd, line + count, MAXLINE, 0);
                    if (recvNum < 0) {
                        if (errno == EAGAIN) { bReadOk = true; break; }  // 非阻塞讀完
                        else if (errno == ECONNRESET) { CloseAndDisable(sockfd, &events[i]); break; }
                        else if (errno == EINTR) continue;
                        else { CloseAndDisable(sockfd, &events[i]); break; }
                    }
                    else if (recvNum == 0) {  // 對端關閉
                        CloseAndDisable(sockfd, &events[i]); break;
                    }
                    count += recvNum;
                    if (recvNum == MAXLINE) continue;  // 可能還有數據
                    else { bReadOk = true; break; }
                }
                if (bReadOk) {
                    line[count] = '\0';
                    printf("read: %s", line);
                    ev.data.fd = sockfd;
                    ev.events = EPOLLOUT | EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
                }
            }
            else if (events[i].events & EPOLLOUT) {  // 寫事件
                int sockfd = events[i].data.fd;
                const char *str = "hello from epoll : long string may be cut\n";
                int len = strlen(str), count = 0, written; bool bWritten = false;
                while (1) {
                    written = send(sockfd, str + count, len - count, 0);
                    if (written == -1) {
                        if (errno == EAGAIN) { bWritten = true; break; }
                        else if (errno == ECONNRESET) { CloseAndDisable(sockfd, &events[i]); break; }
                        else if (errno == EINTR) continue;
                        else { CloseAndDisable(sockfd, &events[i]); break; }
                    }
                    else if (written == 0) { CloseAndDisable(sockfd, &events[i]); break; }
                    count += written;
                    if (count < len) continue;
                    else { bWritten = true; break; }
                }
                if (bWritten) {
                    ev.data.fd = sockfd;
                    ev.events = EPOLLIN | EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
                }
            }
        }
    }
    return 0;
}

客戶端代碼(多線程收發)

#include <sys/socket.h>, <netinet/in.h>, <arpa/inet.h>, <unistd.h>,
       <stdio.h>, <string.h>, <stdlib.h>, <pthread.h>

#define MAXLINE 100, SERV_PORT 5000

int sockfd; char recvline[MAXLINE+1]; pthread_t tid;

void *recv_thread(void *arg) {
    int n;
    while ((n = recv(sockfd, recvline, 30, 0)) > 0) {
        recvline[n] = '\0'; printf("%s", recvline);
    }
    return NULL;
}

int main(int argc, char **argv) {
    if (argc != 2) { printf("usage: %s \n", argv[0]); return 0; }
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { perror("inet_pton"); return 0; }
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    pthread_create(&tid, NULL, recv_thread, NULL);
    char input[100];
    while (fgets(input, 100, stdin) != EOF) write(sockfd, input, strlen(input));
    return 0;
}

關鍵點總結

  • ET 模式下必須將 socket 設置為非阻塞,並循環讀寫直到 EAGAIN。
  • recv 返回 0 表示對端發送 FIN(正常關閉),ECONNRESET 表示收到 RST(異常斷開)。
  • 發送端同理處理 EAGAIN 視為發送緩衝區滿,待下次 EPOLLOUT 繼續。
  • 事件切換使用 EPOLL_CTL_MOD 在讀/寫間轉換。

參考資料

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved