什麼是epoll?
epoll是linux內核爲處理大批量文件描述符而作了改進的poll,是Linux下多路複用IO接口select/poll的增強版本,它能顯著提高程序在大
量併發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要
遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。
注:epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空
間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。
注:epoll文件描述符用完後,直接用close關閉即可,非常方便。事實上,任何被偵聽的文件符只要其被關閉,那麼它也會自動從被偵
聽的文件描述符集合中刪除,很是智能。
epoll相關的系統調用有:epoll_create, epoll_ctl和epoll_wait。
epoll_create:
返回值:>0:非空文件描述符;
-1:函數調用失敗,同時會自動設置全局變量errno;
epoll_ctl:用來添加/修改/刪除需要偵聽的文件描述符及其事件.
epoll的事件註冊函數,它不同於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。
op:表示動作,爲以下三個宏任意一個(根據需要):添加,修改,刪除
.epoll_wait:接收發生在被偵聽的描述符上的,用戶感興趣的IO事件。
收集在epoll監控的事件中已經就緒的事件。
events:是分配好的epoll_event結構體數組,epoll將會把就緒的事件賦值到events數組中(events不可以是空指針,內核只負責把數據複製到這個events數組中)。
maxevents:告之內核這個events有多大,這個 maxevents的值不能小於創建epoll_create()時的size
timeout:是超時時間(毫秒)
1.0表示輪詢非阻塞,立即返回;
2.-1將不確定,也有說法說是永久阻塞。
3.大於0,以timeput事件輪詢返回
返回值:
1.成功,返回對應I/O上已準備好的文件描述符數
2.0表示已超時。
3.-1,發生錯誤。
epoll的優點:
本身沒有最大併發連接的限制,僅受系統中進程能打開的最大文件數目限制;
效率提升:只有活躍的socket纔會主動的去調用callback函數;
省去不必要的內存拷貝:epoll通過內核與用戶空間mmap同一塊內存實現。
當然,以上的優缺點僅僅是特定場景下的情況:高併發,且任一時間只有少數socket是活躍的。
服務器具體實現代碼:
(客戶端代碼在前幾篇博客寫過幾個版本,在此不再贅述)
/*************************************************************************
> File Name: epoll.c
> Author: liumin
> Mail: [email protected]
> Created Time: Sun 11 Jun 2017 02:42:50 PM CST
************************************************************************/
#include<stdio.h>
#include<string.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
static void Usage(char* proc)
{
printf("Usage: %s [local_ip] [local_port]\n",proc);
}
int startup(const char* _ip, int _port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
perror("socket");
return 2;
}
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
perror("bind");
return 3;
}
if(listen(sock, 10) < 0)
{
perror("listen");
return 4;
}
return sock;
}
typedef struct fd_buf
{
int fd;
char buf[10240];
}fd_buf_t,*fd_buf_p;
static void* alloc_fd_buf(int fd)
{
fd_buf_p tmp = (fd_buf_p)malloc(sizeof(fd_buf_t));
if(!tmp)
{
perror("malloc");
return NULL;
}
tmp->fd = fd;
return tmp;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1], atoi(argv[2]));
int epollfd = epoll_create(256);
if(epollfd < 0)
{
perror("epoll_create");
close(listen_sock);
return 5;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = alloc_fd_buf(listen_sock);
epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev);
int nums = 0;
struct epoll_event evs[256];
int timeout = -1;
while(1)
{
switch(nums = epoll_wait(epollfd, evs, 256, timeout))
{
case -1:
perror("epoll_wait");
break;
case 0:
printf("timeout...\n");
break;
default:
{
printf("nums : %d \n",nums);
int i = 0;
for(; i < nums; i++)
{
fd_buf_p fp = (fd_buf_p)evs[i].data.ptr;
if(fp->fd == listen_sock && (evs[i].events & EPOLLIN))
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(new_sock < 0)
{
perror("accept");
continue;
}
printf("get a new client: ip:%s ,port:%d \n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
ev.events = EPOLLIN;
ev.data.ptr = alloc_fd_buf(new_sock);
epoll_ctl(epollfd, EPOLL_CTL_ADD, new_sock, &ev);
}//if
else if(fp->fd != listen_sock)
{
if(evs[i].events & EPOLLIN)
{
ssize_t s = read(fp->fd, fp->buf, sizeof(fp->buf));
if(s > 0)
{
fp->buf[s] = 0;
printf("client say# %s\n", fp->buf);
ev.events = EPOLLOUT;
ev.data.ptr = fp;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fp->fd, &ev);
}
else if(s <= 0)
{
printf("client quit!\n");
close(fp->fd);
epoll_ctl(epollfd, EPOLL_CTL_DEL, fp->fd, NULL);
free(fp);
}
else
{}
}
else if(evs[i].events & EPOLLOUT)
{
const char* msg = "HTTP/1.0 200 OK\r\n\r\n<html><h1>hello epoll!</h1><html>";
write(fp->fd, msg, strlen(msg));
close(fp->fd);
//這裏主要用於瀏覽器測試,所以在輸出後關閉文件描述符
//若使用客戶端、Telnet遠程登錄測試 可以去掉 實現連續發送信息
epoll_ctl(epollfd, EPOLL_CTL_DEL, fp->fd, NULL);
free(fp);
}
else
{}
}
else
{}
}//for
}
break;
}
}
return 0;
}