IOMultiplex (一)- select/epoll
1.select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:#define__FD_SETSIZE 1024 表示select最多同时监听1024个fd。而epoll没有,它的限制是最大的打开文件句柄数目。
2.epoll的最大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个"伪"AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。
3.使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。
4.epoll是linux 专属,windows没有,select是windows和linux都有的,windows还有个完成端口这里不讨论。
5.select是轮询fd,而epoll是轮询event。
6.select是水平触发,epoll分为水平触发和select一样(默认,EPOLLLT),边缘触发方式(EPOLLET)。(例如:同时有5个client连接listen socket EPOLLLT会提醒5次。EPOLLET 会提醒1次,要循环做recv才能直到errno == EAGAIN才完成。)
二:程序示例
#ifdef WIN32
#define IP "127.0.0.1"
//#define IP "192.168.8.173"
#else
#define IP "0.0.0.0"
//#define IP "192.168.8.15"
#endif
#define PORT 6000
#define DLEVEL 0
int main(int argc, char ** argv)
{
int ret = 0;
int socket = -1;
int newsocket = -1;
char buf[100] = "zwg";
int bufsize = 100;
int sendsize = 0;
int recvsize = 0;
#if DLEVEL == 0
//tcp server
printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Tcp Server <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
ITcpInterface * ntcp = new CTcp();
ISocketInterface * pSocket = (ISocketInterface*)ntcp->GetSocketInterface();
socket = pSocket->Create(IP,PORT,SOCK_STREAM);
ret = ntcp->Listen(socket);
if (ret < 0)
{
printf("socket listen error \n");
pSocket->Close(socket);
}
printf(">>>>>>>>>>>>>>> Local IP :%s PROT : %d <<<<<<<<<<<<<<<<\n", pSocket->GetLocalIp(socket),pSocket->GetLocalPort(socket));
pSocket->InitIOMultiplex(socket);
pSocket->AddIOMultiplex(socket);
for (;;)
{
ret = pSocket->UseIOMultiplex(TIMEOUT);
if (ret == STATE_ERROR)
{
break;
}
else if (ret == STATE_TIMEOUT)
{
continue;
}
else if (ret >= STATE_READY)
{
for (int i = 0 ; i < ret ; i++)
{
//如果是新用户连接
if (pSocket->IsNewConnect(socket,i))
{
newsocket = ntcp->Accept(socket);
pSocket->AddIOMultiplex(newsocket);
pSocket->DestoryReadEvent(i);
pSocket->DestoryWriteEvent(i);
printf(">>>>>>>>>>>>>>> new connect IP :%s PROT : %d <<<<<<<<<<<<<<<<\n", ntcp->GetConnetIp(newsocket),ntcp->GetConnetPort(newsocket));
continue;
}
//如果是读事件
if(pSocket->IsReadEvent(i))
{
memset(buf,0,100);
recvsize = ntcp->Recv(buf,bufsize,pSocket->GetFdListOrEvents(i));
//如果不是用户断开连接
if(!pSocket->IsCloseEvent(i,recvsize))
{
printf("recv size = %d : %s\n",recvsize,buf);
pSocket->DestoryReadEvent(i);
//如果是可写事件 如果协议栈中没有满则一直可写,不是有写事件
if (pSocket->IsWriteEvent(i))
{
sendsize = ntcp->Send("zwg",bufsize,pSocket->GetFdListOrEvents(i));
printf("send size = %d : %s\n",sendsize,buf);
pSocket->DestoryWriteEvent(i);
continue;
}
}
else
{
printf(">>>>>>>>>>>>>>> connect close IP :%s PROT : %d <<<<<<<<<<<<<<<<\n", ntcp->GetConnetIp(pSocket->GetFdListOrEvents(i)),ntcp->GetConnetPort(pSocket->GetFdListOrEvents(i)));
pSocket->Close(pSocket->GetFdListOrEvents(i));
}
continue;
}
else
{
continue;
}
}
}
else
{
continue;
}
}
pSocket->UinitIOMultiplex();
pSocket->Close(socket);
if (ntcp)
{
delete ntcp;
ntcp = NULL;
}
#endif
printf("\npress any key to quit....");
return getchar();
</pre><p>二:具体实现</p><p><strong><span style="color:#ff0000">//zsocket.h</span></strong></p><p></p><pre name="code" class="cpp">/*
* Copyright (c/c++) <2014.09.17> <zwg>
* Function The Socket operation tcp && udp server && client && socketpair for process
*/
#ifndef __ZSOCKET__H__
#define __ZSOCKET__H__
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#ifdef WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") //添加SOCKET库
#pragma warning(disable : 4786) //禁止显示4786警告
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#endif
using namespace std;
//选择返回SOCKET当前状态类型
#define STATE_READY 1
#define STATE_ERROR -1
#define STATE_TIMEOUT 0
//And how to "read" and "write" and " A new connection" at the same time happen
#define MAX_EVENT_NUM 1024
#define MAX_FD_NUM 1024
//socket 超时时间 //3s
#define TIMEOUT 3
#ifdef WIN32
typedef int socklen_t;
#else
#endif
//Common interface
struct ISocketInterface
{
virtual ~ISocketInterface(){};
virtual int Create(char * ip = "", int port = 0,int sock_type = SOCK_STREAM) = 0;
virtual void Close(int socket) = 0;
virtual int InitIOMultiplex(int socket) = 0;
virtual int UinitIOMultiplex() = 0;
virtual int AddIOMultiplex(int socket,int epoll_modo = 0) = 0;
virtual int UseIOMultiplex(int timeout) = 0;
virtual int IsNewConnect(int socket,int eventlabel) = 0;
virtual int IsReadEvent(int label) = 0;
virtual int IsWriteEvent(int label) = 0;
virtual void DestoryReadEvent(int label) = 0;
virtual void DestoryWriteEvent(int label) = 0;
virtual int IsCloseEvent(int label,int recvsize) = 0;
virtual int GetFdListOrEvents(int label) = 0;
virtual char * GetLocalIp(int socket) = 0;
virtual int GetLocalPort(int socket) = 0;
};
class CSocket : public ISocketInterface
{
public:
CSocket();
virtual ~CSocket();
int Create(char * ip = "", int port = 0,int sock_type = SOCK_STREAM);
void Close(int socket);
int InitIOMultiplex(int socket);
int UinitIOMultiplex();
int AddIOMultiplex(int socket,int epoll_modo = 0);
int UseIOMultiplex(int timeout);
int IsNewConnect(int socket,int eventlabel);
int IsReadEvent(int label);
int IsWriteEvent(int label);
void DestoryReadEvent(int label);
void DestoryWriteEvent(int label);
int IsCloseEvent(int label,int recvsize);
int GetFdListOrEvents(int label);
char * GetLocalIp(int socket);
int GetLocalPort(int socket);
protected:
//init socket lib
int InitSocketLib();
//uinit socket lib
void UinitSocketLib();
int Select(int timeout);
int CreateEpoll(int socket);
int AddEpoll_LT(int socket);
int AddEpoll_ET(int socket);
int WaitEpoll(int timeout);
int DestroyEpoll();
private:
public:
protected:
private:
int m_IOMultiplexFd;
#ifdef WIN32
fd_set m_readFdset;
fd_set m_writeFdset;
int * m_fd_list;
#else
struct epoll_event * m_epl_events;
#endif
};
#endif
//zsocket.cpp
int CSocket::Select(int timeout)
{
int ret = 0;
#ifdef WIN32
timeval selectTimeout;
selectTimeout.tv_sec = timeout;
selectTimeout.tv_usec = 0;
/*
If there is no GetLastError () always returns 10022,
after repeated search, finally found that
int nSize = select (0, & recSocketFet, 0, 0, & nTimeOut);
After a timeout, the response would not recSocketFet socket out,
so only a socket (recSocketFet here) next time the select,
because recSocketFet no longer have the socket, so return 10022 - invalid parameters,
this should add FD_ZERO before the select (& recSocketFet);
*/
FD_ZERO (&m_readFdset);
FD_ZERO (&m_writeFdset);
if (m_fd_list != NULL)
{
for (int i = 0; i< MAX_FD_NUM ;i++)
{
if (m_fd_list[i] != -1) {
FD_SET(m_fd_list[i], &m_readFdset);
FD_SET(m_fd_list[i], &m_writeFdset);
}
}
}
/*
linux 中int maxfdp是一个整数值,是指集合中所有文件描述符的范围,
即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
*/
ret = select (m_IOMultiplexFd + 1, &m_readFdset, &m_writeFdset, NULL, &selectTimeout);
#else
#endif
return ret;
}
int CSocket::CreateEpoll(int socket)
{
#ifdef WIN32
printf("win32 can not use CreateEpoll\n");
return -1;
#else
//Besides the size parameters are ignored, this function is identical to that of epoll_create
m_IOMultiplexFd = epoll_create1 (0);
if (m_IOMultiplexFd == -1)
{
printf ("epoll_create faild\n");
return -1;
}
//And how to "read" and "write" and " A new connection" at the same time happen
m_epl_events = (struct epoll_event *)calloc (MAX_EVENT_NUM, sizeof(struct epoll_event));
#endif
return 1;
}
int CSocket::AddEpoll_LT(int socket)
{
#ifdef WIN32
printf("win32 can not use AddEpoll\n");
return -1;
#else
int ret = 0;
struct epoll_event event;
event.data.fd = socket;
event.events = EPOLLIN | EPOLLOUT ;//读,写,水平触发方式 /和select一样(默认,EPOLLLT)
ret = epoll_ctl (m_IOMultiplexFd, EPOLL_CTL_ADD, socket, &event);
if (ret == -1)
{
printf ("epoll_ctl faild\n");
return -1;
}
return 1;
#endif
}
int CSocket::AddEpoll_ET(int socket)
{
#ifdef WIN32
printf("win32 can not use AddEpoll\n");
return -1;
#else
int ret = 0;
struct epoll_event event;
event.data.fd = socket;
event.events = EPOLLIN | EPOLLOUT | EPOLLET ;//读,写,边缘触发方式
ret = epoll_ctl (m_IOMultiplexFd, EPOLL_CTL_ADD, socket, &event);
if (ret == -1)
{
printf ("epoll_ctl faild\n");
return -1;
}
return 1;
#endif
}
int CSocket::WaitEpoll(int timeout)
{
#ifdef WIN32
printf("win32 can not use WaitEpoll\n");
return -1;
#else
return epoll_wait (m_IOMultiplexFd, m_epl_events, MAX_EVENT_NUM, timeout * 1000);
#endif
}
int CSocket::DestroyEpoll()
{
#ifdef WIN32
printf("win32 can not use DestroyEpoll\n");
return -1;
#else
if (m_epl_events)
{
free(m_epl_events);
m_epl_events = NULL;
}
#endif
return 1;
}
int CSocket::Create(char * ip, int port,int sock_type)
{
struct sockaddr_in Addr;
int sockfd = 0;
unsigned long nonBlock = 1;
sockfd = socket(AF_INET, sock_type, 0);
if (sockfd == -1)
{
return sockfd;
}
#ifdef WIN32
//设置成非阻塞方式
if (ioctlsocket(sockfd, FIONBIO, &nonBlock))
{
printf("TCP Can't Put Socket In Non-Blocking Mode");
Close(sockfd);
return 0;
}
#else
//Set non-blocking way
int flags = fcntl(sockfd, F_GETFL, 0);
if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK))
{
printf("TCP Can't Put Socket In Non-Blocking Mode");
Close(sockfd);
return NULL;
}
#endif
if (ip != NULL && port > 0)
{
Addr.sin_addr.s_addr = inet_addr(ip);
Addr.sin_family = AF_INET;
Addr.sin_port = htons(port);
if (bind(sockfd, (struct sockaddr *) &Addr, sizeof(struct sockaddr_in)) ==-1)
{
return 0;
}
}
return sockfd;
}
void CSocket::Close(int socket)
{
if(!socket) //如果未创建socket
{
return;
}
#ifdef WIN32
closesocket(socket);
if (m_fd_list != NULL)
{
for (int i = 0; i< MAX_FD_NUM ;i++)
{
if (m_fd_list[i] == socket) {
m_fd_list[i] = -1;
break;
}
}
}
#else
close(socket);
#endif
}
int CSocket::InitIOMultiplex(int socket)
{
if (socket <= 0)
{
printf("InitIOMultiplex faild: First createsocket\n");
return -1;
}
#ifdef WIN32
m_IOMultiplexFd = socket;
m_fd_list = (int *)calloc (MAX_FD_NUM, sizeof(int));
for (int i = 0; i < MAX_FD_NUM; i++)
{
m_fd_list[i] = -1;
}
return 1;
#else
return CreateEpoll(socket);
#endif
}
int CSocket::UinitIOMultiplex()
{
#ifdef WIN32
if (m_fd_list)
{
free(m_fd_list);
m_fd_list = NULL;
}
return 1;
#else
return DestroyEpoll();
#endif
}
int CSocket::AddIOMultiplex(int socket,int epoll_modo)
{
#ifdef WIN32
for (int i = 0 ; i< MAX_FD_NUM ;i++)
{
if (m_fd_list[i] == -1) {
m_fd_list[i] = socket;
break;
}
}
if (m_IOMultiplexFd < socket) {
m_IOMultiplexFd = socket;
}
return 1;
#else
if (epoll_modo == 0)
{
return AddEpoll_LT(socket);
}
else
{
return AddEpoll_ET(socket);
}
#endif
}
int CSocket::UseIOMultiplex(int timeout)
{
int ret = 0;
#ifdef WIN32
ret = Select(timeout);
if ((ret != -1) && (ret != 0))
{
return MAX_FD_NUM;
}
#else
ret = WaitEpoll(timeout);
#endif
return ret;
}
int CSocket::IsNewConnect(int socket,int eventlabel)
{
#ifdef WIN32
return FD_ISSET(socket,&m_readFdset);
#else
return m_epl_events[eventlabel].data.fd == socket;
#endif
}
int CSocket::IsReadEvent(int label)
{
int ret = 0;
#ifdef WIN32
if (m_fd_list[label] == -1)
{
return ret;
}
ret = FD_ISSET(m_fd_list[label], &m_readFdset);
#else
ret = m_epl_events[label].events & EPOLLIN;
#endif
return ret;
}
int CSocket::IsWriteEvent(int label)
{
int ret = 0;
#ifdef WIN32
if (m_fd_list[label] == -1)
{
return ret;
}
ret = FD_ISSET(m_fd_list[label], &m_writeFdset);
#else
ret = m_epl_events[label].events & EPOLLOUT;
#endif
return ret;
}
void CSocket::DestoryReadEvent(int label)
{
#ifdef WIN32
FD_CLR(m_fd_list[label],&m_readFdset);
#else
#endif
}
void CSocket::DestoryWriteEvent(int label)
{
#ifdef WIN32
FD_CLR(m_fd_list[label],&m_writeFdset);
#else
#endif
}
int CSocket::IsCloseEvent(int label,int recvsize)
{
#ifdef WIN32
if((recvsize == 0)||(recvsize == -1))
{
DestoryReadEvent(label);
DestoryWriteEvent(label);
return 1;
}
return 0;
#else
if((recvsize == 0)||(recvsize == -1))
{
return 1;
}
return 0;
#endif
}
int CSocket::GetFdListOrEvents(int label)
{
#ifdef WIN32
return m_fd_list[label];
#else
return m_epl_events[label].data.fd;
#endif
}
char * CSocket::GetLocalIp(int socket)
{
struct sockaddr_in ConnectAddr;
socklen_t sin_size = sizeof(struct sockaddr_in);
if(!getsockname(socket, (struct sockaddr *)&ConnectAddr, &sin_size))
{
return inet_ntoa(ConnectAddr.sin_addr);
}
return NULL;
}
int CSocket::GetLocalPort(int socket)
{
struct sockaddr_in ConnectAddr;
socklen_t sin_size = sizeof(struct sockaddr_in);
if(!getsockname(socket, (struct sockaddr *)&ConnectAddr, &sin_size))
{
return ntohs(ConnectAddr.sin_port);
}
return 0;
}<strong>
</strong>