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
Asterisk是一個開源的PBX架構;但它並非一個成品。一般狀況下,因爲企業應用的多樣性,很難有一個成型的PBX產品能夠知足企業的各類需求。傳統的PBX成品,要麼功能和靈活性不足,要麼配置和維護複雜;並且都具備一個致命的缺點,那就是開放性、可擴展性。 c#
Asterisk具備傳統PBX沒法比擬的優勢,那就是其靈活性,可擴展能力;Asterisk的擴展能力是經過開放相應的架構和接口來實現的。這就意味着Asterisk是一個組件而不是一個成型的產品,Asterisk的核心提供了一個基本的可運行環境,而外圍相應的能力則能夠經過加載和配置相關的插件和模塊來實現。 數據結構
Asterisk是一個開源的PBX架構;但它並非一個成品。Asterisk的擴展能力是經過開放相應的架構和接口來實現的。這就意味着Asterisk是一個組件而不是一個成型的產品,Asterisk的核心提供了一個基本的可運行環境,而外圍相應的能力則能夠經過加載和配置相關的插件和模塊來實現。 架構
所以,使用Asterisk,必定會面臨二次開發問題,這些二次開發主要圍繞如下幾個方面: app
(1)內部核心模塊 socket
①開發擴展編解碼能力模塊 async
②開發擴展相應的通道模塊 ide
(2)外圍動態可加載模塊 函數
①開發應用部分
②開發外圍管理部分
通常來講,Asterisk使用者不多須要去開發編解碼能力模塊和通道模塊等內部核心模塊;而須要開發最多的狀況則是外圍動態可加載模塊,即外圍管理部分和應用開發,本文也是指這些方面的開發。
Asterisk通道是指經過asterisk創建起來的一路通話。這類通話都包含一個incoming鏈接和一個outbound鏈接。每一個電話都是經過一種通道驅動程序創建起來的,好比SIP,ZAP,IAX2等等。每一類的通道驅動,都擁有本身私有的通道數據結構,這些私有的結構從屬於一個通用的Asterisk通道數據結構中,具體定義在channel.h和channel.c中。
Asterisk PBX呼叫流程如圖2所示。
(1)經過Asterisk的一個電話呼叫在一個通道驅動接口上到達,如SIP Socket。
(2)通道驅動在該通道上建立一個PBX通道並啓動一個pbx線程
(3)撥號方案被執行,撥號方案在一些地方經過dial應用(查看app_dial.c)
強制Asterisk建立一個呼出呼叫,一旦呼出,Asterisk會有如下兩個動做將發生。
(1)Dial建立一個呼出的PBX通道並請求一種通道驅動建立一個呼叫
(2)當呼叫被應答時,Asterisk橋接媒體流,因而在第一個通道上的主叫能夠和在第
二個通道也就是呼出通道上的被叫通話。
咱們以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); ... }