freeswitch mod_callcenter源码解析

文章详细介绍了FreeSWITCH模块mod_callcenter的加载过程,包括注册事件、回调函数、加载配置、启动座席分配线程以及注册APP和API接口。mod_callcenter_load函数执行时,创建并启动了一个每100ms查询members表以分配座席的线程。当找到合适的座席时,通过outbound_agent_thread_run进行连接操作,实现呼叫中心的功能。

mod_callcenter按照fs模块开发标准定义了加载,运行,关闭三个函数

SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_callcenter_shutdown);
SWITCH_MODULE_RUNTIME_FUNCTION(mod_callcenter_runtime);
SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load);

其中mod_callcenter_runtime未定义,实际只有mod_callcenter_load和mod_callcenter_shutdown起作用。首先分析load,整个过程中做了5件事:

1.注册事件callcenter::info

2.注册事件的回调函数

3.加载配置文件

4.开启座席分配线程

5.注册app和api接口

SWITCH_MODULE_LOAD_FUNCTION(mod_callcenter_load)
{
	switch_api_interface_t *api_interface;
	switch_json_api_interface_t *json_api_interface;
	switch_status_t status;
    
    //注册事件callcenter::info
	/* create/register custom event message type */
	if (switch_event_reserve_subclass(CALLCENTER_EVENT) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", CALLCENTER_EVENT);
		return SWITCH_STATUS_TERM;
	}

    //注册事件的回调函数
	/* Subscribe to presence request events */
	if (switch_event_bind_removable(modname, SWITCH_EVENT_PRESENCE_PROBE, SWITCH_EVENT_SUBCLASS_ANY,
									cc_presence_event_handler, NULL, &globals.node) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to subscribe for presence events!\n");
		return SWITCH_STATUS_GENERR;
	}

	memset(&globals, 0, sizeof(globals));
	globals.pool = pool;

	switch_core_hash_init(&globals.queue_hash);
	switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool);
    
    //加载配置文件
	if ((status = load_config(pool)) != SWITCH_STATUS_SUCCESS) {
		switch_event_unbind(&globals.node);
		switch_event_free_subclass(CALLCENTER_EVENT);
		switch_core_hash_destroy(&globals.queue_hash);
		return status;
	}

	switch_mutex_lock(globals.mutex);
	globals.running = 1;
	switch_mutex_unlock(globals.mutex);

	/* connect my internal structure to the blank pointer passed to me */
	*module_interface = switch_loadable_module_create_module_interface(pool, modname);
    
    //开启座席分配线程
	if (!AGENT_DISPATCH_THREAD_STARTED) {
		cc_agent_dispatch_thread_start();
	}
    
    //注册app和api接口
	SWITCH_ADD_APP(app_interface, "callcenter", "CallCenter", CC_DESC, callcenter_function, CC_USAGE, SAF_NONE);
	SWITCH_ADD_APP(app_interface, "callcenter_track", "CallCenter Track Call", "Track external mod_callcenter calls to avoid place new calls", callcenter_track, CC_USAGE, SAF_SUPPORT_NOMEDIA);
	SWITCH_ADD_API(api_interface, "callcenter_config", "Config of callcenter", cc_config_api_function, CC_CONFIG_API_SYNTAX);
	SWITCH_ADD_API(api_interface, "callcenter_break", "Stop watching an uuid and release agent", cc_break_api_function, "callcenter_break agent <uuid>");

	SWITCH_ADD_JSON_API(json_api_interface, "callcenter_config", "JSON Callcenter API", json_callcenter_config_function, "");

	switch_console_set_complete("add callcenter_config agent add");
	//......

	/* indicate that the module should continue to be loaded */
	return SWITCH_STATUS_SUCCESS;
}

load已经执行完成,看看 cc_agent_dispatch_thread_start 这个线程

void cc_agent_dispatch_thread_start(void)
{
	......

	switch_threadattr_create(&thd_attr, globals.pool);
	switch_threadattr_detach_set(thd_attr, 1);
	switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
	switch_threadattr_priority_set(thd_attr, SWITCH_PRI_REALTIME);
	switch_thread_create(&thread, thd_attr, cc_agent_dispatch_thread_run, NULL, globals.pool);
}

又开启了一个线程 cc_agent_dispatch_thread_run

void *SWITCH_THREAD_FUNC cc_agent_dispatch_thread_run(switch_thread_t *thread, void *obj)
{
    ......

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Agent Dispatch Thread Started\n");

	while (globals.running == 1) {
		char *sql = NULL;
		sql = switch_mprintf("SELECT queue,uuid,session_uuid,cid_number,cid_name,joined_epoch,(%" SWITCH_TIME_T_FMT "-joined_epoch)+base_score+skill_score AS score, state, abandoned_epoch, serving_agent, instance_id FROM members"
				" WHERE (state = '%q' OR state = '%q' OR (serving_agent = 'ring-all' AND state = '%q') OR (serving_agent = 'ring-progressively' AND state = '%q')) AND instance_id = '%q' ORDER BY score DESC",
				local_epoch_time_now(NULL),
				cc_member_state2str(CC_MEMBER_STATE_WAITING), cc_member_state2str(CC_MEMBER_STATE_ABANDONED), cc_member_state2str(CC_MEMBER_STATE_TRYING), cc_member_state2str(CC_MEMBER_STATE_TRYING), globals.cc_instance_id);

		cc_execute_sql_callback(NULL /* queue */, NULL /* mutex */, sql, members_callback, NULL /* Call back variables */);
		switch_safe_free(sql);
		switch_yield(100000);
	}

    ......
}

这个线程每100ms查询一次members表,选出状态为Waiting,Trying,Abandoned的member,运行回调函数 members_callback。members_callback的作用是根据队列策略,使用sql找到一个合适的agent分配给member。根据队列策略选择agent的逻辑可以看这篇博文中queues 配置详解的部分。FreeSWITCH mod_callcenter 整理_normal_circuit_congestion_顶顶通-FreeSWITCH二次开发接口的博客-CSDN博客

cc_execute_sql_callback(NULL /* queue */, NULL /* mutex */, sql, agents_callback, &cbt /* Call back variables */);

对于选出的agent,调用agents_callback。agents_callback会检查tiers的一些参数,更新member的状态,然后开启新线程oubound_agent_thread_run。

switch_thread_create(&thread, thd_attr, outbound_agent_thread_run, h, h->pool);

oubound_agent_thread_run的代码有500多行,主要做的事情是将member和agent连接起来,如果座席是callback模式,那么会先使用originate执行一个单腿呼叫,然后再定位到agent的channel,设置一些通道变量;如果是uuid_standby模式,会播放一段提示音。之后会判断呼叫是否成功,如果成功那么运行bridge函数,电话就接通了,失败的话会根据失败原因,让座席休眠一段时间。中间还有许多表的更新,和触发事件的操作,就不具体分析了。

if (!strcasecmp(h->agent_type, CC_AGENT_TYPE_CALLBACK)){
    ......
    //单腿呼叫
	status = switch_ivr_originate(NULL, &agent_session, &cause, dialstr, globals.agent_originate_timeout, NULL, cid_name ? cid_name : h->member_cid_name, cid_number ? cid_number : h->member_cid_number, NULL, ovars, SOF_NONE, NULL, NULL);
    ......
}else if (!strcasecmp(h->agent_type, CC_AGENT_TYPE_UUID_STANDBY)) {
    ......
    //放音乐
    if (cc_warning_tone) {
	  switch_ivr_park_session(agent_session);
	  switch_channel_wait_for_flag(agent_channel, CF_PARK, SWITCH_TRUE, 5000, NULL);
	  playback_array(agent_session, cc_warning_tone);
	}
    ......
}
.....
/* Originate/Bridge is not finished, processing the return value */
if (status == SWITCH_STATUS_SUCCESS) {
    ......
    //bridge member and agent
    if (switch_ivr_uuid_bridge(h->member_session_uuid,                     switch_core_session_get_uuid(agent_session)) != SWITCH_STATUS_SUCCESS) {
	bridged = 0;
    }
    ......
}else{
    ......
	/* Put agent to sleep for some time if necessary */
	if (delay_next_agent_call > 0) {
		char ready_epoch[64];
		switch_snprintf(ready_epoch, sizeof(ready_epoch), "%" SWITCH_TIME_T_FMT, local_epoch_time_now(NULL) + delay_next_agent_call);
		cc_agent_update("ready_time", ready_epoch , h->agent_name);
		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(member_session), SWITCH_LOG_DEBUG, "Agent %s sleeping for %d seconds\n", h->agent_name, delay_next_agent_call);
	}   
    ...... 
}

mod_callcenter_load到这里已经分析结束了,还有一个主要的函数是APP接口 ,也就是拨号方案里面用到的callcenter。这个函数也比较长,300多行,但是注释十分详尽。主要就是新建一个member对象,插入到members表里,等待cc_agent_dispatch_thread_run分配agent。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值