C语言socket编程-epoll


前言

epoll是Linux内核的可扩展I/O事件通知机制。于Linux 2.5.44首度登场,它设计目的旨在取代既有POSIX select与poll系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能。


一、简介

相比select/poll的主动查询,epoll模型采用基于事件的通知方式,事先为建立连接的句柄注册事件,一旦该句柄就绪,内核会采用回调机制将句柄加入到epoll的指定的句柄集合中,之后进程再根据该集合中句柄的数量,对客户端请求逐一进行处理。

虽然epoll机制中返回的同样是就绪句柄的数量,但epoll中的集合只存储了就绪的句柄,服务器进程无需再对所有的句柄进行扫描;且epoll机制使用内存映射机制(类似共享内存),不必再将内核中的句柄集合复制到内存空间;此外,epoll机制不受进程可打开最大句柄数量的限制(只与系统内存有关),可连接远超过默认FD_SETSIZE的进程。

二、相关系统调用

1、epoll_create

函数原型:int epoll_create(int size)
函数功能:用于创建一个epoll句柄
参数说明:

  • size:该epoll中可监听的文件描述符的最大个数
  • 返回值:返回一个用于引用epoll的句柄,调用失败后返回-1

2、epoll_ctl

函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数功能:注册监听事件,将fd注册到epfd中
参数说明:

  • epfd:epoll实例的句柄
  • op:操作类型,可以是EPOLL_CTL_ADD、EPOLL_CTL_MOD或EPOLL_CTL_DEL
  • fd:需要注册的句柄
  • event:指向epoll_event结构体的指针,用于描述事件类型和数据
  • 返回值:成功返回0,失败返回-1

epoll_event 是 Linux 内核中 epoll 事件处理机制的核心数据结构,声明如下:

struct epoll_event {
    uint32_t events;    // 表示事件类型
    epoll_data_t data;  // 用户数据,可以是一个指针或一个文件描述符
};

events表示的事件类型

  • EPOLLIN:表示对应的文件描述符可以读取(包括对端已经关闭连接)。
  • EPOLLOUT:表示对应的文件描述符可以写入。
  • EPOLLERR:表示对应的文件描述符发生错误。
  • EPOLLRDHUP:表示对端已经关闭连接,或者关闭了写端。
  • EPOLLHUP:表示对应的文件描述符被挂起。

3、epoll_wait

函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函数功能:等待监听的事件发生
参数说明:

  • epfd:epoll实例的句柄
  • events:存储IO事件信息的数组
  • maxevents:数组的最大长度
  • timeout:等待IO事件的超时时间
  • 返回值:发生IO事件的句柄数量,超时返回-1

三、代码示例

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>

#define ERR 1
#define OK 0
#define ADDR "127.0.0.1"
#define PORT 23
#define BACKLOG 1024
#define MAX_SIZE 1024
#define CLIENT_NUM 1024

int main()
{
    int sockFd, newSockFd, iRet, maxIndex, clientFds[CLIENT_NUM], efd, nReady, flag, j;
    unsigned int iLocalAddr;
    struct sockaddr_in localAddr, remoteAddr;
    socklen_t addrlen;
    struct epoll_event tep, eps[CLIENT_NUM];
    char buf[MAX_SIZE] = {0};

    // 创建socket
    sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockFd < 0) {
        printf("create socket failed, errno: %d\n", errno);
        return ERR;
    }

    // 配置地址信息
    localAddr.sin_family = AF_INET;
    localAddr.sin_port = htons(PORT);
    inet_pton(AF_INET, ADDR, &iLocalAddr);
    localAddr.sin_addr.s_addr = iLocalAddr;

    // 绑定地址和socket
    iRet = bind(sockFd, &localAddr, sizeof(localAddr));
    if (iRet < 0) {
        printf("bind socket failed, errno: %d\n", errno);
        return ERR;
    }

    // 监听socket的链接请求
    iRet = listen(sockFd, BACKLOG);
    if (iRet < 0) {
        printf("listen failed, errno: %d\n", errno);
        return ERR;
    }

    maxIndex = 0;

    // 初始化客户端链接
    for (int i = 0; i < CLIENT_NUM; i++) {
        clientFds[i] = -1;
    }

    // 创建epoll实例
    efd = epoll_create(CLIENT_NUM);
    if (efd < 0) {
        printf("create epoll fd failed, errno: %d\n", errno);
        return ERR;
    }

    tep.events = EPOLLIN;
    tep.data.fd = sockFd;

    // 注册socket句柄的监听事件
    iRet = epoll_ctl(efd, EPOLL_CTL_ADD, sockFd, &tep);
    if (iRet < 0) {
        printf("add default event failed, errno: %d\n", errno);
        return;
    }

    while (1) {
        // 等待有事件发生
        nReady = epoll_wait(efd, eps, CLIENT_NUM, -1);
        if (nReady == -1) {
            printf("epoll_wait failed, errno: %d\n", errno);
            continue;
        }

        for (int i = 0; i < nReady; i++) {
            if (!(eps[i].events & EPOLLIN)) {
                continue;
            }

            if (eps[i].data.fd == sockFd) {
                // socket句柄有事件发生,接收客户端链接,创建链接句柄
                newSockFd = accept(sockFd, &remoteAddr, &addrlen);
                if (newSockFd < 0) {
                    printf("accept failed, errno: %d\n", errno);
                    continue;
                }

                flag = -1;

                // 将新的连接句柄添加到集合中
                for (j = 0; j < CLIENT_NUM; j++) {
                    if (clientFds[i] == -1) {
                        clientFds[i] = newSockFd;
                        flag = j;
                        break;
                    }
                }

                if (flag == -1) {
                    char *fullMsg = "the client pool is full";
                    write(newSockFd, fullMsg, strlen(fullMsg));
                } else {
                    printf("client[%d] fd[%d] connect succ\n", j, clientFds[j]);
                }

                if (maxIndex < j) {
                    maxIndex = j;
                }

                // 注册链接句柄的监听事件,等待客户端发送消息
                tep.events = EPOLLIN;
                tep.data.fd = newSockFd;
                iRet = epoll_ctl(efd, EPOLL_CTL_ADD, newSockFd, &tep);
                if (iRet < 0) {
                    printf("add event failed, fd: %d, errno: %d\n", newSockFd, errno);
                    continue;
                }
            } else {
                // 收到客户端的消息
                int tmpFd = eps[i].data.fd;
                iRet = read(tmpFd, buf, MAX_SIZE);
                if (iRet < 0) {
                    printf("read failed, errno: %d\n", errno);
                } else if (iRet == 0) {
                    for (j = 0; j < MAX_SIZE; j++) {
                        if (clientFds[j] == tmpFd) {
                            clientFds[j] = -1;
                        }
                    }
                    printf("client[%d] fd[%d] disconnected\n", j, tmpFd);
                    close(tmpFd);
                } else {
                    printf("recv msg: %s\n", buf);
                    write(tmpFd, "this is server msg!", strlen("this is server msg!"));
                }
            }
        }
    }
}

总结

epoll是Linux操作系统提供的一种I/O多路复用机制,可以用于高效地管理大量的网络连接。它相比于传统的select和poll机制,具有更高的性能和更好的可扩展性。

epoll的核心是一个epoll文件描述符,通过epoll_ctl函数向其注册文件描述符,并指定需要监听的事件类型。当某个文件描述符上发生指定的事件时,epoll_wait函数会返回该文件描述符的信息,应用程序可以据此进行相应的处理。

需要注意的是,epoll并不是万能的,它适用于大量的连接和少量的数据交换,对于大量数据交换的场景,还需要使用其他的技术来提高性能。此外,epoll在使用时需要注意一些细节,如避免文件描述符泄漏、正确使用EPOLLONESHOT等。

总之,epoll是一个非常重要的网络编程工具,深入理解并正确使用它可以提高应用程序的性能和可靠性。

  • 43
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以回答关于C语言实现epoll IO复用的问题。以下是一个基本的epoll IO复用示例代码: ``` #include <sys/epoll.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #define MAX_EVENTS 10 #define PORT 8080 int main () { int server_fd, new_socket, epoll_fd, n, i; struct sockaddr_in address; struct epoll_event event, events[MAX_EVENTS]; // Create a socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("Failed to create socket"); exit(EXIT_FAILURE); } // Set socket options if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("Failed to set socket options"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // Bind the socket to a port if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("Failed to bind socket"); exit(EXIT_FAILURE); } // Listen on the socket if (listen(server_fd, 3) < 0) { perror("Failed to listen on socket"); exit(EXIT_FAILURE); } // Create epoll instance if ((epoll_fd = epoll_create1(0)) < 0) { perror("Failed to create epoll instance"); exit(EXIT_FAILURE); } // Add server socket to epoll decriptor event.events = EPOLLIN; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) { perror("Failed to add server socket to epoll"); exit(EXIT_FAILURE); } while (1) { n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (i = 0; i < n; i++) { if (events[i].data.fd == server_fd) { // Server socket event if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("Failed to accept incoming connection"); exit(EXIT_FAILURE); } // Add new socket to epoll descriptor event.events = EPOLLIN; event.data.fd = new_socket; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event) < 0) { perror("Failed to add new socket to epoll"); exit(EXIT_FAILURE); } } else { // Client socket event // Handle read events on the client sockets and process the // incoming data. Also handle disconnect events. } } } return 0; } ``` 希望能对您有所帮助!算了,这是一个关于蒟蒻程序员的笑话:为什么程序员总喜欢喝奶茶?因为他们喜欢借用框架!哈哈!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值