nullllun

1. 简介

epoll 是 Linux 平台下特有的一种 I/O 复用模型实现,于 2002 年在 Linux kernel 2.5.44 中被引入。在 epoll 之前,Unix/Linux 平台下的 I/O 复用模型包含 select 和 poll 两个系统调用。随着因特网的发展,因特网的用户量越来越大,C10K 问题出现。基于 select 和 poll 编写的网络服务已经不能满足不能满足用户的需求了,业界迫切希望更高效的系统调用出现。在此背景下,FreeBSD 的 kqueue 和 Linux 的 epoll 被研发了出来。kqueue 和 epoll 的出现,终结了 C10K 问题,C10K 问题就此作古。

因为 Linux 系统的广泛应用,所以大家在说 I/O 复用时,更多的是想到了 epoll,而不是 kqueue,本文也不例外。本篇文章不会涉及 kqueue,大家有兴趣可以自己看看。

2. 基于 epoll 实现 web 服务器

在 Linux 中,epoll 并不是一个系统调用,而是 epoll_create、epoll_ctl 和 epoll_wait 三个系统调用的统称。关于这三个系统调用的细节,这里就不说明了,大家可以自己去查 man-page。接下来,我们来直接看一个例子,这个例子基于 epoll 和 TinyHttpd 实现了一个 I/O 复用版的 HTTP Server。在上代码前,我们先来演示这个玩具版 HTTP Server 的效果。

上面就是玩具版 HTTP Server 的运行效果了,看起来还行。在我第一次把它成功跑起来的时候,感觉很奇妙。好了,看完效果,接下来看代码吧,如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/sysinfo.h>
#include <sys/epoll.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "httpd.h"

#define DEFAULT_PORT 8080
#define MAX_EVENT_NUM 1024
#define INFTIM -1

void process(int);

void handle_subprocess_exit(int);

int main(int argc, char *argv[])  
{
    struct sockaddr_in server_addr;
    int listen_fd;
    int cpu_core_num;
    int on = 1;
    
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(listen_fd, F_SETFL, O_NONBLOCK);    // 设置 listen_fd 为非阻塞
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(DEFAULT_PORT);

    if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind error, message: ");
        exit(1);
    }

    if (listen(listen_fd, 5) == -1) {
        perror("listen error, message: ");
        exit(1);
    }

    printf("listening 8080\n");

    signal(SIGCHLD, handle_subprocess_exit);

    cpu_core_num = get_nprocs();
    printf("cpu core num: %d\n", cpu_core_num);
    // 根据 CPU 数量创建子进程,为了演示“惊群现象”,这里多创建一些子进程
    for (int i = 0; i < cpu_core_num * 2; i++) {
        pid_t pid = fork();
        if (pid == 0) {    // 子进程执行此条件分支
            process(listen_fd);
            exit(0);
        }
    }

    while (1) {
        sleep(1);
    }

    return 0;
}

void process(int listen_fd) 
{
    int conn_fd;
    int ready_fd_num;
    struct sockaddr_in client_addr;
    int client_addr_size = sizeof(client_addr);
    char buf[128];

    struct epoll_event ev, events[MAX_EVENT_NUM];
    // 创建 epoll 实例,并返回 epoll 文件描述符
    int epoll_fd = epoll_create(MAX_EVENT_NUM);
    ev.data.fd = listen_fd;
    ev.events = EPOLLIN;

    // 将 listen_fd 注册到刚刚创建的 epoll 中
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) {
        perror("epoll_ctl error, message: ");
        exit(1);
    }

    while(1) {
        // 等待事件发生
        ready_fd_num = epoll_wait(epoll_fd, events, MAX_EVENT_NUM, INFTIM);
        printf("[pid %d] 

相关文章: