多路I/O转接之epoll模型

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结

果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了,因此性能得到很大的提高。对于多文件描述符而且都处于大量活跃情况下,其性能提升的并不高,性能和poll差不多。

对于新浪,腾讯这种服务器,其连接数量比较多,而活跃度不是很高。这种情况下适合使用epoll。创建文件描述符最大可以创建几十万个,但是能创建进程和线程的个数会少很多。

1.首先要设置系统最大打开文件描述符限制(Ubuntu 系统)

输入ulimit -n 4096 可以设置打开文件描述符个数的,这是系统默认最大打开文件描述符个数,如果超过这个就会报错。

下面介绍通过修改系统配置文件,来改变这一默认值。修改后需要重启系统才能生效。

 sudo vi /etc/security/limits.conf


*表示所有用户  nofile 表示最大打开文件个数 soft 表示软限制 hard 硬限制。soft限制不能超过硬限制。hard值不能大于系统file-max值。

epoll 模型及相关API


#include <sys/epoll.h>

int epoll_create(int size);//参数size用来告诉内核监听的文件描述符个数
//(确切谁最大并发数量大小),跟内存大小有关,linux内核用一颗红黑树去维护,返回一个文件句柄

//控制某个epoll监控的文件描述符上的事件:注册(相当于把要监听的文件描述符插入到树中)、修改(本身监听的是写属性,修改为读属演变为树的查找)、
//删除(删除树的节点)。
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告诉内核要监听的事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
typedef union epoll_data
{ 
     void *ptr;
     int fd; 
     uint32_t u32; 
     uint64_t u64;
}epoll_data_t;
struct epoll_event 
{
    uint32_t events; /* Epoll events */ 
    epoll_data_t data; /* User data variable */
};
//event事件
//EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
//EPOLLOUT:表示对应的文件描述符可以写
//EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
//EPOLLERR:表示对应的文件描述符发生错误
//EPOLLHUP:表示对应的文件描述符被挂断;
//EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
//EPOLLONESHOT:只监听一次
关于ET(Edge Trigger)和LT(Level Trigger)
 
 
 
 

LT是缺省工作模式

对于采用LT工作模式的文件描述符,当epoll_wait()接收到某一个文件描述符事件消息时候可以不立即触处理该事件,当程序下一次调用epoll_wait还会再次接收到该文件描述符事件消息,直到该事件被处理。而对于采用ET工作模式的文件描述符,当epoll_wait一旦接收到某一个文件描述符消息,程序必须立即处理该事件,因为后续的epoll_wait调用将不再向程序通知这一事件。

#include<stdio.h>
#include<string.h>
#include <sys/epoll.h>
#include <sys/un.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<errno.h>
#define OPEN_MAX 1024

int create_listen(int port);//创建监听socket描述符
int socket_recv(int socket_fd);//处理客户端消息
int socket_accept(int listen_st);//接收客服端连接
int run_server(int port);

int create_listen(int port)
{
    int listen_st,on;
    struct sockaddr_in s_addr;
    listen_st =socket(AF_INET,SOCK_STREAM,0);
    if(listen_st==-1)
    {
        perror("socket error ");
        return -1;
    }
    if(setsockopt(listen_st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))==-1)
    {
        perror("setsockopt error");
        return -1;
    }
    s_addr.sin_port=htons(port);
    s_addr.sin_family=AF_INET;
    s_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    
    if(bind(listen_st,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in))==-1)
    {
        perror("bind error");
        return -1;
    }
    if (listen(listen_st, 100) == -1) // 设置文件描述符具有监听的功能
    {  
        perror("listen error");
        return -1;  
    }  
    return listen_st;  
}
int socket_recv(int socket_fd)
{
    char buf[1024]={0};
    int len=0;
    bzero(buf,sizeof(buf));
    if((len=recv(socket_fd,buf,sizeof(buf),0))>0)
    {
        printf("recv:%s \n",buf);
        send(socket_fd,buf,strlen(buf),0);
        return len;
    }
    return len;
}
int socket_accept(int listen_st)
{
    struct sockaddr_in c_addr;
    int sockaddr_len,conn_st;
    sockaddr_len=sizeof(c_addr);
    bzero(&c_addr,sizeof(struct sockaddr_in));
    conn_st=accept(listen_st,(struct sockaddr *)&c_addr,&sockaddr_len);
    if(conn_st<0)
    {
        perror("accept");
    }else
    {
         printf("received form %s at port:%d \n",
             inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
    }
    return conn_st;
 
}
int run_server(int port)
{
    int i,listen_st,conn_st,st;
    int epoll_fd;
    int nready;

    listen_st=create_listen(port);
    if(listen_st==-1)
    {
        return -1;
    }
    if((epoll_fd=epoll_create(OPEN_MAX))==-1)//创建一个树
    {
        perror("epoll_create");
        return -1;
    }
    struct epoll_event event,ep[OPEN_MAX];
    event.events=EPOLLIN|EPOLLERR|EPOLLHUP;//事件为读、出错、挂起
    event.data.fd=listen_st;
    epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_st,&event);
    while(1)
    {
        nready = epoll_wait(epoll_fd,ep, OPEN_MAX, -1);//小于0的表示epoll 阻塞
        if(nready<0)
        {
            perror("epoll error");
            break;
        }
        for(i=0;i<nready;i++)
        {
            if(!ep[i].events&EPOLLIN)
            {
                continue;
            }
            if(ep[i].data.fd==listen_st)
            {
                conn_st=socket_accept(listen_st);
                event.events=EPOLLIN|EPOLLERR|EPOLLHUP;//需要监听的事件
                event.data.fd=conn_st;
                if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,conn_st,&event)==-1) //添加监听文件描述符
                {
                    perror("eopll_ctl error");
                }
                continue;
            }
            
            if(ep[i].events&EPOLLIN)
            {
                st=ep[i].data.fd;
                if(socket_recv(st)<=0)
                {
                    close(st);
                    ep[i].data.fd=-1;
                }
            }
            if(ep[i].events&EPOLLERR)
            {
                st=ep[i].data.fd;
                close(st);
                ep[i].data.fd=-1;
            }
            if(ep[i].events&EPOLLHUP)
            {
                st=ep[i].data.fd;
                if(socket_recv(st)<=0)
                {
                    close(st);
                    ep[i].data.fd=-1;
                }
            }
        }

    }
    close(listen_st);
    close(epoll_fd);
    return 0;
}


int main(int argc,char *argv[])
{
    if(argc<2)
    {
        printf("usage:%s port \n",argv[0]);
        return 0;
    }
    int port=atoi(argv[1]);
    if(port==0)
    {
        printf("port error \n");
        return 0;
    }
    printf("start server \n");
    run_server(port);
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值