libiop通讯流程和api讲解

上一篇讲到了libiop基本结构,这次根据libiop提供的test跟踪下消息和运行流程

void echo_server_test()
{
    int keepalive_timeout = 60;
    iop_base_t *base = iop_base_new(10240);
    printf("create a new iop_base_t object.\n");
    iop_add_tcp_server(base,"0.0.0.0",7777,
        my_echo_parser,my_echo_processor,
        my_echo_on_connect,my_echo_on_destroy,my_echo_on_error,
        keepalive_timeout);
    printf("create a new tcp server on port 7777.\n");
    printf("start iop run loop.\n");
    iop_run(base);

}

echo_server_test 函数内部添加了一个tcpserver,将函数一层一层展开

展开iop_add_tcp_server

int iop_add_tcp_server(iop_base_t *base, const char *host, unsigned short port,
                       iop_parser parser, iop_processor processor,
                       iop_cb on_connect,iop_cb on_destroy,
                       iop_err_cb on_error, int keepalive_timeout)
{

    iop_tcp_server_arg_t *sarg = 0;
    io_handle_t h = INVALID_HANDLE;
    sarg = (iop_tcp_server_arg_t *)malloc(sizeof(iop_tcp_server_arg_t));
    if(!sarg){return -1;}
    memset(sarg,0,sizeof(iop_tcp_server_arg_t));
    h = iop_tcp_server(host,port);
    if(h == INVALID_HANDLE){return -1;}
    sarg = (iop_tcp_server_arg_t *)malloc(sizeof(struct tag_iop_tcp_server_arg_t));
    if(!sarg)
    {
        iop_close_handle(h);
        return -1;
    }
#ifdef WIN32
    strcpy_s(sarg->host,sizeof(sarg->host)-1, host);
#else
    strcpy(sarg->host,host);
#endif
    sarg->port = port;
    sarg->timeout = keepalive_timeout;
    sarg->on_connect = on_connect;
    sarg->on_destroy = on_destroy;
    sarg->on_error = on_error;
    sarg->parser = parser;
    sarg->processor = processor;
    _list_add_before(base->tcp_protocol_list_head, _list_node_new(sarg));
    return iop_add(base,h,EV_TYPE_READ,_iop_tcp_server_cb,(void *)sarg,-1);
}

解读iop_add_tcp_server

函数参数iop_base_t 是iop基本事件结构,前面有说过,

struct tag_iop_base_t
{
    iop_t *iops;        /*所有iop*/
    int maxio;            /*最大并发io数,包括定时器在内*/
    int maxbuf;            /*单个发送或接收缓存的最大值*/
    int free_list_head;    /*可用iop列表*/
    int free_list_tail; /*最后一个可用iop*/
    int io_list_head;    /*已用io类型的iop列表*/
    int timer_list_head;    /*已用timer类型的iop列表*/
    int connect_list_head;  /*异步连接的iop列表*/
    volatile int exit_flag;    /*退出标志*/

    int dispatch_interval;        /*高度的间隔时间*/
    iop_op_t op_imp;           /*事件模型的内部实现*/
    void *model_data;         /*事件模型特定的数据*/

    iop_time_t cur_time;        /*当前调度时间*/
    iop_time_t last_time;        /*上次调度时间*/
    iop_time_t last_keepalive_time; /*上次检查keepalive的时间*/

    _list_node_t * tcp_protocol_list_head;    /*use for advance tcp server model.*/
};

第二个参数host是主机地址,port是端口号,剩下的 parser为解析函数指针

processor为处理函数指针,on_connect为连接的回调函数指针,on_destroy,为

销毁功能函数指针,on_error为错误情况下函数指针,keepalive_timeout为超时时间

这几个函数指针的类型如下,基本都类似的

/*tcp连接事件回调函数*/
typedef void (*iop_cb)(iop_base_t *,int,void *);
void iop_default_cb(iop_base_t *base, int id, void *arg);

/*
*   返回-1代表要删除事件,返回0代表不删除
*/
typedef int (*iop_err_cb)(iop_base_t *,int,int,void *);

int iop_default_err_cb(iop_base_t *base, int id, int err, void *arg);

/************************************
*协议解析器,
* parameters:
*    char *buf:数据
*    int len:数据长度
*return:
*    返回0代表还要收更多数据以代解析,-1代表协议错误,>0代表解析成功一个数据包
***********************************/
typedef int (*iop_parser)(char *, int);
int iop_default_parser(char *buf, int len);

/*
*数据处理器
*parameters:
*    base:iop_base_t 指针
*    id:iop对象的id
*    buf:数据包起始点
*    len:数据包长度
*    arg:自带的参数
*return:
    -1: 代表要关闭连接,0代表正常
*/
typedef int (*iop_processor)(iop_base_t *,int,char *,int,void *);

回到iop_add_tcp_server函数里

开辟arg的空间

sarg = (iop_tcp_server_arg_t *)malloc(sizeof(iop_tcp_server_arg_t));

绑定端口和地址

h = iop_tcp_server(host,port);
对arg赋值
sarg->port = port;
    sarg->timeout = keepalive_timeout;
    sarg->on_connect = on_connect;
    sarg->on_destroy = on_destroy;
    sarg->on_error = on_error;
    sarg->parser = parser;
    sarg->processor = processor;

下面这句代码最重要

iop_add(base,h,EV_TYPE_READ,_iop_tcp_server_cb,(void *)sarg,-1);

这句代码将socket h绑定了一个读事件,当有读事件就绪时会触发iop_tcp_server_cb这个函数。

如何将h和iop_tcp_server_cb绑定的,展开iop_add

int iop_add(iop_base_t *base,io_handle_t handle,unsigned int events,iop_event_cb evcb,void *arg,int timeout)
{
    int r = 0;
    iop_t *iop = _iop_base_get_free_node(base);
    if(!iop){return -1;}
    iop->handle = handle;
    iop->events = events;
    iop->timeout = timeout;
    iop->evcb = evcb;
    iop->last_dispatch_time = base->cur_time;
    iop->arg = arg;
    //io 事件
    if(handle != INVALID_HANDLE)
    {
        //LOG_DBG("iop_add io, id=%d.\n", iop->id);
        iop->prev = -1;
        iop->next = base->io_list_head;
        base->io_list_head = iop->id;
        iop->iop_type = IOP_TYPE_IO;
            iop_set_nonblock(handle);
        r = (*(base->op_imp.base_add))(base, iop->id, handle, events);
        if(r != 0)
        {
            iop_del(base,iop->id);
            return -1;
        }
    }
    else
    {
        /*timer*/
        //LOG_DBG("iop_add timer, id=%d.\n", iop->id);
        iop->prev = -1;
        iop->next = base->timer_list_head;
        base->timer_list_head = iop->id;
        iop->iop_type = IOP_TYPE_TIMER;
    }
    return iop->id;
}

iop_add 形参不做解释,其中形参evcb也是函数指针

/*事件回调函数,返回-1代表要删除对象,返回0代表正常*/
typedef int (*iop_event_cb)(iop_base_t *,int,unsigned int,void *);

在iop_add内部完成iop回调函数evcb的绑定和基本参数赋值

然后判断是io事件还是定时器事件

对于IO事件,要通知网络层(epoll,select等不同模型)进行绑定,

调用base中op_imp成员的base_add函数指针完成绑定。

r = (*(base->op_imp.base_add))(base, iop->id, handle, events);

之所以能调用是因为之前op_imp.base_add被赋值了。

回到

void echo_server_test()
{
    int keepalive_timeout = 60;
    iop_base_t *base = iop_base_new(10240);
        ...
}
iop_base_t* iop_base_new(int maxio)
{
#ifdef _HAVE_EVENT_PORTS_
#endif
#ifdef _HAVE_WORKING_KQUEUE_
#endif
#ifdef _HAVE_EPOLL_
    return iop_base_new_special(maxio,"epoll");
#endif
#ifdef _HAVE_DEVPOLL_
#endif
#ifdef _HAVE_POLL_
    return iop_base_new_special(maxio,"poll");
#endif
#ifdef _HAVE_SELECT_
    return iop_base_new_special(maxio,"select");
#endif
    return NULL;
}

一层一层看

iop_base_t* iop_base_new_special(int maxio,const char *model)
{
    int r = -1;
    iop_base_t *base = NULL;
    if(strcmp(model,"epoll")==0)
    {
        base = _iop_base_new(maxio);
        if(base)
        {
            r = iop_init_epoll(base, maxio);
        }
    }
        ......
}
int iop_init_epoll(void *iop_base, int maxev)
{
    ...
    //模型内部实现,不同模型不同的函数指针和名字
    iop_op->name = "epoll";
    iop_op->base_free = epoll_free;
    iop_op->base_dispatch = epoll_dispatch;
    iop_op->base_add = epoll_add;
    iop_op->base_del = epoll_del;
    iop_op->base_mod = epoll_mod;

    //1024 is not the max events limit.
    //创建epoll表句柄
   ...
    //iop_epoll_data_t类型的数据存在base的model_data里
    //方便回调
    base->model_data = iop_data;

    return 0;
}

上面就是在new函数里实现的一层一层函数指针的绑定,所以之后才可以调用对应的函数指针。

在iop_add 函数绑定成功后,整个iop_add_tcp_server流程走完了。

我们下一步看看如何派发消息

void echo_server_test()
{
    int keepalive_timeout = 60;
    iop_base_t *base = iop_base_new(10240);
    ...
    iop_add_tcp_server(...,...);
    ...
    iop_run(base);

}

iop_run函数完成消息轮询和派发

void iop_run(iop_base_t *base)
{
    while(base->exit_flag == 0)
    {
        iop_dispatch(base);
    }
    iop_base_free(base);
}

iop_dispatch消息派发函数

iop_base_free iop_base释放

int iop_dispatch(iop_base_t *base)
{
    int cur_id = 0;
    int next_id = 0;
    int r = 0;
    iop_t *iop = (iop_t *)0;
    //调用不同模型的函数指针实现消息派发
    dispatch_imp_cb dispatch_cb = base->op_imp.base_dispatch;
    r = (*dispatch_cb)(base,base->dispatch_interval);
    if( r == -1)
    {
        return -1;
    }

    //检测定时器时间,定时调用
    if(base->cur_time > base->last_time)
    {
        //check timers...
        cur_id = base->timer_list_head;
        while(cur_id != -1)
        {
            iop = base->iops + cur_id;
            next_id = iop->next;
            if(base->cur_time > iop->last_dispatch_time + iop->timeout)
            {
                IOP_CB(base,iop,EV_TYPE_TIMER);
            }
            cur_id = next_id;
        }

        /*********check for connect list.*********************/

        cur_id = base->connect_list_head;
        while(cur_id != -1)
        {
            iop = base->iops + cur_id;
            next_id = iop->next;
            if(base->cur_time > iop->last_dispatch_time + iop->timeout)
            {
                IOP_CB(base,iop,EV_TYPE_TIMEOUT);
            }
            cur_id = next_id;
        }

        //超时检测
        /*********clear keepalive, 60 seconds per times***********************/
        if(base->cur_time > base->last_keepalive_time+60)
        {
            base->last_keepalive_time = base->cur_time;
            cur_id = base->io_list_head;
            while(cur_id != -1)
            {
                iop = base->iops+cur_id;
                next_id = iop->next;
                if(iop->timeout > 0 && iop->last_dispatch_time + iop->timeout < base->cur_time)
                {
                    IOP_CB(base,iop,EV_TYPE_TIMEOUT);
                }
                cur_id = next_id;
            }
        }

        base->last_time = base->cur_time;
    }

    return r;
}

这句代码是消息派发的关键

  //调用不同模型的函数指针实现消息派发
    dispatch_imp_cb dispatch_cb = base->op_imp.base_dispatch;
    r = (*dispatch_cb)(base,base->dispatch_interval);

base->op_imp.base_dispatch之前在epoll_init里完成过初始化

其实调用的是epoll的dispatch

static int epoll_dispatch(iop_base_t * base, int timeout)
{
    int i;
    int id = 0;
    iop_t *iop = NULL;
    //iop_base中取出模型数据
    iop_epoll_data_t *iop_data = (iop_epoll_data_t *)(base->model_data);
    int n = 0;
    do{
        n = epoll_wait(iop_data->epfd, iop_data->events, iop_data->nevents, timeout);
    }while((n < 0) && (errno == EINTR));
    base->cur_time = time(NULL);
    for(i = 0; i < n; i++)
    {
        //取出iop的id
        id = (int)((iop_data->events)[i].data.u32);
        if(id >= 0 && id < base->maxio)
        {
            iop = (base->iops)+id;
            //这个宏是调用绑定在iop的事件回调函数(accept,read,write等)
            IOP_CB(base,iop,from_epoll_events(iop_data->events[i].events));
        }
    }
    return n;
}

这句话完成绑定在iop的回调函数调用,基本功能就是accept,read或者write等

//这个宏是调用绑定在iop的事件回调函数(accept,read,write等)
            IOP_CB(base,iop,from_epoll_events(iop_data->events[i].events));

这样就是整个libiop通讯流程和事件驱动机制

我的公众号

时间: 2024-11-08 23:34:02

libiop通讯流程和api讲解的相关文章

smartJS 0.1 API 讲解 - FlowController

本篇介绍0.1版中最后一个特性,FlowController:同时也对第一版总结一下,因为近两年全部都是在搞前端,都是做一些js框架类的东西,也做了不少有意思的功能,做smartjs对我来说一个是对自己做一个总结,也希望分享一些东西给大家借鉴. 而对smartjs的来说,整体思想都并不是为了实现什么功能.特效和内容.而是希望体现一些前端编程模式或者思想上的内容,这次的0.1版本中,主要体现了一些面向切面编程.非侵入式编程.异步编程,生命周期控制的一些思想.包括后续的oop的内容,基于策略的数据管

smartJS 0.1 API 讲解 - PromiseEvent

上篇简单的介绍smartjs了一些通用方法的api.这篇介绍基础的PromiseEvent(这个名字一直没想好,以前准备用callbacks的,但避免与jquery混淆,st的命名空间可以直接挂到$上) PromiseEvent 基于事件和promise的回调管理,类似于jquery的callbacks,但具有结果传递,优先级,事件参数,promise控制等功能 接口方法 var events = st.promiseEvent(mode); events.add(name,function(e

【jquery】 API讲解 内部培训资料

资料在百度云盘 一.jquery  API讲解 1.jquery  api如何使用 jquery  api http://www.hemin.cn/jq/ 2.常用api讲解 选择器: 通过$()获取jquery对象的常用方式: #div1          根据元素id获取 .red             根据class获取 div               根据标签名字获取 #div1 ul li input  混合模式 li.red 查找带red这个class的li input[typ

PHP与API讲解(一)

了解API: 在使用与创建自己的API之前我们需要先了解什么是API! API代表应用程序编程接口,而接口指的是一个特定的服务.一个应用程序或者其他程序的公共模块. 理解SOA(面向服务的架构):SOA是PHP应用程序日益普及的方法,它是基于一个服务层的系统,提供系统所需要的所有功能,但这个功能提供的是应用层,并未连接到表现层.这样,多种系统就可以使用这个相同的模块化.可重复使用的功能了.(备注:在学习API的时候看过的两本有关API的书籍都有提到这个SOA,但是由于自己还是个菜鸟所以对这个理解

HTML5 文件拖放API讲解

本章向大家讲解一下HTML 5中文件API与拖放API的使用方法.HTML5的文件API,可以在浏览器中直接显示客户端文件的信息或文件中的内容,而通过拖放API,可以直接将位于客户端中的文件拖动到浏览器中,也可以单独拖动页面中的元素或者元素中的内容. HTML5拖拽文件预览效果图: 在线演示 以前,我们使用file控件,单击上传按钮后选择计算机中的文件.在HTML5中,我们可以先将计算机中的文件直接拖动到浏览器中进行预览,确定文件是我们所需要的,然后单击上传按钮将该文件上传到服务器端. 我们使用

DIOCP网络通讯流程

DIOCP 运作核心探密 原文连接: http://blog.qdac.cc/?p=2362 原作者: BB 天地弦的DIOCP早已经广为人知了,有很多的同学都用上了它,甚至各种变异.修改版本也出了不少.我最近也在学习DIOCP,打算将它用于自己的服务端,今天让我们来一起探密它(DIOCP)的运作核心吧. DIOCP作为对Windows的IOCP完成端口封装,拥有了很高的性能,经过对ECHO示例的测试,它能轻松应对几万连接和并发.网络通讯一般分为6大阶段:请求连接.接受连接.接收数据.处理数据.

Activity官方API讲解

一个活动是一个单一的,集中的东西,用户可以做的.几乎所有的活动与用户交互,因此Activity类需要创建为您窗口,在其中您可以将您的UI与照顾 的setContentView(查看).而活动经常呈现给用户作为全屏窗口,它们也可以以其他方式使用:作为浮动窗口(经由与主题windowIsFloating组)或嵌入另一活性(使用的内部的ActivityGroup).有两种方法几乎所有活动的子类将实施: 的onCreate(捆绑)是你初始化你的活动.最重要的是,在这里你会通常所说的setContentV

配置文件详解和核心api讲解

一.配置文件详解 1.映射文件详解 1.映射配置文件的位置和名称没有限制. -建议:位置:和实体类放在统一目录下.  名称:实体类名称.hbm.xml.    2.在映射配置文件中,标签内的name属性的值要和实体内的属性对应. (1)class标签内的name的值为实体类的全路径. (2)property标签内的name的值为实体类的属性. (3)id标签内的name的值为实体类的属性. (4)id和property标签内的column属性可以不写. (5)id和property标签内有一个t

葫芦娃关于快速幂流程的详细讲解

快速幂的流程大概是这样的,维护一个等式a^b=x^y*z. 比如说现在求3的10次方 第一步:3^10=3^10*1 第二步:3^10*1=9^5*1 第三步:9^5*1=9^4*9 第四步:9^4*9=81^2*9 第五步:81^2*9=6561^1*9 第六步:6561^1*9=1^1*59049 所以3^10=59049 上面总共进行了五次乘法运算,相比较朴素的十次来说,要好一些 经过上面的演算,抽象成自然语言大概是这样: 初始化,x=a,y=b,z=1, 每一次,首先如果y不大于0则退出