Asterisk框架解读

本文深入解析开源PBX系统Asterisk的内核,涵盖核心模块、启动过程和基本呼叫流程。Asterisk由PBX交换核心、调度I/O管理、应用调用、编解码、动态模块加载和CDR生成模块组成。此外,文章还探讨了Asterisk的启动流程和一个典型呼叫的处理步骤,涉及通道、应用、编解码及函数调用路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文转自:http://www.cppblog.com/eyesmart/archive/2009/09/03/95219.html

 

Asterisk是一个开源的pbx系统,在公开的资料中,很难找到asterisk内核系统的详细描述。因此,很有必要写一篇内核框架的描述文档,作为内部培训文档,相互学习提高。

本文主要从三个层面来描述asterisk内核,即asterisk内核模块、内核启动过程、基本呼叫流程。

一、asterisk内核模块

Asterisk由内部核心和外围动态可加载模块组成。内部核心由以下六个部分组成:PBX交换核心模块(PBX Switching Core)、调度和I/O管理模块(Scheduler and I/O Manager)、应用调用模块(Application Launcher)、编解码转换模块(Codec Translator)、动态模块加载器模块(Dynamic Module Loader)和CDR生成模块(CDR Core)。

外围动态可加载模块包括以App_开始的Applications、以Func_开始的Functions、以Res_开始的Resources、以Chan_开始的channels、以Codec_开始的codec编解码模块等。

1.内核模块

1) PBX交换核心模块(PBX Switching Core):

l  pbx.c

pbx.c是asterisk的核心模块,每路呼叫都需要经过它调度。pbx实现了builtin applications,也就是内置的应用,比如最常见的Answer,Hangup, Background,Wait等等。

struct ast_app是一个关键数据结构,它定义了注册builtin applications的结构。

load_pbx函数用来注册builtin applications和一些命令行CLI命令(每个模块都有些CLI命令)。该函数在系统启动时被调用。

pbx_exec是Answer/BackGround/Busy/Goto/GotoIf/Hangup/Set等builtin applications的执行入口函数,它被pbx_extension_helper调用。

ast_pbx_start函数是每路呼叫的起点。

2)        调度和I/O管理模块(Scheduler and I/O Manager):

l         Channel.c:

Channel.c/channel.h定义了channel操作的结构体和接口函数。

struct ast_channel_tech结构体是所有channel都要用到的关键结构体,它定义channel操作的一系列回调函数指针,如call、 hangup、answer等。每个channel模块都会定义ast_channel_tech的实体,并将各自的回调函数赋值给它。例如 chan_sip.c中定义如下:

/*! /brief Definition of this channel for PBX channel registration */

static const struct ast_channel_tech sip_tech = {

       .type = "SIP",

       .description = "Session Initiation Protocol (SIP)",

       .capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1),

       .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,

       .requester = sip_request_call,

       .devicestate = sip_devicestate,

       .call = sip_call,

       .hangup = sip_hangup,

       .answer = sip_answer,

       .read = sip_read,

       .write = sip_write,

       .write_video = sip_write,

       .indicate = sip_indicate,

       .transfer = sip_transfer,

       .fixup = sip_fixup,

       .send_digit_begin = sip_senddigit_begin,

       .send_digit_end = sip_senddigit_end,

       .bridge = ast_rtp_bridge,

       .send_text = sip_sendtext,

       .func_channel_read = acf_channel_read,

};

ast_call、ast_hangup、ast_answer等函数分别实现ast_channel_tech中的call、hangup、answer等回调函数的调用。

struct ast_channel结构体定义了channel的上下文参数,它是每个参与呼叫的channel必不可少的,都会调用ast_channel_alloc来申请ast_channel。

l io.c

io.c实现了asterisk跟外部交互时的I/O管理,如chan_sip为了从外部接收SIP信令,调用ast_io_add添加IO接口,并调用ast_io_wait实现外部消息接收。

3)应用调用模块(Application Launcher):

在pbx.c中定义了一系列的应用调用接口。

applications模块定义了application回调函数并注册后,在pbx.c中通过应用调用接口回调执行。

应用调用接口的关键函数是pbx_extension_helper,它执行dialplan,在cli上打印“Executing ……”,并抛出ami event事件,同时调用pbx_exec执行application回调函数。

4) 编解码转换模块(Codec Translator):

Translate.c:

struct ast_translator:编码转换描述结构体,它定义了编码转换的名称、回调函数、运行时选项。

struct ast_trans_pvt:编码转换上下文描述结构体。

ast_register_translator:编码转换注册接口函数,供各编码模块调用,注册struct ast_translator类型的结构体变量。

ast_unregister_translator:编码转换注销函数

ast_translate:编码转换的执行函数。

codec_gsm.c/codec_...:对应各种编码的编解码执行模块,如g.711alaw/g.711ulaw/gsm等。

5)动态模块加载器模块(Dynamic Module Loader):

该模块主要是Module.h。

Module.h中定义了struct ast_module_info结构,用来保存各模块的注册、注销回调函数,以及模块描述信息。

load_module、unload_module,每个应用模块的注册、注销函数,由各个模块自行定义为static函数。

AST_MODULE_INFO_STANDARD:注册接口、注销接口、模块描述信息等模块信息的登记接口。它是一个宏定义,动态模块调用它时,首先定义类型为ast_module_info的__mod_info静态结构变量,保存模块信息,并定义__attribute__ ((constructor)) __reg_module和__attribute__ ((destructor)) __unreg_module,在程序启动和退出时调用。

6)CDR生成模块(CDR Core):

Cdr.c:

ast_cdr_register:cdr driver注册,供cdr_mysql等调用,注册话单保存的回调函数。

ast_cdr_engine_init:CDR模块初始化,注册cdr status、加载cdr.conf、启动CDR线程。

ast_cdr_detach:产生话单的接口函数,呼叫结束时被调用。

2.外围可加载模块:

1)Applications

以app_开始的模块,如app_dial.c、app_db.c、app_queue.c、app_record.c、app_meetme.c 等,代码保存在apps目录中。每个application模块都定义了load_module函数和unload_module函数,分别用来注册和注销application。

load_module函数调用ast_register_application函数,注册application命令,例如app_dial模块注册Dial:res = ast_register_application(app, dial_exec, synopsis, descrip)。

unload_module函数调用ast_unregister_application函数,注销application命令。

每个application模块都会使用AST_MODULE_INFO_STANDARD宏来登记模块信息__mod_info。 AST_MODULE_INFO_STANDARD将load_module和unload_module注册为回调函数,供module load/unload/reload调用。

2)Channel

以chan_开始的模块,如chan_sip.c、chan_h323.c、chan_mgcp.c 、chan_iax2.c、 chan_zap.c等,对应代码保存在channels目录中。

channel注册、注销过程和application基本类似。由于每个channel需要和外部交互,都会在load_module中启用do_monitor线程来侦听外部tcp/udp端口,接收外部消息。

每个channel也定义了各自的cli命令和Function命令,例如chan_sip定义了sip debug/history/no/notify/prune/ reload/set/show等cli命令和SIP_HEADER、CHECKSIPDOMAIN、SIPPEER、SIPCHANINFO等 Function命令。

3)Functions

以Fun_开始的模块,例如Fun_db.c、func_moh.c、func_cdr.c等,对应代码保存在funcs目录中。

Function注册、注销过程也和application类似。

每个Function模块也定义了各自的Function命令,例如Fun_db.c就定义了DB、DB_EXISTS、DB_DELETE等Function命令。

二、asterisk启动过程

主要就main函数讲解asterisk的启动过程:

// 代码太多了,省略。。。。。。


三、asterisk基本呼叫流程

从内核的角度去分析问题时,弄清楚呼叫流程是非常关键的,只有理清了呼叫流程,才能从流程的各个环节细节中分析出问题所在。

Asterisk所有功能都是基于函数调用的模式,呼叫流程也不例外。因此如何从一团乱麻似的内核函数调用中理出函数调用执行路线,是解读呼叫流程的关键。

所有呼叫都跟astersisk的channel有关。这路通话都包含一个incoming连接和一个outbound连接。每个电话都是通过对应的channel程序建立起来的,比如Chan_sip,Chan_zap,Chan_iax2等等。每一类的channel,都拥有自己私有的 channel数据结构,例如chan_sip的struct sip_pvt结构,这些私有的结构从属于一个通用的Asterisk通道数据结构中,具体定义在channel.h的struct ast_channe中。

下图是asterisk 的呼叫流程图:

我们以sip的呼叫过程为例来描述,其他channel的呼叫过程基本类似。

Astersik下注册的sip用户主动发起一个呼叫的函数调用过程(incoming)如下:

do_monitor->sipsock_read->handle_request->handle_request_invite->sip_new/ast_pbx_start->pbx_thread->__ast_pbx_run

-> ast_spawn_extension ->pbx_extension_helper->pbx_exec->执行dialplan

当Chan_sip模块被加载时,会启动一个独立的监听线程do_monitor,不断侦听sip端口上的外部消息;

当sip用户拨叫被叫号码后,chan_sip的do_monitor调用sipsock_read函数,在sip端口收到invite消息,然后就调用handle_request和handle_request_invite进行处理。

在handle_request_invite中,首先解析invite消息,对该sip用户的业务属性分析,确认被叫可达,然后就调用sip_new申请channel资源,并调用ast_pbx_start函数启动一个pbx_thread线程来专门处理该呼叫。

pbx_thread线程调用__ast_pbx_run。

__ast_pbx_run是一个衔接dialplan和内核的关键函数,它首先调用ast_exists_extension函数,根据分机号码的context属性,匹配到对应的dialplan;然后进入一个for死循环,逐条执行dialplan对应的context中的语句。

pbx_extension_helper函数调用pbx_extension_helper,在pbx_extension_helper中调用 pbx_find_extension找到对应的context后,通过verbose打印dialplan执行语句“Executing ……”,同时调用pbx_exec执行该dialplan。执行到dial语句呼叫被叫。

在等待被叫接通的过程中,完成媒体协商过程,向主叫发送180、200OK消息接通呼叫。

当其他用户呼叫asterisk的sip用户时,函数调用过程(outbound)如下: Dial->dial_exec->dial_exec_full->ast_request/ast_call/wait_for_answer/ ast_bridge_call

呼叫执行到dial时,pbx_exec调用application dial的接口函数dial_exec,dial_exec调用dial_exec_full。

在dial_exec_full中,首先调用ast_request,在ast_request调用chan_sip对应的回调函数 sip_request_call为该被叫sip用户申请channel资源。然后调用ast_call,在ast_call中调用chan_sip对应的回调函数sip_call向被叫发送INVITE消息,呼叫被叫SIP用户。

然后该呼叫线程会调用wait_for_answer等待被叫接通。

在呼叫接通后,也即wait_for_answer函数返回,在dial_exec_full中调用ast_bridge_call桥接媒体,这样呼叫就正式接通了。

当chan_sip的侦听线程接收到BYE消息,则调用handle_request_bye找到相应的channel,执行hangup释放呼叫。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值