select 和 epoll都是用来监听套接字上是否有事件发生,简单来讲,select是轮询方式,而epoll是触发方式的,用回调把信息赋给event结构体。
select:轮询检查文件描述符集合,实现方法如下:
fd_set fdRead;
//将文件描述符集合清零
FD_ZERO(&fdRead);
//在文件描述rd中增加文件描述符iSocket,可以加多个
FD_SET(iSocket,&fdRead);
//select ,设置recv超时时间,写的类似
int iRet = select(iSocket+ 1, &fdRead, NULL, NULL, &tvTimeOut);
switch(iRet)
{
case 0:
printf("Time out \n");
return -1;
case -1:
printf("Select Error\n");
return -2;
default:
if (FD_ISSET(iSocket,&fdRead))//测试iSocket是否在描述符集合中
{
iRecvLen = recv(iSocket,pcRecvBuf, iBufLen, 0);
if (iRecvLen == -1)
{
printf("Recverror\n");
return -3;
}
else if(0 == iRecvLen)
{
printf("Socketclose \n");
return -4;
}
else
{
break;
}
}
}
如果文件描述符中有多个socket,便会轮询检查。
epoll:epoll的使用,首先通过epoll_create创建一个epoll对象,得到一个epoll的描述符,我们需要监听哪个fd上的事件通过epoll_ctl,最后 epoll_wait会返回发生了事件的数目,具体的事件在参数events中。
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<sys/ioctl.h>
#include<sys/epoll.h>
#define MAX_EVENTS10
#define LISTENQ 12
int main()
{
int n = 0;
int listen_sock;
int conn_sock;
int nfds;
int epollfd;
int iRet = -1;
socklen_t len;
struct epoll_event ev;
struct epoll_event events[MAX_EVENTS];
char buf[32] = "tongzhilin";
/*create listen socket */
listen_sock = socket(AF_INET,SOCK_STREAM, 0);
int iSocketOpt= 1;
setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,(void *)&iSocketOpt, sizeof(int));
/*bind*/
struct sockaddr_instServerAddrIn;
stServerAddrIn.sin_family = AF_INET;
stServerAddrIn.sin_port = htons(6666);
stServerAddrIn.sin_addr.s_addr =htonl(INADDR_ANY);
iRet = bind(listen_sock,(structsockaddr*)&stServerAddrIn, sizeof(stServerAddrIn));
if(iRet < 0)
{
printf("bind error\n");
return -3;
}
/*listen*/
iRet = listen(listen_sock, LISTENQ);
if(iRet < 0)
{
printf("listenerror\n");
return -3;
}
/*epoll*/
epollfd = epoll_create(10);
if(epollfd == -1)
{
perror("epoll_create");
return -1;
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
//EPOLL_CTL_ADD:注册;EPOLL_CTL_MOD修改;EPOLL_CTL_DEL:删除
if(epoll_ctl(epollfd, EPOLL_CTL_ADD,listen_sock, &ev) == -1)
{
perror("epoll_ctl:listen_sock");
return -1;
}
while(1)
{
printf("Wait..\n");
nfds = epoll_wait(epollfd, events,MAX_EVENTS, -1);
if(nfds == -1)
{
perror("epoll_pwait");
return -1;
}
for (n = 0; n < nfds; ++n)
{
if(events[n].data.fd ==listen_sock)
{
conn_sock =accept(listen_sock, (struct sockaddr *)&stServerAddrIn, &len);
if(conn_sock == -1)
{
perror("accept");
return -1;
}
//非阻塞
int ulflag = 1;
ioctl(listen_sock,FIONBIO, &ulflag);
ev.events = EPOLLIN| EPOLLET;
ev.data.fd =conn_sock;
//EPOLL_CTL_ADD:注册;EPOLL_CTL_MOD修改;EPOLL_CTL_DEL:删除
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1)
{
perror("epoll_ctl:conn_sock");
return -1;
}
}
else
{
send(events[n].data.fd,buf, sizeof(buf), 0);
}
}
}
return 0;
}
总结:select如果同时建立的连接很多,但是只有少数的有事件发生,这种情况下,效率就会很低。epoll的实现中就避免了该问题,对于要监管的每个fd都会有回调函数,当该fd上发生事件时,会调用对应的回调函数。这样,在连接很多,少数事件发生的情况下,依旧会效率很高。当然,如果连接很多,大部分连接都有事件时,两者的效率应该是差不多的。