Linux网络编程——多路复用之epoll

目录

  • Linux网络编程——多路复用之epoll

    • 基础API
    • 实例一、epoll实现在线聊天
    • 实例二、epoll实现在客户端断开后服务端能一直运行,客户端可以多次重连

Linux网络编程——多路复用之epoll

? epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

? 目前epell是linux大规模并发网络程序中的热门首选模型。

? epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

基础API

  1. 创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。

? #include <sys/epoll.h>

? int epoll_create(int size) size:监听数目

  1. 控制某个epoll监控的文件描述符上的事件:注册、修改、删除。

? #include <sys/epoll.h>

? int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

? epfd: 为epoll_creat的句柄

? op: 表示动作,用3个宏来表示:

? EPOLL_CTL_ADD (注册新的fd到epfd),

? EPOLL_CTL_MOD (修改已经注册的fd的监听事件),

? EPOLL_CTL_DEL (从epfd删除一个fd);

? event: 告诉内核需要监听的事件

? struct epoll_event {

? __uint32_t events; /* Epoll events */

? epoll_data_t data; /* User data variable */

? };

? typedef union epoll_data {

? void *ptr;

? int fd;

? uint32_t u32;

? uint64_t u64;

? } epoll_data_t;

? EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)

? EPOLLOUT: 表示对应的文件描述符可以写

? EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

? EPOLLERR: 表示对应的文件描述符发生错误

? EPOLLHUP: 表示对应的文件描述符被挂断;

? EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的

? EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

  1. 等待所监控文件描述符上有事件的产生,类似于select()调用。

? #include <sys/epoll.h>

? int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

? events: 用来存内核得到事件的集合,

? maxevents: 告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,

? timeout: 是超时时间

? -1: 阻塞

? 0: 立即返回,非阻塞

? >0: 指定毫秒

? 返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1

实例一、epoll实现在线聊天

tcp_server.c

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
    ERROR_CHECK(new_fd, -1, "accept");
    printf("client ip=%s, port=%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    char buf[128] = {0};
    int epfd = epoll_create(1);//参数size表示监听的数目大小
    ERROR_CHECK(epfd, -1, "epoll_create");
    struct epoll_event event, evs[2];
    event.events = EPOLLIN; //表示对应的文件描述符可读,监控读事件
    event.data.fd = STDIN_FILENO;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);//一次注册永久生效
    ERROR_CHECK(ret, -1, "epoll_ctl");
    event.data.fd = new_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &event);
    int i, readyFdNum;
    while(1){
        memset(evs, 0, sizeof(evs));
        readyFdNum = epoll_wait(epfd, evs, 2, -1);//大小为2,-1表示永久阻塞,返回值是需要处理的事件数目
        for(i = 0;i < readyFdNum;i++){
            if(evs[i].data.fd == new_fd){
                bzero(buf, sizeof(buf));
                ret = recv(new_fd, buf, sizeof(buf), 0);
                ERROR_CHECK(ret, -1, "recv");
                if(ret == 0){
                    printf("byebye!\n");
                    goto chatOver;
                }
                printf("%s\n", buf);
            }
            if(0 == evs[i].data.fd){
                memset(buf, 0, sizeof(buf));
                ret = read(STDIN_FILENO, buf, sizeof(buf));
                if(ret == 0){
                    printf("byebye!\n");
                    goto chatOver;
                }
                ret = send(new_fd, buf, strlen(buf) - 1, 0);
                ERROR_CHECK(ret, -1, "send");
            }
        }
    }
chatOver:
    close(new_fd);
    close(socketFd);
    return 0;
}

实例二、epoll实现在客户端断开后服务端能一直运行,客户端可以多次重连

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    char buf[128] = {0};
    int epfd = epoll_create(1);//参数size表示监听的数目大小
    ERROR_CHECK(epfd, -1, "epoll_create");
    struct epoll_event event, evs[3];
    event.events = EPOLLIN; //表示对应的文件描述符可读,监控读事件
    event.data.fd = STDIN_FILENO;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);//一次注册永久生效
    ERROR_CHECK(ret, -1, "epoll_ctl");
    event.data.fd = socketFd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, socketFd, &event);
    int i, readyFdNum;
    while(1){
        memset(evs, 0, sizeof(evs));
        readyFdNum = epoll_wait(epfd, evs, 3, -1);//大小为3,-1表示永久阻塞,返回值是需要处理的事件数目
        for(i = 0;i < readyFdNum;i++){
            if(evs[i].data.fd == socketFd){
                addrlen = sizeof(client);
                new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
                ERROR_CHECK(new_fd, -1, "accept");
                printf("client ip=%s, port=%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
                event.data.fd = new_fd;
                ret = epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &event);
                ERROR_CHECK(ret, -1, "epoll_ctl");
            }
            if(evs[i].data.fd == new_fd){  //new_fd中有数据
                memset(buf, 0, sizeof(buf));
                ret = recv(new_fd, buf, sizeof(buf), 0);
                ERROR_CHECK(ret, -1, "recv");
                if(ret == 0){
                    printf("byebye!\n");
                    event.events = EPOLLIN;
                    event.data.fd = new_fd;
                    ret = epoll_ctl(epfd, EPOLL_CTL_DEL, new_fd, &event);
                    //printf("line = %d\n", __LINE__);
                    ERROR_CHECK(ret, -1, "epoll_ctl");
                    continue;
                }
                printf("%s\n", buf);
            }
            if(0 == evs[i].data.fd){//标准输入
                memset(buf, 0, sizeof(buf));
                ret = read(STDIN_FILENO, buf, sizeof(buf));
                if(ret == 0){
                    printf("byebye\n");
                    goto chatOver;
                }
                ret = send(new_fd, buf, strlen(buf) - 1, 0);
                ERROR_CHECK(ret, -1, "send");
            }
        }
    }
chatOver:
    close(new_fd);
    close(socketFd);
    return 0;
}

原文地址:https://www.cnblogs.com/Mered1th/p/10803663.html

时间: 2024-08-05 03:16:00

Linux网络编程——多路复用之epoll的相关文章

Linux 网络编程八(epoll应用--大并发处理)

//头文件 pub.h #ifndef _vsucess #define _vsucess #ifdef __cplusplus extern "C" { #endif //服务器创建socket int server_socket(int port); //设置非阻塞 int setnonblock(int st); //接收客户端socket int server_accept(int st); //关闭socket int close_socket(int st); //接收消息

linux网络编程-----&gt;高并发---&gt;epoll多路I/O转接服务器

做网络服务的时候并发服务端程序的编写必不可少.前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定. 常见的linux并发服务器模型: 多进程并发服务器 多线程并发服务器 select多路I/O转接服务器 poll多路I/O转接服务器 epool多路I/O转接服务器. 本次主要讨论poll多路I/转接并发服务器模型: 前几章介绍完了多进程并发服务器,  多线程并发服务器, selete多路I/O转接服务器,  poll多路I/O转接服务器, 本章开始介

linux网络编程学习笔记之六 -----I/O多路复用服务端

多进程和多线程的目的是在于最大限度地利用CPU资源,当某个进程不需要占用太多CPU资源,而是需要I/O资源时,可以采用I/O多路复用,基本思路是让内核把进程挂起,直到有I/O事件发生时,再把控制返回给程序.这种事件驱动模型的高效之处在于,省去了进程和线程上下文切换的开销.整个程序运行在单一的进程上下文中,所有的逻辑流共享整个进程的地址空间.缺点是,编码复杂,而且随着每个逻辑流并发粒度的减小,编码复杂度会继续上升. I/O多路复用典型应用场合(摘自UNP6.1) select的模型就是这样一个实现

Linux网络编程 五种I/O 模式及select、epoll方法的理解

Linux网络编程 五种I/O 模式及select.epoll方法的理解 web优化必须了解的原理之I/o的五种模型和web的三种工作模式 五种I/O 模式--阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/OLinux网络编程 五种I/O 模式及select.epoll方法的理解

嵌入式 Linux网络编程(五)——epoll机制

嵌入式 Linux网络编程(五)--epoll机制 一.epoll简介 epoll是在2.6内核中提出的,是select和poll的增强版本.epoll更加灵活,没有描述符限制,使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中. 1.epoll函数 #include <sys/epoll.h> int epoll_create(int size); 创建一个epoll的句柄,size表示监听的文件描述的数量 int epoll_ctl(int epfd,

linux网络编程学习笔记之二 -----错误异常处理和各种碎碎(更新中)

errno 在unix系统中对大部分系统调用非正常返回时,通常返回值为-1,并设置全局变量errno(errno.h),如socket(), bind(), accept(), listen().erron存放一个正整数来保存上次出错的错误值. 对线程而言,每个线程都有专用的errno变量,不必考虑同步问题. strerror converts to English (Note: use strerror_r for thread safety) perror is simplified str

Linux网络编程入门 (转载)

http://www.cnblogs.com/RascallySnake/archive/2012/01/04/2312564.html (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端        在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一        个地方获取文件

Linux网络编程:客户端/服务器的简单实现

一. Socket的基本知识 1. socket功能 Socket层次 Socket实质上提供了进程通信的端点,进程通信之前,双方必须首先各自创建一个端点,否则是没有办法建立联系并相互通信的. 每一个Socket都一个半相关描述: {协议, 本地地址, 本地端口} 完整的Socket的描述: {协议, 本地地址, 本地端口, 远程地址, 远程端口} 2. Socket工作流程 面向连接(TCP)的Socket工作流程 UDP的socket工作流程 l 服务器端 首先,服务器应用程序用系统调用so

Linux网络编程入门

(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍 客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端 在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一 个地方获取文件的时候,是我们的ftp程序主动同外面进行通信(获取文件), 所以这个地方我们的ftp程序就是客户端程序. 服务端 和客户端相对应的程序即为服务端程序.被动的等待外面的程序来和自己通