IO复用 -- kqueue

IO复用 – kqueue

标签(空格分隔): IO复用


摘要

kqueue 是 FreeBSD 上的一种的多路复用机制。它是针对传统的 select/poll 处理大量的文件描述符性能较低效而开发出来的。注册一堆描述符到 kqueue 以后,当其中的描述符状态发生变化时, kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了。

kqueue 支持多种类型的文件描述符,包括 socket 、信号、定时器、 AIO 、 VNODE 、 PIPE 。本文重点讨论 kqueue 如何控制 socket 描述符。其中 kqueue 对 AIO , POSIX 的异步 IO系列的支持,是异步行为完成通知机制之一。另外两种常见的机制是异步信号和线程例程。用 kqueue 的明显好处是完成事件的处理线程可以灵活地指定。

kqueue API

  1. kqueue
    原形:
int kqueue(void);

功能:生成内核事件队列,返回队列描述符,其他api操作都是基于此描述符。如:注册,反注册,获取通知等

  1. 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 中就保存可用事件集合。

  1. 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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值