Introduction to ARI and Channels

Channels: An Overview

在asterisk,channel是终端和asterisk的通信路径。通信路径包含到端点传递的所有双向信息。包含信令(例如"变更设备的状态为ringing",或者挂断呼叫)和媒体(终端接收和发送的真实音频或者视频)。
当asterisk创建一个channel来表示通信路径时,asterisk会为他分配一个UniqueID-在channel的整个生命周期中充当唯一句柄,以及唯一的Name。UniqueID可以是ARI客户端提供的全局唯一标识符。如果ARI客户端没有向channel提供UniqueID,asterisk将为channel分配一个。默认情况下,他使用带有单调递增整数的时间戳,可选的和asterisk system name一起使用。

Channels to Endpoints

channel name由两部分组成:创建的channel的类型,以及由channel类型确定的描述性标识符。可用的channel类型取决于asterisk系统的配置方式;对于大部分示例,我们使用于SIP设备通信的"PJSIP"channel。
在这里插入图片描述
在上述图片中,Alice的sip设备呼入asterisk,并且asterisk已经为得到的channel指定了UniqueID Asterisk01-123456789.1 ,而PJSIP channel驱动也指定了channel name PJSIP/Alice-0000001。为了操作该channel,ARI操作将使用UniqueID Asterisk01-123456789.1作为channel的handle。

Internal Channels - Local Channels

虽然大多数channel位于外部终端与asteirsk之间,但asterisk也可以完全在内部创建channel。这些channel - 成为Local Channels - 有助于在asterisk内部各个资源之间传输媒体。
local channel的特殊之处在于local channel总是在channels中成对出现。创建单个Local "channel"将必然导致asteirsk创建两个channels。位于两个Local channel之间的是一个特殊的虚拟endpoint:在两个channel之间来回转发媒体。local channel中的一个永久的绑定到此endpoint,无法移动-但是,该channel对应的另一个local channel可以任意操作。进入其中一个local channel的媒体都通过另个一local channel传出,反之亦然。
在这里插入图片描述
在上图中,ARI创建了一个local channel,Local/myapp@default。因此asterisk创建了一对uniqueID分别为Asterisk01-123456790.1和Asterisk01-123456790.2的channel。Local channel的name为Local/myapp@default-00000000;1和Local/myqpp@default-0000000;2-其中;1和;2分别表示Local channel的两边。

Channels in a Stasis Application

当在asteirsk创建channel时,便开始执行dialplan。所有channel进入dialplan定义为context/extension/priorit cuple的location。dialplan中的每个tuple location定义了hcannel将执行的一些asterisk应用。当应用执行完毕,tuple的priority增1,并执行dialplan的下一步。持续进行直到dialplan执行完毕,dialplan应用告送channel挂断,或者设备自己挂断。
channel通过Stasis dialplan应用将channel控制权移交给ARI。该应用从dialplan控制channel,并通知具有websocket连接的ARI客户端一个channel现在可以进行控制。当这种情形发生时,发出一个StasisStart事件。当channel离开Stasis dialplan引用-因为要求挂断或者设备自己挂断,发出一个StasisEnd事件。当发出StasisEnd事件时,ARI不再控制该channel并且hcannel重新传送到dialplan。
默认情况下,asterisk的资源不会将自身相关的事件发送给ARI应用。为了获取有关资源的事件,下面三条至少满足一条:

  • 资源必须是进入Stasis dialplan应用的一个channel。在这种情况下会隐式创建一个订阅。当channel离开Stasis dialplan应用时,销毁订阅。
  • 当channel在Stasis dialplan应用时,channel可能与其他资源有交互-例如 bridge。当channel与其他资源交互时,也会创建该资源的订阅。当Stasis dialplan应用中没有channel与该资源交互时,隐式订阅也会被销毁。
  • 任何情况下,ARI应用可以通过应用操作订阅asterisk中的资源。当资源存在时,ARI应用拥有该订阅。

Example: Interacting with Channels

对于我们的Python示例,我们主要依赖ari-py库。因为ari library会通过python logging发出有效信息,所以我们应该继续设置-现在,显示ERROR信息的basicConfig就够用了。最后,我们需要通过初始化一个到asterisk的连接来获得客户端。使用ari.connect方法我们需要保证三件事情:

  • 要连接asterisk server的HTTP URI。这里我们假设asterisk和脚本运行在同一台服务器上,并且使用asterisk的默认http server端口-8088。
  • 连接的ARI user的username,在这里我们设置为asterisk。
  • 连接的ARI user的password,在这里我们设置为asterisk。
尽管很多示例将使用这些认证信息,单请根据你的server修改连接认证信息。
请不要在生产平台使用这些认证信息。
 #!/usr/bin/env python
 
import ari
import logging
 
logging.basicConfig(level=logging.ERROR)
 
client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk')

一旦我们建立了连接,我们的第一个任务是打印出所有现有的channel,或者-如果没有channel-打印这里没有channel。channel resource有对应的操作: GET/channels。由于ari-py library使用操作的昵称动态构建 资源调用的操作,我们在channels resource上使用list方法获取asteirsk当前的所有channels。

current_channels = client.channels.list()
if (len(current_channels) == 0):
    print "No channels currently :-("
else:
    print "Current channels:"
    for channel in current_channels:
        print channel.json.get('name')

GET /channels操作返回channel resource的列表。这些channels resource以JSON形式返回,ari-py library会转换这些resources的uniqueid为对象的属性,而不处理其他字段。因为我们仅仅需要name,我们可以从JSON中提取并打印出来。
下一步将涉及更多,当一个channel进入Stasis dialplan应用时,我们希望打印关于channel的所有信息并且当channel离开时打印名字。因此,我们需要订阅StasisStart和StasisEnd事件:

client.on_channel_event('StasisStart', stasis_start_cb)
client.on_channel_event('StasisEnd', stasis_end_cb)

我们需要两个处理函数: 处理StasisStart事件的stasis_start_cb和处理StasisEnd事件的stasis_end_cb:

def stasis_start_cb(channel_obj, ev):
    """Handler for StasisStart event"""
 
    channel = channel_obj.get('channel')
    print "Channel %s has entered the application" % channel.json.get('name')
 
    for key, value in channel.json.items():
        print "%s: %s" % (key, value)
 
def stasis_end_cb(channel, ev):
    """Handler for StasisEnd event"""
 
    print "%s has left the application" % channel.json.get('name')

最后,我们需要告送客户端运行我们的应用。一旦调用clinet.run,就会创建websocket连接,我们的应用将一直等待事件。可以通过Ctril +C来终止并断开连接。

client.run(apps='channel-dump')

channel-dump.py

channel-dump.py的完整代码如下:

#!/usr/bin/env python
 
import ari
import logging
 
logging.basicConfig(level=logging.ERROR)
 
client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk')
 
current_channels = client.channels.list()
if (len(current_channels) == 0):
    print "No channels currently :-("
else:
    print "Current channels:"
    for channel in current_channels:
        print channel.json.get('name')
 
def stasis_start_cb(channel_obj, ev):
    """Handler for StasisStart event"""
 
    channel = channel_obj.get('channel')
    print "Channel %s has entered the application" % channel.json.get('name')
 
    for key, value in channel.json.items():
        print "%s: %s" % (key, value)
 
def stasis_end_cb(channel, ev):
    """Handler for StasisEnd event"""
 
    print "%s has left the application" % channel.json.get('name')
 
client.on_channel_event('StasisStart', stasis_start_cb)
client.on_channel_event('StasisEnd', stasis_end_cb)
 
client.run(apps='channel-dump')

channel-dump.py in action

这里是channel-dump.py的示例输出。第一次连接时,asterisk中没有channel-但之后来自Alice的PJSIP channel进入extension 1000。于是打印出有关channel的所有信息。在听了一段时间的静音后,alice挂断-我们的脚本通知我们alice的channel已经离开了应用。

asterisk:~$ python channel-dump.py
No channels currently :-(
Channel PJSIP/alice-00000001 has entered the application
accountcode:
name: PJSIP/alice-00000001
caller: {u'Alice': u'', u'6575309': u''}
creationtime: 2014-06-09T17:36:31.698-0500
state: Up
connected: {u'name': u'', u'number': u''}
dialplan: {u'priority': 3, u'exten': u'1000', u'context': u'default'}
id: asterisk-01-1402353503.1
PJSIP/alice-00000001 has left the application

JavaScript(Node.js)

对于我们的javaScript示例,我们主要依赖于Node.js ari-client库。铜鼓初始化一个到asterisk的连接来获取客户端。使用ari.connect方法,我们需要假定四件事情:

  • 要连接asterisk server的HTTP URI。这里我们假设asterisk和脚本运行在同一台服务器上,并且使用asterisk的默认http server端口-8088。
  • 连接的ARI user的username,在这里我们设置为asterisk。
  • 连接的ARI user的password,在这里我们设置为asterisk。
  • 错误发生时可以调用的回调函数,后跟一个ari客户端实例。
尽管很多示例将使用这些认证信息,单请根据你的server修改连接认证信息。
请不要在生产平台使用这些认证信息。
/*jshint node:true*/
'use strict';
 
var ari = require('ari-client');
var util = require('util');
 
ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);
 
// handler for client being loaded
function clientLoaded (err, client) {
  if (err) {
    throw err;
  }
}

一旦建立了连接,我们的第一步要做的就是打印所有现有的channels,如果没有channels-打印这里没有channel。channel resource对此有个操作GET /channels。由于ari-client库动态构建了客户端:使用操作的昵称操作资源调用的对象,我们在所有的channesl资源上使用list操作以便获取asterisk当前的所有channels。

client.channels.list(function(err, channels) {
  if (!channels.length) {
    console.log('No channels currently :-(');
  } else {
    console.log('Current channels:');
    channels.forEach(function(channel) {
      console.log(channel.name);
    });
  }
});

GET /channels操作 当任何错误发生时调用callback以及返回channel resource的列表。ari-client将为每个channel resouce返回javaScript对象。可以从这些对象中直接获取name属性。
下一步将涉及更多,当一个channel进入Stasis dialplan应用"channel-dump"时,我们希望打印关于channel的所有信息并且当channel离开时打印名字。因此,我们需要订阅StasisStart和StasisEnd事件:

client.on('StasisStart', stasisStart);
client.on('StasisEnd', stasisEnd);

我们需要两个回调函数-StasisStart事件对应的stasisStart以及StasisEnd事件对应的stasisEnd。

// handler for StasisStart event
function stasisStart(event, channel) {
  console.log(util.format(
      'Channel %s has entered the application', channel.name));
  // use keys on event since channel will also contain channel operations
  Object.keys(event.channel).forEach(function(key) {
    console.log(util.format('%s: %s', key, JSON.stringify(channel[key])));
  });
}
// handler for StasisEnd event
function stasisEnd(event, channel) {
  console.log(util.format(
      'Channel %s has left the application', channel.name));
}

最后我们需要告送client开始我们的应用。一旦调用client.start,将建立一个websocket连接并且客户端会处理所有来自websocket的事件。可以使用ctrl+C 关闭连接

client.start('channel-dump');

channel-dump.js

channe-dump.js的完整代码如下

/*jshint node:true*/
'use strict';
 
var ari = require('ari-client');
var util = require('util');
 
ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);
 
// handler for client being loaded
function clientLoaded (err, client) {
  if (err) {
    throw err;
  }
 
  client.channels.list(function(err, channels) {
    if (!channels.length) {
      console.log('No channels currently :-(');
    } else {
      console.log('Current channels:');
      channels.forEach(function(channel) {
        console.log(channel.name);
      });
    }
  });
 
  // handler for StasisStart event
  function stasisStart(event, channel) {
    console.log(util.format(
        'Channel %s has entered the application', channel.name));
 
    // use keys on event since channel will also contain channel operations
    Object.keys(event.channel).forEach(function(key) {
      console.log(util.format('%s: %s', key, JSON.stringify(channel[key])));
    });
  }
 
  // handler for StasisEnd event
  function stasisEnd(event, channel) {
    console.log(util.format(
        'Channel %s has left the application', channel.name));
  }
 
  client.on('StasisStart', stasisStart);
  client.on('StasisEnd', stasisEnd);
 
  client.start('channel-dump');
}

channel-dump.js in action

这里是channel-dump.js的示例输出。当第一次连接的时候asterisk没有channel-直到来自Alice的PJSIPchannel呼入拨打1000时。可以打印关于她的channel的所有信息。一段时间后,Alice挂断或者我们的应用断开呼叫。

asterisk:~$ node channel-dump.js
No channels currently :-(
Channel PJSIP/alice-00000001 has entered the application
accountcode:
name: PJSIP/alice-00000001
caller: {u'Alice': u'', u'6575309': u''}
creationtime: 2014-06-09T17:36:31.698-0500
state: Up
connected: {u'name': u'', u'number': u''}
dialplan: {u'priority': 3, u'exten': u'1000', u'context': u'default'}
id: asterisk-01-1402353503.1
PJSIP/alice-00000001 has left the application
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值