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

被折叠的 条评论
为什么被折叠?



