IOMultiplex (一)- select/epoll

IOMultiplex (一)- select/epoll

一:epoll和select区别

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>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值