epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合。然后根据这个集合进行相应的数据处理。
#include
int epoll_create(int size);
int epoll_ctl(int epfd, int op,int fd,struct epoll_event *event);
int epoll_wait(int epfd,struct epoll_event*event,int maxevents,int timeout);
Epoll用于两种工作模式-水平触发(Level Triggered)和边缘触发(Edge Triggered).
水平触发(Level Triggered),是缺省的工作方式,并且同时支持 block和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select\poll都是这种模型的代表。
边缘触发(Edge Triggered).是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。
区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。
1、使用epoll_create函数创建epoll句柄
在内核版本2.6.8以后epoll_create函数中的size就不被使用,但是需要这个参数大于0。
2、事件注册
int epoll_ctl(int epfd, int op ,int fd,struct epoll_event *event);
参数解析:
Epfd:是epoll_create所拿到的句柄
op:表示要在epfd上进行的操作,其用三个宏来表示
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件
EPOLL_CTL_DEL: 从epfd中删除fd
Fd:需要监听的fd
Event:告诉内核监听事件的类型,其 struct epoll_event结构体如下所示:
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 datavariable */
};
其中包含两部分:
事件类型(events),可以使用以下的几个宏:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
用户数据,用户可以将相关文件描述符的的数据存放到data中。
3、等待事件产生
int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout);
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值必须大于0,参数timeout是超时时间(毫秒单位计数,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
实现简单的聊天室程序,服务器是基于epoll模型实现。整个程序包含1个头文件和3个c文件,service.c为服务器程序,client.c为客户端程序,socket.c为socket封装好的API函数接口
service.c程序
/*************************************************************************************************************
* 文件名: service.c
* 功能: 基于TCP创建socket服务端程序
* 作者: edward
* 创建时间: 2014年12月31日9:45
* 最后修改时间: 2012年3月12日
* 详细: 根据的socket建立TCP连接过程,创建服务程序,
并在服务端等待接收和处理客户端的数据,
并将结果输出到终端上
*************************************************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "socket.h"
#define MAXFDS 256
int main(int argc,char**argv)
{
int socketFd;
int nfds;
int epfd;
int NewsocketFd;
int Count;
int rtn;
unsigned char buf[1024];
struct epoll_event ev, events[MAXFDS];
if(2 > argc)
{
Log("Input error\n");
Log("Usage: %s \n",argv[0]);
Log("Eample: %s 1234\n",argv[0]);
return -1;
}
//1、服务器初始化
socketFd = socketServiceInit(atoi(argv[1]));
if(0 > socketFd)
{
Log("Error socketServiceInit\n");
return -1;
}
do{
//~向内核申请MAXFDS+1大小的存储fd的空间
epfd = epoll_create(MAXFDS+1);
if(0 > epfd)
{
Log("Error epoll_create\n");
break;
}
//~设置需要监听的fd和触发事件类型
ev.data.fd = socketFd;
/*
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
*/
ev.events = EPOLLIN | EPOLLET;
//~控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件
rtn = epoll_ctl(epfd,EPOLL_CTL_ADD,socketFd,&ev);
/*
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值
EPOLL_CTL_ADD 注册、
EPOLL_CTL_MOD 修 改、
EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
如果调用成功返回0,不成功返回-1
*/
if(0 > rtn)
{
Log("Error epoll_ctl\n");
break;
}
for(;;)
{ //~轮询I/O事件的发生
nfds = epoll_wait(epfd,events,MAXFDS,0);
/*
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;
-1相当于阻塞,
0相当于非阻塞。
一般用-1即可返回发生事件数。
*/
for(Count = 0;Count NewsocketFd)
{
continue;
}
//将客户端的FD放入epoll中
ev.data.fd = NewsocketFd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,NewsocketFd,&ev);
//接收到数据,读socket
}else if(events[Count].events & EPOLLIN){
//Log("read");
if(socketFd == events[Count].data.fd) continue;
bzero(buf,sizeof(buf));
//3、接收客户端数据
rtn = SocketRecv(events[Count].data.fd,buf,sizeof(buf));
if(0 == rtn)
{
Log("Connect Close \n");
epoll_ctl(epfd, EPOLL_CTL_DEL, events[Count].data.fd, NULL);
deleteSocketRec(events[Count].data.fd);
continue;
}
Log("[Recv]: %s",buf);
//4、发送数据到客户端
socketSeverSendAllclients(buf,sizeof(buf));
//有数据待发送,写socket
}else if(events[Count].events & EPOLLOUT){
//Log("write\n");
strcpy(buf,"data from service");
socketSeverSendAllclients(buf,sizeof(buf));
}
}
}
}while(0);
//5、关闭所有的连接,断开TCP请求
socketSeverClose();
return 0;
}
Client.c程序
/*************************************************************************************************************
* 文件名: client.c
* 功能: 基于TCP创建socket服务端程序
* 作者: edward
* 创建时间: 2014年12月31日9:45
* 最后修改时间: 2012年3月12日
* 详细: 根据的socket建立TCP连接过程,创建客户端程序,
并在与服务器建立连接后,想服务发送数据
*************************************************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "socket.h"
#define SIZE 1024
int main(int argc,char**argv)
{
int rtn;
int socketFd;
unsigned char buf[SIZE];
unsigned char flag = 1;
if(3 > argc)
{
Log("Input error\n");
Log("Usage: %s \n",argv[0]);
Log("Eample: %s 192.168.0.119 1234\n",argv[0]);
return -1;
}
//1、初始化Socket
socketFd = SocketClientInit(argv[1],argv[2]);
if(0 > socketFd)
{
Log("Error on SocketClientInit function\n ");
return -1;
}
//2、往服务器发送数据,并接收服务器数据
while(flag > 0)
{
bzero(buf,sizeof(buf));
printf("Input: ");
fgets(buf,SIZE,stdin);
rtn = strncmp(buf,"exit",4);
if(0 == rtn)
{
flag = 0;
}
socketSend(socketFd,buf,sizeof(buf));
bzero(buf,sizeof(buf));
SocketRecv(socketFd,buf,sizeof(buf));
Log("Recv: %s",buf);
}
//3、关闭的连接
socketClientClose(socketFd);
return 0;
}
/*************************************************************************************************************
* 文件名: socket.c
* 功能: socket初始化等操作函数
* 作者: edward
* 创建时间: 2014年12月31日9:45
* 最后修改时间:2014年12月31日9:45
* 详细: socket操作函数
*************************************************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "socket.h"
#define MAX_CLIENTS 50
socketRecord_t *socketRecordHead = NULL;
/*********************Socket Server 操作函数部分****************/
/*************************************************************************************************************************
*函数 : int socketServiceInit(unsigned int port)
*功能 : 服务端socket初始化
*参数 : port:端口号
*返回 : socketFd
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 在使用epoll的时候需要设置socket为非阻塞模式
*************************************************************************************************************************/
int socketServiceInit(unsigned int port)
{
struct sockaddr_in serv_addr;
int ret;
if(NULL != socketRecordHead)
{
return -1;
}
//1、分配内存空间用于存储socket的一些数据
socketRecord_t *lsSocket = malloc(sizeof(socketRecord_t));
do{
if(NULL == lsSocket)
break;
//2、创建一个socket连接类型的IPV4 ,流式套接字。
lsSocket->socketFd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == lsSocket->socketFd)
{
Log("Error opening Socket: \n");
break;
}
//3、设置的为非阻塞模式
fcntl(lsSocket->socketFd,F_SETFL,O_NONBLOCK);
bzero(&serv_addr,sizeof(struct sockaddr_in));
//4、填充服务器端的sockaddr结构
serv_addr.sin_family = AF_INET; //IPV4
//(将本机器上的long数据转化为网络上的long数据)服务器程序能运行在任何ip的主机上 //INADDR_ANY 表示主机可以是任意IP地址, 即服务器程序可以绑定到所有的IP上
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//server_addr.sin_addr.s_addr=inet_addr("192.1 68.1 .1 "); //用 于绑定到一个固定IP,inet_addr用 于把数字加格式的ip转化为整形ip
serv_addr.sin_port = htons(port);//设置端口号(将本机器上的short数据转化为网络上的short数据)
//5、捆绑socketfd描述符到IP地址
ret = bind(lsSocket->socketFd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(-1 == ret)
{
Log("Error on binding: \n");
break;
}
//6、设置允许连接的最大客户端的数目
ret = listen(lsSocket->socketFd,10);
if(-1 == ret)
{
Log("Error on Listen: \n");
break;
}
lsSocket->next = NULL;
socketRecordHead = lsSocket;
Log("Service IP %s:%d\n",inet_ntoa(serv_addr.sin_addr),port);
return lsSocket->socketFd;
}while(0);
free(lsSocket);
return -1;
}
/*************************************************************************************************************************
*函数 : int createSocketRec(void)
*功能 : 服务端socket初始化
*参数 : 无
*返回 : socketfd
-1 失败
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 在使用epoll的时候需要设置socket为非阻塞模式
*************************************************************************************************************************/
int createSocketRec(void)
{
socketRecord_t *srchRec;
int ret ,tr = 1;
if(NULL == socketRecordHead)
{
return -1;
}
socketRecord_t *newSocket = malloc(sizeof(socketRecord_t));
do{
if(NULL == newSocket)break;
newSocket->clilen = sizeof(newSocket->cli_addr);
//接收客户端连接请求
newSocket->socketFd = accept(socketRecordHead->socketFd,(struct sockaddr *) &(newSocket->cli_addr), &(newSocket->clilen));
if(0 > newSocket->socketFd)
{
Log("Error on accept: \n");
break;
}
fcntl(newSocket->socketFd, F_SETFL, O_NONBLOCK);
newSocket->next = NULL;
srchRec = socketRecordHead;
while (srchRec->next)
srchRec = srchRec->next;
srchRec->next = newSocket;
Log("content from %s\n",inet_ntoa(newSocket->cli_addr.sin_addr));
return (newSocket->socketFd);
}while(0);
Log("Create error\n");
free(newSocket);
return -1;
}
/*************************************************************************************************************************
*函数 : void deleteSocketRec(int rmSocketFd)
*功能 : 服务端关闭连接请求
*参数 : rmSocketFd:
*返回 : 无
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 在关闭的同时,需要将该socketFD从单链表中删除
*************************************************************************************************************************/
void deleteSocketRec(int rmSocketFd)
{
socketRecord_t *srchRec, *prevRec = NULL;
// Head of the timer list
srchRec = socketRecordHead;
// Stop when rec found or at the end
while ((srchRec->socketFd != rmSocketFd) && (srchRec->next))
{
prevRec = srchRec;
// over to next
srchRec = srchRec->next;
}
if (srchRec->socketFd != rmSocketFd)
{
Log("deleteSocketRec: record not found\n");
return;
}
// Does the record exist
if (srchRec)
{
// delete the timer from the list
if (prevRec == NULL)
{
//trying to remove first rec, which is always the listining socket
Log(
"deleteSocketRec: removing first rec, which is always the listining socket\n");
return;
}
//remove record from list
prevRec->next = srchRec->next;
close(srchRec->socketFd);
free(srchRec);
}
}
/*************************************************************************************************************************
*函数 : int socketSeverGetNumClients(void)
*功能 : 服务端获取已经连接的客户端数目
*参数 : 无
*返回 : -1:失败
>0 :客户端数目
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 所有的已经连接上的客户端信息都保存在链表中
*************************************************************************************************************************/
int socketSeverGetNumClients(void)
{
int recordCnt = 0;
socketRecord_t *srchRec;
//Log("socketSeverGetNumClients++\n", recordCnt);
// Head of the timer list
srchRec = socketRecordHead;
if (srchRec == NULL)
{
//Log("socketSeverGetNumClients: socketRecordHead NULL\n");
return -1;
}
// Stop when rec found or at the end
while (srchRec)
{
//Log("socketSeverGetNumClients: recordCnt=%d\n", recordCnt);
srchRec = srchRec->next;
recordCnt++;
}
//Log("socketSeverGetNumClients %d\n", recordCnt);
return (recordCnt);
}
/*************************************************************************************************************************
*函数 : void socketSeverGetClientFds(int *fds, int maxFds)
*功能 : 服务端关闭连接请求
*参数 : fds: 存储socketFd的数组
maxFds:fds数组大小
*返回 : 无
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 遍历链表得到所有socketfd
*************************************************************************************************************************/
void socketSeverGetClientFds(int *fds, int maxFds)
{
int recordCnt = 0;
socketRecord_t *srchRec;
assert(fds!=NULL);
// Head of the timer list
srchRec = socketRecordHead;
// Stop when at the end or max is reached
while ((srchRec) && (recordCnt < maxFds))
{
//printf("getClientFds: adding fd%d, to idx:%d \n", srchRec->socketFd, recordCnt);
fds[recordCnt++] = srchRec->socketFd;
srchRec = srchRec->next;
}
return;
}
/*************************************************************************************************************************
*函数 : void socketSeverClose(void)
*功能 : 服务端关闭连接请求
*参数 : 无
*返回 : 无
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 在关闭的同时,需要将该socketFD从单链表中删除
*************************************************************************************************************************/
void socketSeverClose(void)
{
int fds[MAX_CLIENTS], idx = 0;
socketSeverGetClientFds(fds, MAX_CLIENTS);
while (socketSeverGetNumClients() > 1)
{
Log("socketSeverClose: Closing socket fd:%d\n", fds[idx]);
deleteSocketRec(fds[idx++]);
}
//Now remove the listening socket
if (fds[0])
{
Log("socketSeverClose: Closing the listening socket\n");
close(fds[0]);
}
}
/*************************************************************************************************************************
*函数 : int socketSeverSendAllclients(unsigned char* buf, int len)
*功能 : 服务端向所有的客户端发送数据
*参数 : buf:数据存储
len:数据长度
*返回 : 0:成功
-1:失败
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 无
*************************************************************************************************************************/
int socketSeverSendAllclients(unsigned char* buf, int len)
{
int rtn;
socketRecord_t *srchRec;
assert(buf != NULL);
// first client socket
srchRec = socketRecordHead->next;
// Stop when at the end or max is reached
while (srchRec)
{
//printf("SRPC_Send: client %d\n", cnt++);
rtn = write(srchRec->socketFd, buf, len);
if (rtn < 0)
{
Log("ERROR writing to socket %d\n", srchRec->socketFd);
Log("closing client socket\n");
//remove the record and close the socket
deleteSocketRec(srchRec->socketFd);
return rtn;
}
srchRec = srchRec->next;
}
return 0;
}
/*********************Socket Client 操作函数部分****************/
/*************************************************************************************************************************
*函数 : int SocketClientInit(const char *addr,const char* port)
*功能 : 客户端初始化
*参数 : addr:IP地址
port:端口号
*返回 : socketfd
-1 失败
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 无
*************************************************************************************************************************/
int SocketClientInit(const char *addr,const char* port)
{
int rtn;
int socketFd;
struct sockaddr_in server_addr;
assert(addr!=NULL);
assert(port!=NULL);
socketFd = socket(AF_INET,SOCK_STREAM,0);
if(0 > socketFd)
{
Log("Error on socket\n");
return -1;
}
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = inet_addr(addr);
rtn = connect(socketFd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));
if(0 > rtn)
{
Log("Error on connect\n");
return -1;
}
return socketFd;
}
/*************************************************************************************************************************
*函数 : int socketClientClose(int socketFd)
*功能 : 客户端关闭连接请求
*参数 : socketFd: 客户端文件描述符
*返回 : 0:成功
-1:失败
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 无
*************************************************************************************************************************/
int socketClientClose(int socketFd)
{
if(socketFd)
{
close(socketFd);
return 0;
}
return -1;
}
/*********************公共操作函数部分****************/
/*************************************************************************************************************************
*函数 : int socketSend(int fdClient,unsigned char* buf, int len)
*功能 : 服发送数据函数
*参数 : fdClient: socket文件描述符
buf:数据存储区
len:数据长度
*返回 : 返回写入的字节数
-1 写入失败
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 无
*************************************************************************************************************************/
int socketSend(int fdClient,unsigned char* buf, int len)
{
int rtn = -1;
//Log("socketSend++: writing to socket fd %d\n", fdClient);
assert(NULL != buf);
if (fdClient)
{
rtn = write(fdClient, buf, len);
if (rtn < 0)
{
Log("ERROR writing to socket %d\n", fdClient);
return rtn;
}
}
//Log("socketSend--\n");
return rtn;
}
/*************************************************************************************************************************
*函数 : int SocketRecv(int fdClient,unsigned char *buf,int len)
*功能 : 接收数据函数
*参数 : fdClient: socket文件描述符
buf:数据存储区
len:数据长度
*返回 : 返回读取的字节数
-1 读取失败
*依赖 : 无
*作者 : edward
*时间 : 20141231
*最后修改时间: 20141231
*说明 : 无
*************************************************************************************************************************/
int SocketRecv(int fdClient,unsigned char *buf,int len)
{
int rtn;
assert(NULL != buf);
if(fdClient)
{
rtn = read(fdClient,buf,len);
if (rtn < 0)
{
Log("ERROR Reading to socket %d\n", fdClient);
return rtn;
}
}
return rtn;
}
#ifndef __SOCKET_H__
#define __SOCKET_H__
#include
#include
#include
#define DEBUG
#ifdef DEBUG
#define A_OUT printf("%s:%s:%d:", __FILE__, __FUNCTION__,__LINE__);fflush(stdout);
#define Log(fmt,args...) A_OUT printf(fmt, ##args)
#else
#define Log(fmt,args...) printf(fmt, ##args)
#endif
typedef struct {
void *next;
int socketFd;
socklen_t clilen;
struct sockaddr_in cli_addr;
} socketRecord_t;
#endif