libiop通讯流程和api讲解
上一篇讲到了libiop基本结构,这次根据libiop提供的test跟踪下消息和运行流程
void echo_server_test()
{
int keepalive_timeout = ;
iop_base_t *base = iop_base_new();
printf("create a new iop_base_t object.\n");
iop_add_tcp_server(base,"0.0.0.0",,
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 = ;
io_handle_t h = INVALID_HANDLE;
sarg = (iop_tcp_server_arg_t *)malloc(sizeof(iop_tcp_server_arg_t));
if(!sarg){return -;}
memset(sarg,,sizeof(iop_tcp_server_arg_t));
h = iop_tcp_server(host,port);
if(h == INVALID_HANDLE){return -;}
sarg = (iop_tcp_server_arg_t *)malloc(sizeof(struct tag_iop_tcp_server_arg_t));
if(!sarg)
{
iop_close_handle(h);
return -;
}
#ifdef WIN32
strcpy_s(sarg->host,sizeof(sarg->host)-, 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,-);
}
解读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,-);
这句代码将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 = ;
iop_t *iop = _iop_base_get_free_node(base);
if(!iop){return -;}
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 = -;
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 != )
{
iop_del(base,iop->id);
return -;
}
}
else
{
/*timer*/
//LOG_DBG("iop_add timer, id=%d.\n", iop->id);
iop->prev = -;
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 = ;
iop_base_t *base = iop_base_new();
...
}
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 = -;
iop_base_t *base = NULL;
if(strcmp(model,"epoll")==)
{
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 ;
}
上面就是在new函数里实现的一层一层函数指针的绑定,所以之后才可以调用对应的函数指针。
在iop_add 函数绑定成功后,整个iop_add_tcp_server流程走完了。
我们下一步看看如何派发消息
void echo_server_test()
{
int keepalive_timeout = ;
iop_base_t *base = iop_base_new();
...
iop_add_tcp_server(...,...);
...
iop_run(base); }
iop_run函数完成消息轮询和派发
void iop_run(iop_base_t *base)
{
while(base->exit_flag == )
{
iop_dispatch(base);
}
iop_base_free(base);
}
iop_dispatch消息派发函数
iop_base_free iop_base释放
int iop_dispatch(iop_base_t *base)
{
int cur_id = ;
int next_id = ;
int r = ;
iop_t *iop = (iop_t *);
//调用不同模型的函数指针实现消息派发
dispatch_imp_cb dispatch_cb = base->op_imp.base_dispatch;
r = (*dispatch_cb)(base,base->dispatch_interval);
if( r == -)
{
return -;
} //检测定时器时间,定时调用
if(base->cur_time > base->last_time)
{
//check timers...
cur_id = base->timer_list_head;
while(cur_id != -)
{
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 != -)
{
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+)
{
base->last_keepalive_time = base->cur_time;
cur_id = base->io_list_head;
while(cur_id != -)
{
iop = base->iops+cur_id;
next_id = iop->next;
if(iop->timeout > && 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 = ;
iop_t *iop = NULL;
//iop_base中取出模型数据
iop_epoll_data_t *iop_data = (iop_epoll_data_t *)(base->model_data);
int n = ;
do{
n = epoll_wait(iop_data->epfd, iop_data->events, iop_data->nevents, timeout);
}while((n < ) && (errno == EINTR));
base->cur_time = time(NULL);
for(i = ; i < n; i++)
{
//取出iop的id
id = (int)((iop_data->events)[i].data.u32);
if(id >= && 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通讯流程和事件驱动机制
我的公众号

libiop通讯流程和api讲解的更多相关文章
- 【jquery】  API讲解 内部培训资料
		
资料在百度云盘 一.jquery API讲解 1.jquery api如何使用 jquery api http://www.hemin.cn/jq/ 2.常用api讲解 选择器: 通过$()获取 ...
 - zigbee学习:示例程序SampleApp中通讯流程
		
zigbee学习:示例程序SampleApp中通讯流程 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 参考链接: http://wjf88223.bl ...
 - IdentityServer4【QuickStart】之使用ClientCredentials流程保护API
		
使用ClientCredentials流程保护API 这个示例展示了使用IdentityServer中保护APIs的最基本的场景. 在这个场景中我们会定义一个API和一个想要访问它的客户端.客户端会在 ...
 - 项目实践之工作流引擎基本文档!Activiti工作流框架中流程引擎API和服务详解
		
流程引擎的API和服务 流程引擎API(ProcessEngine API)是与Activiti打交道的最常用方式 Activiti从ProcessEngine开始.在ProcessEngine中,可 ...
 - WCF开发的流程-服务端和客户端之间的通讯(内含demo讲解)
		
讲解技术之前,恳请博友让我说几句废话.今天是我第一在博客园发布属于自己原创的博文(如有雷同,那是绝对不可能的事,嘿嘿).之前一直是拜读各位博友的大作,受益匪浅的我在这对博友们说声谢谢,谢谢你们的共享! ...
 - smartJS 0.1 API 讲解 - FlowController
		
本篇介绍0.1版中最后一个特性,FlowController:同时也对第一版总结一下,因为近两年全部都是在搞前端,都是做一些js框架类的东西,也做了不少有意思的功能,做smartjs对我来说一个是对自 ...
 - PHP与API讲解(一)
		
了解API: 在使用与创建自己的API之前我们需要先了解什么是API! API代表应用程序编程接口,而接口指的是一个特定的服务.一个应用程序或者其他程序的公共模块. 理解SOA(面向服务的架构):SO ...
 - DIOCP网络通讯流程
		
DIOCP 运作核心探密 原文连接: http://blog.qdac.cc/?p=2362 原作者: BB 天地弦的DIOCP早已经广为人知了,有很多的同学都用上了它,甚至各种变异.修改版本也出 ...
 - Hibernate第二篇【API讲解、执行流程图】
		
前言 从上一篇中已经大致介绍了Hibernate并且有了一个快速入门案例的基础了,-.本博文主要讲解Hibernate API 我们看看快速入门案例的代码用到了什么对象吧,然后一个一个讲解 publi ...
 
随机推荐
- 2019CSUST集训队选拔赛题解(一)
			
来自ppq的毒瘤线段树 Sneakers Description 有一天喜欢买鞋的ppq和小伙伴来到了某一家球鞋店,球鞋店有n种球鞋,价格分别为ai,ppq在鞋店兜兜转转,发现鞋店老板会偶尔将某段 ...
 - presto 配置mysql.properties异常Database (catalog) must not be specified in JDBC URL for MySQL connector
			
在presto 0.210 以后配置mysql.properties的时候,对于jdbc-url属性配置后面要加上对应要链接的database connection-url=jdbc:mysql:// ...
 - mysql常用语句入门整理
			
这篇属于小白入门级别,如果你已经高手可以直接跳过 1.运行数据库mysqld.exe,客户端直接mysql -uroot(root是默认用户名) -p 2 showdatabases,showtabl ...
 - ES6中Class的继承关系
			
es5实现中,每个对象都有__proto__属性(也就是关系图中[[prototype]]属性),指向对应的构造函数的prototype.Class 作为构造函数的语法糖,同时有prototype属性 ...
 - redis 学习记录
			
http://www.yiibai.com/redis/redis_quick_guide.html Redis 是一款依据BSD开源协议发行的高性能Key-Value存储系统(cache and s ...
 - Alpha阶段第2周/共2周 Scrum立会报告+燃尽图 02
			
此次作业要求参见 [https://edu.cnblogs.com/campus/nenu/2018fall/homework/2285] Scrum master:祁玉 一.小组介绍 组长:王一可 ...
 - Thunder团队——bug修正
			
团队:欢迎来怼 发现的问题: 1.首先用户通过爱阅APP内部的网址跳转到各大电子书网站时,需要额外启动手机自身浏览器:就以豆瓣网为例,阅读豆瓣网上的一些书籍,是跳转到手机自带浏览器的,APP内部提供的 ...
 - 软件工程-东北师大站-第四次作业PSP
			
1.本周PSP 2.本周进度条 3.本周累计进度图 代码累计折线图 博文字数累计折线图 4.本周PSP饼状图
 - 把字符串"3,1,2,4"以","分割拆分为数组,数组元素并按从小到大的顺序排列
			
package com.wangcf; /** * 把字符串"3,1,2,4"以","分割拆分为数组,数组元素并按从小到大的顺序排列 * @author fan ...
 - 【IdentityServer4文档】- 使用密码保护 API
			
使用密码保护 API OAuth 2.0 协议允许资源拥有者给客户端密码授权:客户端向令牌服务发送用户密码,以获取代表该用户的访问令牌. 该规范建议仅将“资源所有者密码授予”用于“可信”(或旧版)应用 ...