EPOLL服务器流程

EPOLL服务器流程

使用cat命令去查看一个进程可以打开的socket描述符上限

cat /proc/sys/fs/file-max

9223372036854775807
突破文件描述符1024限制

修改:

- 打开sudo vi /etc/security/limits.cof          -->设置默认值,可以直接借助命令

- 在文件尾部写入 

* soft nofile 10000


* hard  nofile 20000

记住要用tab键去对齐。

epoll系列系统调用

epoll是linux系统独有的IO复用函数,它与select和poll有很大的区别

  1. epoll用一组函数完成任务 epoll_create, epoll_ctl, epoll_wait
  2. epoll将用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复闯入文件描述符集和事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
#include<sys/epoll.h>
int epoll_create(int size)

args:

size: 创建的红黑树的监听节点数量(仅供内核参考,内核会进行扩容)

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

epfd: epoll_create函数的返回值,即要操作的文件描述符

op: 对监听红黑树所做的操作
取值有:

  • EPOLL_CTL_ADD 向事件表中注册fd上的事件
  • EPOLL_CTL_MOD 修改fd上的注册事件
  • EPOLL_CTL_DEL 删除fd上的注册事件

fd 待监听的fd

event:

sturct epoll_event{
  uint32_t events;        
  epoll_data_t data; 
}

typedef union epoll_data{
  void* ptr;       //泛型指针
  int fd;         //对应的监听事件的fd
  uint32_t u32;
  unit64_t u64;
}epoll_data_t;

events能够取的值有:
EPOLLIN, EPOLLOUT, EPOLLERR

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

epfd: epoll_create函数的返回值。

events: 他和ctl的epoll_event结构体不一样,它是一个数组(传出参数),传出满足监听事件的文件描述符

maxevents:数组元素的总个数

timeout:-1 阻塞, 0非阻塞,>0超时时间。

返回值:
>0:满足监听的总个数,可以用作循环上限。
0:

使用epoll来制作服务器

常规步骤

  • socket创建套接字listenfd
  • 使用setsockopt实现端口复用
  • 定义服务器地址结构体serv_addr
  • Bind将服务器地址体和套接字绑定在一起
  • 使用Listen开始监听
  • 使用epoll_create创建epll模型
  • 创建变量epoll_event变量ted,输入对应的监听事件EPOLL_CTL_ADD, 和套接字lfd
  • 使用epoll_ctl将对应套接字加入到红黑树结构中

业务流程

  • 打开while循环
  • 使用epoll_wait打开efd的红黑树结构,将监听到的数据放入数组ep(epoll_event类型)
  • 使用for循环遍历目前监听到的事件和套接字
  • 如果ep[i].data.fd == listenfd,那么调用accept创建connfd套接字
  • 创建之后,用tep和epoll_ctl将connfd放进红黑树结构
  • 如果ep[i].data.fd != listenfd,那么说明这是个connfd
  • 定义一个sockfd来作为connfd1, connfd2…
  • 对sockfd这个套接字进行Read读取,如果是0,说明客户端关闭连接,从红黑树结构中删除这个结点即EPOLL_CTL_DEL,并且close(sockfd)
  • 如果小于0,对其error进行判断操作
  • 如果大于0,那么将buf(收到的数据)进行处理后wirte回去给客户端。
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<ctype.h>
#include<pthread.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<poll.h>
#include<sys/epoll.h>
#include"wrap.h"

#define MAX_LINE 80
#define SERV_PORT 8888
#define OPEN_MAX 5000 
int main(int argc, char* argv[]){
    int listenfd, connfd, sockfd, efd;
    ssize_t nready, res;
    struct sockaddr_in serv_addr, cli_addr;
    socklen_t clientaddrLength;
    char buf[MAX_LINE], str[INET_ADDRSTRLEN];
    struct epoll_event tep, ep[OPEN_MAX];   //tep:用于传入epoll_ctl的, ep[]是epoll_wait参数。


    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    Listen(listenfd, 128);
    
    efd = epoll_create(OPEN_MAX);       //创建epoll模型,efd指向红黑树根结点
    if(efd == -1){
        perr_exit("epoll create error");
    }

    tep.events = EPOLLIN;tep.data.fd = listenfd;
    res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
    if(res == -1){
        perr_exit("epoll ctl error");
    }

    int num, n;

    while(true){
        nready = epoll_wait(efd, ep, OPEN_MAX, -1);
        if(nready == -1){
            perr_exit("epoll wait error");
        }

        for(int i = 0;i < nready;i++){
            if(!ep[i].events & EPOLLIN){        //if not a read event, continue loop
                continue;
            }

            if(ep[i].data.fd == listenfd){      //if fd is listenfd
                clientaddrLength = sizeof(cli_addr);
                connfd = Accept(listenfd, (struct sockaddr *)&cli_addr, &clientaddrLength);
                
                printf("received form %s at port %d\n", inet_ntop(AF_INET, &cli_addr.sin_addr, str, sizeof(str)), ntohs(cli_addr.sin_port));
                printf("cfd %d ---- client %d\n", connfd, ++num);
                tep.events = EPOLLIN; 
                tep.data.fd = connfd;
                res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
                if(res == -1){
                    perr_exit("epoll_ctl_error");
                }
            }
            else{
                sockfd = ep[i].data.fd;
                n = Read(sockfd, buf,MAX_LINE);
                if(n == 0){
                    res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd,  NULL);
                    if(res == -1){
                        perr_exit("epoll ctl error");
                    }
                    Close(sockfd);
                    printf("client[%d] closed connection\n", sockfd);
                }else if(n < 0){
                    perror("read n < 0 error");
                    res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
                    Close(sockfd);
                }else{
                    for(i = 0;i < n;i++){
                        buf[i] = toupper(buf[i]);
                    }
                    Write(STDOUT_FILENO, buf, n);
                    Write(sockfd, buf, n);
                }
            }
        }
    }
    Close(listenfd);
    Close(efd);

    return 0;
}


epoll的重要性

epoll是linux下多路复用iO接口select的增强版本, 它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统cpu的利用率,因为它会复用文件描述符集合来传递结果而不使开发者每次等待事件之前都必须重新准备被监听的文件描述符集合。

另一点原因就是获取事件的时候,无须遍历整个被监听的文件描述符集合,只要遍历那些已经被内核IO事件异步唤醒而加入Ready队列的描述符集合

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在 C 语言中实现一个 epoll 服务器,需要以下几个步骤: 1. 创建监听套接字:首先需要创建一个监听套接字,绑定到服务器的 IP 地址和端口上,用于接受客户端连接请求。可以使用 `socket` 函数创建套接字,使用 `bind` 函数将其绑定到指定的地址和端口上,使用 `listen` 函数将其转换为监听套接字。 ```c int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字 struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到任意地址 servaddr.sin_port = htons(PORT); // 绑定到指定端口 bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); // 绑定套接字 listen(sockfd, BACKLOG); // 转换为监听套接字,BACKLOG 表示等待连接队列的最大长度 ``` 2. 创建 epoll 实例:可以使用 `epoll_create` 函数创建一个 epoll 实例,用于监听事件。 ```c int epfd = epoll_create(MAX_EVENTS); // 创建 epoll 实例,MAX_EVENTS 表示监听的最大事件数 ``` 3. 将监听套接字添加到 epoll 实例中:使用 `epoll_ctl` 函数将监听套接字添加到 epoll 实例中,以便监听连接请求事件。 ```c struct epoll_event event; event.events = EPOLLIN; // 监听可读事件 event.data.fd = sockfd; // 监听的文件描述符为监听套接字 epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event); // 将监听套接字添加到 epoll 实例中 ``` 4. 进入事件循环:使用 `epoll_wait` 函数进入事件循环,等待事件的发生。当有事件发生时,使用 `accept` 函数接受客户端连接,并将新连接套接字添加到 epoll 实例中,以便监听其读取事件。 ```c struct epoll_event events[MAX_EVENTS]; while (1) { int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); // 等待事件的发生 for (int i = 0; i < nfds; i++) { if (events[i].data.fd == sockfd) { // 监听套接字有可读事件,表示有新连接请求 struct sockaddr_in cliaddr; socklen_t clilen = sizeof(cliaddr); int connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen); // 接受连接请求 struct epoll_event event; event.events = EPOLLIN; // 监听可读事件 event.data.fd = connfd; // 监听的文件描述符为新连接套接字 epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event); // 将新连接套接字添加到 epoll 实例中 } else { // 客户端有可读事件 char buf[MAXLINE]; int n = read(events[i].data.fd, buf, MAXLINE); // 读取数据 if (n == 0) { // 客户端关闭连接 epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL); // 从 epoll 实例中删除套接字 close(events[i].data.fd); // 关闭套接字 } else { write(events[i].data.fd, buf, n); // 发送数据 } } } } ``` 上述代码中,`MAX_EVENTS` 表示监听的最大事件数,`MAXLINE` 表示一次读取的最大数据量,`BACKLOG` 表示等待连接队列的最大长度。可以根据实际情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值