asterisk概述和代碼分析

引自:http://blog.csdn.net/ren911/article/details/6652395

1 Asterisk項目概述

Asterisk是一個開源的軟件包,一般運行在Linux操做系統平臺上。Asterisk能夠用三種協議來實現VoIP,同時能夠與目前電話使用的標準硬件進行交互通訊,Asterisk在實現VoIP時,不須要任何附加硬件,本文所採用的也是這種使用方式。可是,若是企業沒有與VoIP語音網關運營商創建合做關係,想要本身構建這樣的一個平臺,那麼要和數字電話設備與模擬電話設備進行交互通訊,Asterisk須要一個PCI硬件的支持,這個硬件生產商中最著名的是Digium平臺提供的。 html

Asterisk 的結構基本上是十分簡單,可是它不一樣於大多數的電話產品。基本上,Asterisk擔任的是一箇中間件的功能,它鏈接了底層的電話技術和上層的電話應用。因此,Asterisk 具備很大的柔韌性,特殊的API接口都圍繞着PBX核心系統。這個核心處理着PBX內部之間的相互聯繫。每一部分都是清晰來自於協議、編碼或內部電話使用的硬件接口的抽象。這些抽象的接口使Asterisk能夠與任何的硬件和技術以及未來的硬件和軟件技術完美的結合。從下圖能夠看出,Asterisk由內部核心和外圍動態可加載模塊組成。內部核心由如下六個部分組成:PBX交換核心模塊(PBX Switching Core)、調度和I/O管理模塊(Scheduler and I/O Manager)、應用調用模塊(Application Launcher)、編解碼轉換模塊(Codec Translator)、動態模塊加載器模塊(Dynamic Module Loader)和CDR生成模塊(CDR Core) 。
git

2 Asterisk二次開發概述

Asterisk是一個開源的PBX架構;但它並非一個成品。一般狀況下,因爲企業應用的多樣性,很難有一個成型的PBX產品能夠知足企業的各類需求。傳統的PBX成品,要麼功能和靈活性不足,要麼配置和維護複雜;並且都具備一個致命的缺點,那就是開放性、可擴展性。 c#

Asterisk具備傳統PBX沒法比擬的優勢,那就是其靈活性,可擴展能力;Asterisk的擴展能力是經過開放相應的架構和接口來實現的。這就意味着Asterisk是一個組件而不是一個成型的產品,Asterisk的核心提供了一個基本的可運行環境,而外圍相應的能力則能夠經過加載和配置相關的插件和模塊來實現。 數據結構

Asterisk是一個開源的PBX架構;但它並非一個成品。Asterisk的擴展能力是經過開放相應的架構和接口來實現的。這就意味着Asterisk是一個組件而不是一個成型的產品,Asterisk的核心提供了一個基本的可運行環境,而外圍相應的能力則能夠經過加載和配置相關的插件和模塊來實現。 架構

所以,使用Asterisk,必定會面臨二次開發問題,這些二次開發主要圍繞如下幾個方面: app

(1)內部核心模塊 socket

①開發擴展編解碼能力模塊 async

②開發擴展相應的通道模塊 ide

(2)外圍動態可加載模塊 函數

①開發應用部分

②開發外圍管理部分

通常來講,Asterisk使用者不多須要去開發編解碼能力模塊和通道模塊等內部核心模塊;而須要開發最多的狀況則是外圍動態可加載模塊,即外圍管理部分和應用開發,本文也是指這些方面的開發。

3 Asterisk通道模型與呼叫流程

3.1什麼是asterisk通道?

Asterisk通道是指經過asterisk創建起來的一路通話。這類通話都包含一個incoming鏈接和一個outbound鏈接。每一個電話都是經過一種通道驅動程序創建起來的,好比SIP,ZAP,IAX2等等。每一類的通道驅動,都擁有本身私有的通道數據結構,這些私有的結構從屬於一個通用的Asterisk通道數據結構中,具體定義在channel.hchannel.c中。

3.2基本的呼叫流程

Asterisk PBX呼叫流程如圖2所示。

(1)經過Asterisk的一個電話呼叫在一個通道驅動接口上到達,如SIP Socket。

(2)通道驅動在該通道上建立一個PBX通道並啓動一個pbx線程

(3)撥號方案被執行,撥號方案在一些地方經過dial應用(查看app_dial.c)

強制Asterisk建立一個呼出呼叫,一旦呼出,Asterisk會有如下兩個動做將發生。

(1)Dial建立一個呼出的PBX通道並請求一種通道驅動建立一個呼叫

(2)當呼叫被應答時,Asterisk橋接媒體流,因而在第一個通道上的主叫能夠和在第

二個通道也就是呼出通道上的被叫通話。

3.3詳細呼叫流程分析

咱們以sip的呼叫過程爲例來描述,其餘channel的呼叫過程基本相似。
Astersik下注冊的sip用戶主動發起一個呼叫的函數調用過程以下:
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進行處理。

static void *do_monitor(void *data)
{
int res;
struct sip_pvt *sip;
struct sip_peer *peer = NULL;
time_t t;
int fastrestart = FALSE;
int lastpeernum = -1;
int curpeernum;
int reloading;
/* Add an I/O event to our SIP UDP socket */
if (sipsock > -1)
/*io.c實現了asterisk跟外部交互時的I/O管理,如chan_sip爲了從外部接收SIP信令,調用ast_io_add添加IO接口,並調用ast_io_wait實現外部消息接收。*/
sipsock_read_id =ast_io_add(io, sipsock,sipsock_read,AST_IO_IN, NULL);
。。。
}
static int sipsock_read(int *id, int fd, short events, void *ignore)
{
/*當sip用戶撥叫被叫號碼後,chan_sip的do_monitor調用sipsock_read函數,在sip端口收到invite消息,而後就調用handle_request和handle_request_invite進行處理。*/
if (handle_request(p, &req, &sin, &recount, &nounlock) == -1) {
/* Request failed */
if (option_debug)
ast_log(LOG_DEBUG, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>");
}
在
static int handle_request(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int *recount, int *nounlock)
{
。。。
switch (p->method) {
case SIP_OPTIONS:
res = handle_request_options(p, req);
break;
case SIP_INVITE:
res = handle_request_invite(p, req, debug, seqno, sin, recount, e, nounlock);
break;
case SIP_REFER:
res = handle_request_refer(p, req, debug, ignore, seqno, nounlock);
break;
。。。
}

在handle_request_invite中,首先解析invite消息,對該sip用戶的業務屬性分析,確認被叫可達,而後就調用sip_new申請channel資源:

static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin, int *recount, char *e, int *nounlock)
{
…
/* Don't hold a sip pvt lock while we allocate a channel */
/*ast_channel_alloc定義在channel.c中,每一個呼叫都會調用ast_channel_alloc來申請ast_channel*/
tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, i->amaflags, "SIP/%s-%08x", my_name, (int)(long) i);
}
if (!tmp) {
ast_log(LOG_WARNING, "Unable to allocate AST channel structure for SIP channel\n");
ast_mutex_lock(&i->lock);
return NULL;
}
ast_mutex_lock(&i->lock);
if (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO)
/*Channel.c/channel.h定義了channel操做的結構體和接口函數。
struct ast_channel_tech結構體是全部channel都要用到的關鍵結構體,它定義channel操做的一系列回調函數指針,如call、hangup、answer等。每一個channel模塊都會定義ast_channel_tech的實體,並將各自的回調函數賦值給它。*/
tmp->tech = &sip_tech_info;
else
tmp->tech = &sip_tech;
(struct ast_channel結構體定義了channel的上下文參數,它是每一個參與呼叫的channel必不可少的,都會調用ast_channel_alloc來申請)
/*channel.h*/
struct ast_channel {
/*! \brief Technology (point to channel driver) */
const struct ast_channel_tech *tech;
…
}

裏面ast_channel_tech應該是最主要的一個結構體,定義呼叫流程中標準的過程有那些,在具體的chan_**文件中各協議註冊本身對應這些過程的回調函數

/*channel.h*/
/*! \brief
Structure to describe a channel "technology", ie a channel driver
See for examples:
\arg chan_iax2.c - The Inter-Asterisk exchange protocol
\arg chan_sip.c - The SIP channel driver
\arg chan_zap.c - PSTN connectivity (TDM, PRI, T1/E1, FXO, FXS)
If you develop your own channel driver, this is where you
tell the PBX at registration of your driver what properties
this driver supports and where different callbacks are
implemented.
*/
struct ast_channel_tech {
const char * const type;
const char * const description;
int capabilities;/*!< Bitmap of formats this channel can handle */
int properties;/*!< Technology Properties */
/*! \brief Requester - to set up call data structures (pvt's) */
struct ast_channel *(* const requester)(const char *type, int format, void *data, int *cause);
int (* const devicestate)(void *data);/*!< Devicestate call back */
/*! \brief Start sending a literal DTMF digit 開始傳送DTMF數據*/
int (* const send_digit_begin)(struct ast_channel *chan, char digit);
/*! \brief Stop sending a literal DTMF digit 中止傳送DTMF數據*/
int (* const send_digit_end)(struct ast_channel *chan, char digit, unsigned int duration);
/*! \brief Call a given phone number (address, etc), but don't
take longer than timeout seconds to do so.  */
int (* const call)(struct ast_channel *chan, char *addr, int timeout);
/*! \brief Hangup (and possibly destroy) the channel 掛斷通道*/
int (* const hangup)(struct ast_channel *chan);
/*! \brief Answer the channel 響應通道*/
int (* const answer)(struct ast_channel *chan);
/*! \brief Read a frame, in standard format (see frame.h) 以標準禎格式讀一個禎*/
struct ast_frame * (* const read)(struct ast_channel *chan);
/*! \brief Write a frame, in standard format (see frame.h) 以標準禎格式寫一個禎*/
int (* const write)(struct ast_channel *chan, struct ast_frame *frame);
/*! \brief Display or transmit text 顯示或發送文本*/
int (* const send_text)(struct ast_channel *chan, const char *text);
/*! \brief Display or send an image 顯示或發送圖片*/
int (* const send_image)(struct ast_channel *chan, struct ast_frame *frame);
/*! \brief Send HTML data 發送HTML數據*/
int (* const send_html)(struct ast_channel *chan, int subclass, const char *data, int len);
/*! \brief Handle an exception, reading a frame 處理讀取禎時發生的異常*/
struct ast_frame * (* const exception)(struct ast_channel *chan);
/*! \brief Bridge two channels of the same type together 橋接兩種相同類型的通道*/
enum ast_bridge_result (* const bridge)(struct ast_channel *c0, struct ast_channel *c1, int flags,
struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
/*! \brief Indicate a particular condition (e.g. AST_CONTROL_BUSY or AST_CONTROL_RINGING or AST_CONTROL_CONGESTION 指示一種特殊的情況如sip的486error*/
int (* const indicate)(struct ast_channel *c, int condition, const void *data, size_t datalen);
/*! \brief Fix up a channel:  If a channel is consumed, this is called.  Basically update any ->owner links */
int (* const fixup)(struct ast_channel *oldchan, struct ast_channel *newchan);
/*! \brief Set a given option */
int (* const setoption)(struct ast_channel *chan, int option, void *data, int datalen);
/*! \brief Query a given option */
int (* const queryoption)(struct ast_channel *chan, int option, void *data, int *datalen);
/*! \brief Blind transfer other side (see app_transfer.c and ast_transfer() */
int (* const transfer)(struct ast_channel *chan, const char *newdest);
/*! \brief Write a frame, in standard format */
int (* const write_video)(struct ast_channel *chan, struct ast_frame *frame);
/*! \brief Find bridged channel 查詢已經找到的通道*/
struct ast_channel *(* const bridged_channel)(struct ast_channel *chan, struct ast_channel *bridge);
/*! \brief Provide additional read items for CHANNEL() dialplan function */
int (* func_channel_read)(struct ast_channel *chan, char *function, char *data, char *buf, size_t len);
/*! \brief Provide additional write items for CHANNEL() dialplan function */
int (* func_channel_write)(struct ast_channel *chan, char *function, char *data, const char *value);
/*! \brief Retrieve base channel (agent and local) */
struct ast_channel* (* get_base_channel)(struct ast_channel *chan);
/*! \brief Set base channel (agent and local) */
int (* set_base_channel)(struct ast_channel *chan, struct ast_channel *base);
};

如在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_pbx_start函數啓動一個pbx_thread線程來專門處理該呼叫。

static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *title)
{
…
tmp = ast_channel_alloc(…
…
/*ast_pbx_start函數啓動一個pbx_thread線程來專門處理該呼叫*/
if (state != AST_STATE_DOWN &&ast_pbx_start(tmp)) {
ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
tmp->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
ast_hangup(tmp);
tmp = NULL;
}
…
}
enum ast_pbx_result ast_pbx_start(struct ast_channel *c)
{
pthread_t t;
pthread_attr_t attr;
if (!c) {
ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n");
return AST_PBX_FAILED;
}
if (increase_call_count(c))
return AST_PBX_CALL_LIMIT;
/* Start a new thread, and get something handling this channel. */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (ast_pthread_create(&t, &attr,pbx_thread, c)) {
ast_log(LOG_WARNING, "Failed to create new channel thread\n");
pthread_attr_destroy(&attr);
return AST_PBX_FAILED;
}
pthread_attr_destroy(&attr);
return AST_PBX_SUCCESS;
}

pbx_thread線程調用__ast_pbx_run。

static void *pbx_thread(void *data)
{
/* Oh joyeous kernel, we're a new thread, with nothing to do but
answer this channel and get it going.
*/
/* NOTE:
The launcher of this function _MUST_ increment 'countcalls'
before invoking the function; it will be decremented when the
PBX has finished running on the channel
*/
struct ast_channel *c = data;
__ast_pbx_run(c);
decrease_call_count();
pthread_exit(NULL);
return NULL;
}

__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語句呼叫被叫。

static int __ast_pbx_run(struct ast_channel *c)
{
…
while (ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) {
…
}
int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
{
returnpbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH);
}
/*!
* \brief The return value depends on the action:
*
* E_MATCH, E_CANMATCH, E_MATCHMORE require a real match,
*and return 0 on failure, -1 on match;
* E_FINDLABEL maps the label to a priority, and returns
*the priority on success, ... XXX
* E_SPAWN, spawn an application,
*and return 0 on success, -1 on failure.
*
* \note The channel is auto-serviced in this function, because doing an extension
* match may block for a long time.  For example, if the lookup has to use a network
* dialplan switch, such as DUNDi or IAX2, it may take a while.  However, the channel
* auto-service code will queue up any important signalling frames to be processed
* after this is done.
*/
static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
const char *context, const char *exten, int priority,
const char *label, const char *callerid, enum ext_match_t action)
{
…
ast_rdlock_contexts();
e =pbx_find_extension(c, con, &q, context, exten, priority, label, callerid, action);
if (e) {
if (matching_action) {
ast_unlock_contexts();
return -1;/* success, we found it */
} else if (action == E_FINDLABEL) { /* map the label to a priority */
res = e->priority;
ast_unlock_contexts();
return res;/* the priority we were looking for */
} else {/* spawn */
app = pbx_findapp(e->app);
ast_unlock_contexts();
if (!app) {
ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority);
return -1;
}
if (c->context != context)
ast_copy_string(c->context, context, sizeof(c->context));
if (c->exten != exten)
ast_copy_string(c->exten, exten, sizeof(c->exten));
c->priority = priority;
'''/*passdata應該是在此賦值*/'''
pbx_substitute_variables(passdata, sizeof(passdata), c, e);
if (option_debug) {
ast_log(LOG_DEBUG, "Launching '%s'\n", app->name);
}
if (option_verbose > 2) {
char tmp[80], tmp2[80], tmp3[EXT_DATA_SIZE];
ast_verbose( VERBOSE_PREFIX_3 "Executing [%s@%s:%d] %s(\"%s\", \"%s\") %s\n",
exten, context, priority,
term_color(tmp, app->name, COLOR_BRCYAN, 0, sizeof(tmp)),
term_color(tmp2, c->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)),
term_color(tmp3, passdata, COLOR_BRMAGENTA, 0, sizeof(tmp3)),
"in new stack");
}
/* 管理事件 */
manager_event(EVENT_FLAG_CALL, "Newexten",
"Channel: %s\r\n"
"Context: %s\r\n"
"Extension: %s\r\n"
"Priority: %d\r\n"
"Application: %s\r\n"
"AppData: %s\r\n"
"Uniqueid: %s\r\n",
c->name, c->context, c->exten, c->priority, app->name, passdata, c->uniqueid);
returnpbx_exec(c, app, passdata);/* 0 on success, -1 on failure */
…
}

Pbx_exec函數體爲:

/*
\note This function is special. It saves the stack so that no matter
how many times it is called, it returns to the same place */
int pbx_exec(struct ast_channel *c, /*!< Channel */
struct ast_app *app,/*!< Application */
void *data)/*!< Data for execution */
{
int res;
const char *saved_c_appl;
const char *saved_c_data;
if (c->cdr && !ast_check_hangup(c))
ast_cdr_setapp(c->cdr, app->name, data);
/* save channel values */
saved_c_appl= c->appl;
saved_c_data= c->data;
c->appl = app->name;
c->data = data;
/* XXX remember what to to when we have linked apps to modules */
if (app->module) {
/* XXX LOCAL_USER_ADD(app->module) */
}
/*應該是在此執行了應用*/
res = app->execute(c, S_OR(data, ""));
if (app->module) {
/* XXX LOCAL_USER_REMOVE(app->module) */
}
/* restore channel values */
c->appl = saved_c_appl;
c->data = saved_c_data;
return res;
}

在等待被叫接通的過程當中,完成媒體協商過程,向主叫發送180、200OK消息接通呼叫。


當其餘用戶呼叫asterisk的sip用戶時,函數調用過程以下: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。

static int load_module(void)
{
int res;
res = ast_register_application(app, dial_exec, synopsis, descrip);
res |= ast_register_application(rapp, retrydial_exec, rsynopsis, rdescrip);
return res;
}
static int dial_exec(struct ast_channel *chan, void *data)
{
struct ast_flags peerflags;
memset(&peerflags, 0, sizeof(peerflags));
return dial_exec_full(chan, data, &peerflags, NULL);
}
static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags *peerflags, int *continue_exec)
{
。。。
if (!(c =chan->tech->requester(type, capabilities | videoformat, data, cause)))
return NULL;
。。。
}

chan->tech->requester在chan_sip.c中的ast_channel_tech實體中即爲.requester = sip_request_call
在dial_exec_full中,首先調用ast_request,在ast_request調用chan_sip對應的回調函數sip_request_call爲該被叫sip用戶申請channel資源。而後調用ast_call,在ast_call中調用chan_sip對應的回調函數sip_call向被叫發送INVITE消息,呼叫被叫SIP用戶。

static struct ast_channel *sip_request_call(const char *type, int format, void *data, int *cause)
{
…
/*申請資源*/
if (!(p = sip_alloc(NULL, NULL, 0, SIP_INVITE))) {
ast_log(LOG_ERROR, "Unable to build sip pvt data for '%s' (Out of memory or socket error)\n", (char *)data);
*cause = AST_CAUSE_SWITCH_CONGESTION;
return NULL;
}
…

而後該呼叫線程會調用wait_for_answer等待被叫接通。
在呼叫接通後,也即wait_for_answer函數返回,在dial_exec_full中調用ast_bridge_call橋接媒體,這樣呼叫就正式接通了。
當chan_sip的偵聽線程接收到BYE消息,則調用handle_request_bye找到相應的channel,執行hangup釋放呼叫。

if (!ast_strlen_zero(get_header(req, "Also"))) {
ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method.  Ask vendor to support REFER instead\n",
ast_inet_ntoa(p->recv.sin_addr));
if (ast_strlen_zero(p->context))
ast_string_field_set(p, context, default_context);
res = get_also_info(p, req);
if (!res) {
c = p->owner;
if (c) {
bridged_to = ast_bridged_channel(c);
if (bridged_to) {
/* Don't actually hangup here... */
ast_queue_control(c, AST_CONTROL_UNHOLD);
ast_async_goto(bridged_to, p->context, p->refer->refer_to,1);
} else
ast_queue_hangup(p->owner);
}
} else {
ast_log(LOG_WARNING, "Invalid transfer information from '%s'\n", ast_inet_ntoa(p->recv.sin_addr));
if (p->owner)
ast_queue_hangup(p->owner);
}
} else if (p->owner) {
ast_queue_hangup(p->owner);
...
}
相關文章
相關標籤/搜索