IO复用 – kqueue
标签(空格分隔): IO复用
摘要
kqueue 是 FreeBSD 上的一种的多路复用机制。它是针对传统的 select/poll 处理大量的文件描述符性能较低效而开发出来的。注册一堆描述符到 kqueue 以后,当其中的描述符状态发生变化时, kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了。
kqueue 支持多种类型的文件描述符,包括 socket 、信号、定时器、 AIO 、 VNODE 、 PIPE 。本文重点讨论 kqueue 如何控制 socket 描述符。其中 kqueue 对 AIO , POSIX 的异步 IO系列的支持,是异步行为完成通知机制之一。另外两种常见的机制是异步信号和线程例程。用 kqueue 的明显好处是完成事件的处理线程可以灵活地指定。
kqueue API
- kqueue
原形:
int kqueue(void);
功能:生成内核事件队列,返回队列描述符,其他api操作都是基于此描述符。如:注册,反注册,获取通知等
- kevent
原形:
int kevent(int kq, /*输入参数:kqueue 队列描述符*/
const struct kevent* changelist, /*输入参数: 注册或者反注册的事件数组*/
int nchanges, /*输入参数: 注册或者反注册事件数目*/
struct kevent* eventlist, /*输出参数: 已触发事件数组*/
int neventlist, /*输出参数: 已触发事件个数*/
const struct timespec* timeout); /*等待时间: 0立即返回,NULL 一直等待,知道通知事件发生,否者:指定等待时间*/
/*返回值: 触发事件数目*/
功能:
kevent() 提供三个主要的行为功能。在下面小节中将会用到这两个主要功能。
(1) 注册 / 撤销 注意 kevent() 中的 neventlist 这个输入参数,当将其设为 0,且传入合法的 changelist 和 nchangelist,就会将 changelist 中的事件注册到 kqueue 中。
当关闭某文件描述符时,与之关联的事件会被自动地从 kqueue 移除。
(2) 启用禁用 / 禁止过滤器事件 通过 flags EV_ENABLE 和 EV_DISABLE 使过滤器事件有效或无效。这个功能在利用 EVFILT_WRITE 发送数据时非常有用。
(3) 获取已触发事件 将 nchangelist 设置成 0,当然要传入其它合法的参数,当 kevent 非错误和超时返回时,在 eventlist 和 neventlist 中就保存可用事件集合。
- struct kevent
struct kevent{
uintptr_t ident; /*事件描述符,一般为设备文件描述符*/
short filter; /*事件过滤器*/
u_short flags; /*行为标示*/
u_int fflages; /*过滤标示值*/
intptr_t data; /*过滤器数据*/
void* udata; /*应用透传数据*/
};
ident
事件的 id,实际应用中,一般设置为文件描述符。
filter
可以将 kqueue filter 看作事件。内核检测 ident 上注册的 filter 的状态,状态发生了变化,就通知应用程序。kqueue 定义了较多的 filter,本文只介绍 Socket 读写相关的 filter。
EVFILT_READ
TCP 监听 socket,如果在完成的连接队列 ( 已收三次握手最后一个 ACK) 中有数据,此事件将被通知。收到该通知的应用一般调用 accept(),且可通过 data 获得完成队列的节点个数。 流或数据报 socket,当协议栈的 socket 层接收缓冲区有数据时,该事件会被通知,并且 data 被设置成可读数据的字节数。
EVFILT_WRIT
当 socket 层的写入缓冲区可写入时,该事件将被通知;data 指示目前缓冲区有多少字节空闲空间。
flags
EV_ADD
指示加入事件到 kqueue。
EV_DELETE
指示将传入的事件从 kqueue 中移除。
EV_ENABLE
过滤器事件可用,注册一个事件时,默认是可用的。
EV_DISABLE
过滤器事件不可用,当内部描述可读或可写时,将不通知应用程序。第 5 小节有这个 flag 的用法介绍。
EV_ERROR
一个输出参数,当 changelist 中对应的描述符处理出错时,将输出这个 flag。应用程序要判断这个 flag,否则可能出现 kevent 不断地提示某个描述符出错,却没将这个描述符从 kq 中清除。处理 EV_ERROR 类似下面的代码: if (events[i].flags & EV_ERROR) close(events[i].ident); fflags 过滤器相关的一个输入输出类型标识,有时候和 data 结合使用。
data
过滤器相关的数据值,请看 EVFILT_READ 和 EVFILT_WRITE 描述。
udata
应用自定义数据,注册的时候传给 kernel,kernel 不会改变此数据,当有事件通知时,此数据会跟着返回给应用。
EV_SET
EV_SET(&kev, ident, filter, flags, fflags, data, udata);
struct kevent 的初始化的辅助操作。
demo TCP echo服务器
#ifndef __NETWORK_H__
#define __NETWORK_H__
#include <string>
#include <map>
#define RaiseException(szInfo)\
do\
{\
perror(__FUNCTION__);\
std::stringstream ss;\
ss << "["<<__FILE__<<" "<<__FUNCTION__<<" "<<__LINE__<<"] "<<szInfo;\
std::string szTemp = ss.str();\
throw std::runtime_error(szTemp.c_str());\
} while (0)\
class CNetwork
{
typedef std::map<int,CSocketItem*> SocketMap;
typedef std::map<int,CSocketItem*>::iterator SocketIndex;
public:
CNetwork();
CNetwork(uint16_t port,std::string szIp);
~CNetwork();
enum{
MAX_EVENT = 2000
};
public:
void SetParameter(uint16_t port,std::string szIp = "");
bool InitNetWork();
bool Run();
private:
bool AddEvent(int fd,int fileter);
bool DeleteEvent(int fd, int filter);
bool EableEvent(int fd, int fileter);
bool DisbleEvent(int fd, int filter);
private:
void SendData(int fd,void* pData, int nSize);
private:
bool AcceptHandle(int listenfd, int nsize);
bool ReadHandle(int fd, int nsize);
bool WriteHandle(int fd, void* pData, int nsize);
private:
int m_kq; //kqueue 描述符
int m_port; //端口
std::string m_szIp;
int m_listenfd;
char m_szRecvBuff[1024];
int m_nRecvLen;
};
#endif
#include "Network.h"
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sstream>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
CNetwork::CNetwork()
{
m_kq = kqueue();
}
CNetwork::~CNetwork()
{
}
CNetwork::CNetwork(uint16_t port, std::string szIp)
{
SetParameter(port, szIp);
}
void CNetwork::SetParameter(uint16_t port, std::string szIp)
{
m_kq = kqueue();
m_port = port;
m_szIp = szIp;
}
bool CNetwork::InitNetWork()
{
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int res = 0;
if (-1 == fd)
RaiseException("socket create:");
m_listenfd = fd;
sockaddr_in ServerAddr = {0};
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(m_port);
if (!m_szIp.empty())
ServerAddr.sin_addr.s_addr = inet_addr(m_szIp.c_str());
else
ServerAddr.sin_addr.s_addr = INADDR_ANY;
res = ::bind(fd, (sockaddr *)&ServerAddr, sizeof(ServerAddr));
if (-1 == res)
RaiseException("bind errer");
listen(fd, 5);
std::cout << "开始监听【" << inet_ntoa(ServerAddr.sin_addr) << " " << m_port << "】" << std::endl;
AddEvent(fd, EVFILT_READ|EVFILT_WRITE);
return true;
}
bool CNetwork::AddEvent(int fd, int fileter)
{
int res = 0;
struct kevent events[1];
EV_SET(&events[0], fd, fileter, EV_ADD, 0, 0, NULL);
res = kevent(m_kq, events, 1, NULL, 0, NULL);
if (res < 0)
RaiseException("AddEvent kevent");
return 0;
}
bool CNetwork::DeleteEvent(int fd, int filter)
{
int res = 0;
struct kevent events[1];
EV_SET(events, fd, filter, EV_DELETE, 0, 0, NULL);
res = kevent(m_kq, events, 1, NULL, 0, NULL);
if (res < 0)
RaiseException("DeleteEvent kevent");
return 0;
}
bool CNetwork::EableEvent(int fd, int fileter)
{
int res = 0;
struct kevent events[1];
EV_SET(events, fd, fileter, EV_ENABLE, 0, 0, NULL);
res = kevent(m_kq, events, 1, NULL, 0, NULL);
if (res < 0)
RaiseException("DeleteEvent kevent");
return true;
}
bool CNetwork::DisbleEvent(int fd, int fileter)
{
int res = 0;
struct kevent events[1];
EV_SET(events, fd, fileter, EV_DISABLE, 0, 0, NULL);
res = kevent(m_kq, events, 1, NULL, 0, NULL);
if (res < 0)
RaiseException("DeleteEvent kevent");
return true;
}
bool CNetwork::AcceptHandle(int listenfd, int nsize)
{
try
{
printf("%s - %d\n", __FUNCTION__, nsize);
for (int i = 0; i < nsize; i++)
{
sockaddr_in peerAddr;
socklen_t nLen = sizeof(peerAddr);
int clifd = accept(listenfd, (sockaddr *)&peerAddr, &nLen);
if (-1 == clifd)
continue;
std::cout << "收到连接" << std::endl;
AddEvent(clifd, EVFILT_READ);
AddEvent(clifd,EVFILT_WRITE);
DisbleEvent(clifd,EVFILT_WRITE);
}
}
catch(exception& e)
{
}
return true;
}
void CNetwork::SendData(int fd, void* pData, int nSize)
{
EableEvent(fd,EVFILT_WRITE);
}
bool CNetwork::ReadHandle(int fd, int nsize)
{
try
{
cout<<__FUNCTION__<<std::endl;
int res = recv(fd,m_szRecvBuff,nsize,0);
if(res < 0)
RaiseException("Recv data error");
else if(res == 0)
{
DeleteEvent(fd,EVFILT_READ);
close(fd);
return true;
}
SendData(fd,NULL,0);
m_nRecvLen = res;
cout<<m_szRecvBuff<<endl;
}
catch(std::exception& e)
{
cout<<e.what()<<endl;
}
return true;
}
bool CNetwork::WriteHandle(int fd, void *pData, int nsize)
{
try
{
cout<<__FUNCTION__<<" "<<nsize<<endl;
int res = send(fd,m_szRecvBuff,m_nRecvLen,0);
if(res < 0)
RaiseException("Recv data error");
DisbleEvent(fd, EVFILT_WRITE);
}
catch(std::exception& e)
{
cout<<e.what()<<endl;
}
return false;
}
bool CNetwork::Run()
{
InitNetWork();
struct kevent events[MAX_EVENT];
for (;;)
{
int res = kevent(m_kq, NULL, 0, events, MAX_EVENT, NULL);
if (res < 0)
{
std::cout << "kevent Error" << std::endl;
continue;
}
for (int i = 0; i < res; i++)
{
int fd = events[i].ident;
int nsize = events[i].data;
if (m_listenfd == fd)
{
AcceptHandle(fd, nsize);
}
else if (events[i].filter == EVFILT_READ)
{
cout << "nsize = " << nsize << endl;
ReadHandle(fd, nsize);
}
else if(events[i].filter == EVFILT_WRITE)
{
WriteHandle(fd,events[i].udata,nsize);
}
else
{
std::cout<<"error--"<<std::endl;
}
}
}
return true;
}