linux下 简单学习epoll处理海量链接

 最近在 看一些关于linux C 服务器编程方面的东西,随及就涉及到了大量连接的处理的问题。我 们知道在linux下我们一般用tcp/ip协议去写简单的C/S模型的代码,经常会用到select()函数。它是用来确定一个或多个套接口状态的函数。 对每一个套接口 ,调用者可查询它的可读性、可写性及错误状态信息。用fd_set结构(可以google)来表示一组等待 检查的套接口,可以用来实现多路复用的I/O模型,完成我们所谓的非阻塞模式的工作方式(就是进程或线程执行此函数时不必非要等待事件发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)这里不再赘述。

    epoll与select方式的最大区别首先在于select所用到的FD_SET是有限的,他的大小是由内核中__FD_SETSIZE这个参数来定义的(默认为2048)。改变大小就需要重新编译linux内核。而epoll则不是那样,它的FD上限是最大可以打开文件的数目,这个数字一般远大于2048。你可以用cat /proc/sys/fs/file-max来查看自己机子的上线值。还有其他优点像一些什么内核微调,加速内核与用户空间的信息传递。这些都涉及到了linux内核的机制。。本人不是很懂。

下面介绍一下epoll的使用方法。

       epoll首先是通过create_epoll(int maxfds)来创建一个epoll句柄。(当然最基本的先要建立socket的一系列操作),其中maxfds是epoll所支持的最大句柄数。之后在你的网络主循环里面(就是我们经常写的while(1){...}),每一次链接时间的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,查看他的可读性与可写性。

     其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成 功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。在epoll_wait()之后必需要用循环去遍历事件

for(n = 0; n < nfds; ++n) { 
                if(events[n].data.fd == listener) { /表示有新连接进入了,进行新连接的处理。 
       client = accept(listener, (struct sockaddr *) &local,  &addrlen);
                 if(client < 0){ 
                        perror("accept"); 
                        continue;        
              }
                    setnonblocking(client); // 将新连接置于非阻塞模式,用fcntl()实现(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK);
                    ev.events = EPOLLIN | EPOLLET; // 
注意,这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,如果有写操作的话,这个时候epoll是不会返回事件的,如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET 
                    ev.data.fd = client; 
                    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { 
// 设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面,这里用EPOLL_CTL_ADD来加一个新的 epoll事件,通过EPOLL_CTL_DEL来减少一个epoll事件,通过EPOLL_CTL_MOD来修改一个事件的监听方式。 
                        fprintf(stderr, "epoll set insertion error: fd=%d0, 
                                client); 
                        return -1; 
                    } 
                } 
                else //如果不是主socket的事件的话,则代表是一个用户socket的事件,则来处理这个事件。(比如对应的socket之间的消息传送和接受send()|recv()。。。)
                        handle_message(events[n].data.fd); 

}          
   最后可以用close()去关闭epoll句柄。总共只涉及到了四个函数   epoll_create, epoll_ctl, epoll_wait和close。

一般为了提高服务器的效率可以将事件的监听与处理用不同的线程去处理。这样效率会更高。



网络上搜的关于epoll与其他方式的比较:

 PPC/TPC 模型

这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

 select 模型

 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,www.linuxidc.com 由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …

 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了??!!

 内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法。

 poll 模型

基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。

 Epoll 的提升

把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。

Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。

效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。

 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了

你可能感兴趣的:(linux下 简单学习epoll处理海量链接)