ESL-通过事件控制FreeSWITCH

发布时间 2023-12-21 18:12:25作者: 阿风小子

       通过事件提供的最底层控制机制,允许我们有效地利用工具箱,适时选择使用其中的单个工具。FreeSWITCH是一个核心交换与混合矩阵,它周围有几十个模块提供各种功能特性。

        我们完全控制了所有的即时信息,这些信息组成了所有进出FreeSWITCH的通信包。同时,我们可以在适当的时候调用任何模块中的每个功能。

        充分的知识和完全的控制允许我们根据项目的最小细节定制FreeSWITCH,我们永远不会被迫承认:我做不到。

       这一章,我们将涉及以下内容:

  • 什么是事件系统,它在FreeSWITCH中扮演什么角色
  • 事件是怎样的
  • 怎样连接mod_event_socket
  • 为什么事件套接字(Event Socket)如此紧密相关
  • 如何编写脚本来撬动ESL的强大力量

Event系统基本原理

        在本书的第一章里,我们介绍过事件系统及它在FreeSWITCH架构中所扮演的角色。我们不会在这里重复这些。让我们更新一些要点,并添加一些重要的见解:

  • FreeSWITCH由一个高度优化的交换内核和集成模块的混合引擎组成,外围模块提供各种特性
  • 事件系统是为分发“事件”而优化的内部消息总线
  • FreeSWITCH内部所发生的任何事件都会生成一个或多个事件
  • 事件分为两种不同类型:系统事件和日志事件
  • 任何时候需要记录日志都会生成日志事件。日志事件由处理日志的模块或APP监听,事件的处理结果可能是写日志文件,或写数据库,或写系统日志,或写windows注册表,等等
  • 系统事件由FreeSWITCH内核、模块或其它外部应用生成,用于响应“外部”的刺激和表达“内部”的状态
  • 从现在开始,当我们提及“事件”时,如果没有特别说明,我们指的是“系统事件”
  • 事件是内核与模块间、模块与模块间、FreeSWITCH与外部应用程序间相互交互的主要方式
  • 可以用事件携带“命令”,具体模块或内核会执行这些命令
  • 事件分为许多类别和子类别
  • 通过事件系统总线发布的事件可以被“订阅”(或“绑定”)该事件类别/子类别的模块和外部应用程序“监听”
  • 事件可以通过内核、模块和外部应用程序触发”(注入”)到事件系统总线
  • 触发事件是一种“射击即忘记”的动作。除了在总线上接受事件,射手没有直接反馈
  • 射手指定事件的类别/子类别,所有该类别的订阅者监听者接收事件,并决定如何反应
  • 所有订阅者读取(“消费”)完事件后,事件将从事件系统总线中删除
  • 即使只有一个订阅者读取太慢,那些未使用的事件都会排队,直到订阅者消费它们或队列被塞满
  • 事件系统队列是非常长(好像能容纳1万个事件),但它不是无穷的。如果队列完全填满,则事件系统总线停止,FreeSwitch失败
  • 外部应用程序可以通过模块提供的接口访问FreeSWITCH事件系统
  • 最重要的是mod_event_socket模块,它通过一个TCP套接字对外暴露事件系统,外部应用程序可以连接这个套接字并以文本格式交换事件

 

事件格式 – 报头和包体

        在继续深入之前,让我们先看看“事件”到底是长什么样的。事件是一种松散结合的文本消息:它由许多“报头”(key/value配对格式)和一个可选的“包体”(自由形式的有效载荷)组成。这也是许多著名的网络协议的基础结构,如SIP、SMTP、HTTP等等。下面是一个具体例子:

 
  1.  
    Event-Name: RE_SCHEDULE
  2.  
    Core-UUID: 6ddf010e-bc0b-4366-8413-6becd7f77076 FreeSWITCH-Hostname: lxc111
  3.  
    FreeSWITCH-Switchname: lxc111 FreeSWITCH-IPv4: 192.168.1.111
  4.  
    FreeSWITCH-IPv6: 2001%3Ab07%3A5d32%3A860b%3Aa067%3A61ff%3Afe4f%3A2119 Event-Date-Local: 2017-06-26%2018%3A39%3A51
  5.  
    Event-Date-GMT: Mon,%2026%20Jun%202017%2018%3A39%3A51%20GMT
  6.  
    Event-Date-Timestamp: 1498502391402930 Event-Calling-File: switch_scheduler.c
  7.  
    Event-Calling-Function: switch_scheduler_execute Event-Calling-Line-Number: 71
  8.  
    Event-Sequence: 22105
  9.  
    Task-ID: 1
  10.  
    Task-Desc: heartbeat Task-Group: core
  11.  
    Task-Runtime: 1498502411
 

        这是一个最常见的事件转储,即心跳重新调度事件(一个内部的FreeSWITCH脉冲)。这类事件,和其它大多数事件一样,没有包体。从中我们可以看出事件的文本结构,报头由报头名和报头值组成,它们之间以冒号":"分隔。

        我们接着来看一个携带包体(有效载荷)的事件。包体之前(包体与报头以两个换行,即一个空行分隔)的最后一个报头说明包体的长度,就像SIP消息那样:

 
  1.  
    Event-Name: MESSAGE
  2.  
    Core-UUID: 6ddf010e-bc0b-4366-8413-6becd7f77076 FreeSWITCH-Hostname: lxc111
  3.  
    FreeSWITCH-Switchname: lxc111 FreeSWITCH-IPv4: 192.168.1.111
  4.  
    FreeSWITCH-IPv6: 2001%3Ab07%3A5d32%3A860b%3Aa067%3A61ff%3Afe4f%3A2119 Event-Date-Local: 2017-06-26%2019%3A09%3A13
  5.  
    Event-Date-GMT: Mon,%2026%20Jun%202017%2019%3A09%3A13%20GMT
  6.  
    Event-Date-Timestamp: 1498504153183007
  7.  
    Event-Calling-File: switch_loadable_module.c
  8.  
    Event-Calling-Function: switch_core_chat_send_args Event-Calling-Line-Number: 864
  9.  
    Event-Sequence: 22348 proto: global
  10.  
    from: 1011%40lab.opentelecomsolutions.com to: 1001%40lab.opentelecomsolutions.com type: text/plain
  11.  
    skip_global_process: true blocking: true dest_proto: sip
  12.  
    Delivery-Result-Code: 200 Delivery-Failure: false Content-Length: 12
  13.  
     
  14.  
    ciao a tutti
 

 

        以下是一些所有事件都可用的报头说明:

  • Event-Name:事件的名称,它描述事件所属的类型。也称为事件类型或类别。
  • Core-UUID当前对象在FreeSWITCH内核中的UUID,关联事件本身
  • Event-Date-Local:基于系统时钟描述的事件日期/时间
  • Event-Date-GMT:基于GMT(也就是UTC)时间描述的事件日期/时间
  • Event-Calling-File触发事件的C代码源文件名
  • Event-Calling-Function:触发事件的函数名
  • Event-Calling-Line-Number:触发事件的C代码精确行数

        除了这些通用报头之外,每种事件类型都有许多其它字段。其中的一些我们非常熟悉,比如通道和系统变量(不清楚的回顾一下前面的章节)。还有一些是特定事件类型相关的。

        事件分为很多类型,完成的列表请在http://freeswitch.org/confluence搜索"Event Types"。下列列举部分重要的事件类型(事件类别名称):

  • CHANNEL_*
  • API
  • DTMF
  • MESSAGE
  • CODEC
  • PLAYBACK_(START|STOP)
  • RECORD_(START|STOP)
  • CUSTOM

        其中最后一种,CUSTOM类型,即自定义类型,它是一种魔法类型,开发人员可以(通过它)为模块、功能或服务添加特定的事件。因此,对于自定义事件,有个有趣的报头叫"Event-Subclass",它说明事件的角色和含义。

        大多数终端模块(比如用户以Verto登陆或以SIP注册),和许多APP模块都会生成自定义事件。在APP中,尤其以这几个为最:会议应用、传真管理(mod_spandsp),语音信箱、队列(fifo)、呼叫中心,等等。此外,XML IVR也会生成自定义事件,其子类描述进入/退出菜单相关信息。

        在我们的confluence上搜索"CUSTOM Event SUBCLASSES",可以获取完整的子类列表信息。下面是一些模块的自定义事件子类实例:

  • sofia::register_attempt
  • menu::enter
  • spandsp::rxfaxresult
  • filestring::open
  • conference::maintenance

        在conference::maintenance(就像在其他子类中一样) 中, 有另一个有趣的报头,描述事件的含义。在这个特定的conference语境中,它叫Action

        下面列出conference::maintenance事件子类的一些Action报头:

  • conference-create
  • add-member
  • floor-change
  • start-talking
  • play-file-done

 

事件处理模块

        事件系统围绕FreeSWITCH内核与模块循环传递事件。如果你想进入事件总线侦听,或注入某些事件,该怎么办?        FreeSWITCH有一些称为“事件处理器”的模块(它们的源码在/usr/src/freeswitch/src/mod/event_handlers目录里),它们能够把事件与几乎所有分身任何东西进行对接:

  • mod_amqp
  • mod_amqp
  • mod_cdr_csv
  • mod_cdr_mongodb
  • mod_cdr_pg_csv
  • mod_cdr_sqlite
  • mod_erlang_event
  • mod_event_multicast
  • mod_event_socket
  • mod_event_test
  • mod_event_zmq
  • mod_format_cdr
  • mod_json_cdr
  • mod_kazoo
  • mod_odbc_cdr
  • mod_radius_cdr
  • mod_rayo
  • mod_smpp
  • mod_snmp

        所有这些模块把事件系统与外部世界连接在一起,特别是呼叫详单记录(所有的*cdr*模块),或其它更通用的目的。

        尤其重要的是提供amqp(比如说rabbitMQ对接)、zeroMQ和Erlang接口模块。事件系统里最通用的接口是mod_event_socket模块提供的。

 

mod_event_socket

         这是FreeSWITCH事件系统与外部世界交互的主力军。mod_event_socket以一种最简单的方式工作:它打开一个TCP套接字,并监听连接的到来。多个客户端可以通过文本命令和响应与事件系统进行双向连接和交互。

        缺省配置里,ACL只接收来看成127.0.0.1环回接口的连接,不允许外部网络接入。我们下面将介绍如何改变这个现状。

        所有通信交换数据都是基于TCP的明文文本,所以,如果你需要远程连接,请通过VPN或SSH连接,以保证安全性。或者采用加密的中间件或代理。

        mod_event_socket接受许多不同的命令,我们后面会看到,它们能够显示并过滤事件,或向事件系统总线中注入事件。

        事件一出现在总线中,马上就会显示出来,在一台繁忙的FreeSWITCH服务上,这可能会造成你的终端刷屏,让你看不清任何内容。你可能需要过滤它们,或者寻找一种可以快速捕获它们的方式(比方说把它们写入文件,然后用grep查找,能通过其它应用程序分析)。我们接下来将看到更多事件过滤相关的内容。

 

mod_event_socket 配置

        如你所料,XML配置文件是/usr/local/freeswitch/conf/autoload_configs/event_socket.conf.xml。

        在演示配置实例中,mod_event_socket监听所有网络接口上的8021端口,但是它的ACL只接受来自127.0.0.1(本机的环回接口)的连接。其它所有连接将直接回应"access denied"并断开。

        你可以调整两个选项来配置可接受的连接:

  • listen-ip缺省配置是"::",表示监听所有接口,包括IPV4和IPV6。你可以把这个参数设为"0.0.0.0",这样表示只监听所有IPV4的接口;或者"127.0.0.1",表示只监听本地的环回接口
  • apply-inbound-acl缺省配置是"loopback.auto",它表示只允许本地环回接口的连接。你可以写一个acl文件:/usr/local/freeswitch/conf/autoload_configs/acl.conf.xml,并在这里引用;或者配置any_v4.auto",允许所有IPV4的地址连接。

        显然,还需要配置的重要参数还有两个:端口和密码。

        安全性问题,请注意!:我这里再次强调,所有交换的通信信息都是基于TCP的明文文本,因此,如果你需要远程连接,请确保通过VPN或SSH连接。或通过加密中间件/代理访问。

 

通过telnet读取与发送事件

        下面示例,展示了怎样通过telnet连接mod_event_socket,并开始观察事件系统总线里的所有事件。黑体部分(译者改变了字体颜色)是我们输入的内容,每个命令输入完毕后,请按两次回车:

telnet 127.0.0.1 8021

Trying 127.0.0.1...

Connected to 127.0.0.1. Escape character is '^]'. Content-Type: auth/request

auth ClueCon

Content-Type: command/reply Reply-Text: +OK accepted

events plain all

Content-Type: command/reply

Reply-Text: +OK event listener enabled plain

        从此刻起,终端上将会有事件内容输出。在一台空闲的FreeSWITCH服务器上,我们会看到许多HEARTBEAT和RE_SCHEDULE事件。在一台繁忙的服务器上,我们将无法看清屏幕上输出的内容。请记住,可以输入"Ctrl-]"回退到telnet交互终端,然后输入"quit"关闭连接。

        显然,如果我们在呼叫过程中进行观察,事情会变得非常有趣(使用带有大的滚动缓存的终端工具,或者使用"screen",这样你才能完整地记录呼叫过程中的所有事件):


        稍后的示例,我们将看到如何发送命令,如何注入事件,如何才能只看我们关心的事件。

        让我们看一个快速实例,它向事件系统总线发送一个事件。我们首先输入"sendevent"命令和消息类型,接着输入该事件类型所需要的报头(缺省报头FreeSWITCH会自动帮我们加上),最后输入包体内容“Hello World”并按两次回车键。试一下,telnet连接本机端口8021,然后开始输入(在输入完"ClueCon"和"World"后均需要按两次回车):

auth ClueCon

Content-Type: command/reply Reply-Text: +OK accepted

sendevent SEND_MESSAGE Content-Type: text/plain User: 1001

Profile: internal

Host: lab.opentelecomsolutions.com Content-Lenght: 11

Hello World

Content-Type: command/reply

Reply-Text: +OK df178dbd-9b9a-4b48-bcc0-440737e4a2f6

 

        我们发送了一个MESSAGE类型的事件,它会向"lab.opentelecomsolutions.com"域下的"1001"注册用户发送一条SIP MESSAGE聊天信息。Linphone和几乎所有其它SIP软电话都能够显示聊天信息。记住,如果需要退出我们所建立的telnet,按下"Ctrl-]"组合键,然后输入"quit"。

 

fs_cli

        令人惊讶的是:fs_cli就是一个通过本机TCP端口8021连接mod_event_socket的应用程序。连接建立之后,fs_cli发送一些命令并接收"logging"事件。

        让我们研究一下8021端口上的响应!首先断开所有之前已经连接的fs_cli会话或其它应用(因为它们有可能使用与我们一样的接口,这样会影响观察)。然后用ngrep开始嗅探:

ngrep -d lo -q -W single port 8021

 

        打开另一个终端,执行fs_cli。Ngrep将会显示连接和认证过程、"api"命令和"log"命令,它启用"logging"事件的内容显示:


 

接下来,用一部注册话机拨打500,再研究ngrep的输出信息:


事件系统命令

        一旦通过mod_event_socket端口或其它任何实质方式(比如通过其它事件处理模块)连接上事件系统,你就通过简单命令进行交互。

        在http://freeswitch.org/confluence上提供了许多在线文档信息,具体信息可以在"Command Documentation"中搜索"mod_event_socket"。

        我们接下来介绍几个最重要的命令。别忘记:在命令(包括所有报头和包体)输入结束后,按两次回车键发送命令

 

api

        发送一个api命令(堵塞模式),并等待命令执行结束:

 

api <command><arg>

 

        所有可以在FreeSWITCH控制台上执行的命令都可以通过api命令执行,例如:

api originate sofia/mydomain.com/ext@yourvsp.com 1000 api sleep 5000

 

bgapi

        发送一个api命令(非堵塞模式),它将在后台执行任务,执行结果会以事件形式回传,这个事件携带一个uuid,可用于匹配原始命令。

bgapi <command><arg>

        它的执行范围和前面的api命令完全相同,不同的是,服务端会立刻返回,客户端可以并发处理其它后续命令。

以下是一个命令返回消息的示例:

 

Content-Type: command/reply

Reply-Text: +OK Job-UUID: c7709e9c-1517-11dc-842a-d3a3942d3d63

        当命令在后台执行完毕后,FreeSWITCH会触发一个事件,携带执行结果信息,你可以比较Job-UUID,以确定消息具体是哪条命令的执行结果。要接收这类事件,你必须订阅BACKGROUND_JOB事件。

如果需要,你可以自己定义Job-UUID的值:

 

bgapi status

Job-UUID: d8c7f660-37a6-4e73-9170-1a731c442148

 

      返回消息中会把它原封不动地带回来:

 

Content-Type: command/reply

Reply-Text: +OK Job-UUID: d8c7f660-37a6-4e73-9170-1a731c442148 Job-UUID: d8c7f660-37a6-4e73-9170-1a731c442148

 

event

        按类别启用或禁用事件,或者全部启用/禁用(可以指定输出格式为文本、XML或json):

 

event plain <listof events to log or all> event xml <listof events to log or all> event json <listof events to log or all>

 

        event命令用于从FreeSwitch订阅事件。你可以在同一行中指定任意数量的事件,它们之间以空格分隔。

实例:

 

event plain ALL

event plain CHANNEL_CREATE CHANNEL_DESTROY CUSTOM conference::maintenance sofia::register sofia::expire

event xml ALL

event json CHANNEL_ANSWER

 

          后面调用的event命令不会覆盖之前的event设置。假设我们首先订阅了DTMF事件:

 

event plain DTMF

        过段时间后,你需要追加订阅CHANNEL_ANSWER,这时只需要单独订阅就够了:

event plain CHANNEL_ANSWER

在这之后,你将持续接收到DTMF事件和CHANNEL_ANSWER事件,后来者不会覆盖先驱者。

 

filter

        指定监听的事件类型。它指定的是输出过滤,不是输入过滤,换句话说,filter命令只作用于接收到的内容。允许在同一条连接上安装多个过虑器:

用法:

filter <EventHeader><ValueToFilter>

示例:

        以下示例将订阅所有事件,然后创建两个过滤器,一个监听HEARTBEATS事件,另一个监听CHANNEL_EXECUTE事件:

 

events plain all

filter Event-Name CHANNEL_EXECUTE filter Event-Name HEARTBEAT

      现在,你只会收到HEARTBEAT和CHANNEL_EXECUTE事件。你可以对任意事件报头进行过滤。如果要过滤指定通道,你将需要用到uuid:

filter Unique-ID d29a070f-40ff-43d8-8b9d-d369b2389dfe

      如果要筛选多个uuid,你只需要对每个UUID设置过滤器就行了。如果你在一个会议中,需要监视哪个用户正在发言(开始/停止说话),那么这个用法将会很用益处:

filter plain all

filter plain CUSTOM conference::maintenance filter Unique-ID $participantB

filter Unique-ID $participantA filter Unique-ID $participantC

 

        这时,你将收到参与者A、B和C在所有会场中的信息。如果你只想接收一个指定会场里的所有用户的事件,那么你可以这样表达:

filter Conference-Unique-ID $ConfUUID

你可以对FreeSwitch事件中获得的任何参数进行筛选:

filter plain all

filter call-direction Inbound

filter Event-Calling-File mod_conference.c filter Conference-Unique-ID $ConfUUID

 

你可以单独使用它们,也可以组合使用,这完全取决于你期望接收的事件类型的最终结果。

 

filter delete

        撤销指定事件类型的过滤器。在纠正设置错误,或过滤器失效的时候可以使用"filter delete"。

用法:

filter delete <EventHeader><ValueToFilter>

示例:

filter delete Event-Name HEARTBEAT

现在,你再也收不到HEARTBEAT事件了。

filter delete Unique-ID d29a070f-40ff-43d8-8b9d-d369b2389dfe

 

        上面这个是撤销指定Unique-ID的过滤器。执行之后,你不会再想到相关ID的事件。

filter delete Unique-ID

这一条撤销所有基于Unique-ID设定的过滤器。

 

sendevent

向事件系统发送一个事件 (报头换行输入:

sendevent <event-name>

 

         下面实例是发送带消息体的事件,消息体长度由content- length 报头指定:

sendevent SEND_MESSAGE Content-Type: text/plain User: 1001

Profile: internal

Host: lab.opentelecomsolutions.com Content-Lenght: 11

Hello World

 

exit

exit

 

这个命令关闭socket 连接。

 

auth

auth <password>

 

鉴权,你必须在TCP连接建立成功后立即发送auth命令。

 

log

log <level>

        启用日志事件的显示级别。级别定义和控制台相同,比如:7=DEBUG ,3=ERR。

 

nolog

nolog

 

禁用前一条log 命令所设定的日志输出。

 

nixevent

nixevent <event types | ALL       | CUSTOM custom event sub-class>

 

禁止指定的事件类型。当您希望用'event all'后面跟着'nixevent '来查看除某种类型事件外的所有事件时,它非常有用。

 

noevents

noevents

 

禁用之前event 命令所启用的所有事件。

 

sendmsg

sendmsg <uuid>

 

        控制给定UUID的现有呼叫,它需附加"call-command"报头,报头值可以是execute或hangup

示例:

sendmsg ec96830d-0ad5-4a55-afbd-11bb625db2d0 call-command: execute

execute-app-name: playback

execute-app-arg: /tmp/ivr-wakey_wakey_sunshine.wav

 

sendmsg ec96830d-0ad5-4a55-afbd-11bb625db2d0 call-command: hangup

hangup-cause: USER_BUSY

 

call-command

这个报头值可以是以下两者之一:

execute

        执行拨号方案的APP。这时需要附加报头"execute-app-name",还可以用"execute-app-arg"报头向APP传参。此外,还可以用"loops" 报头指定命令重复的次数。

 

hangup

        挂断呼叫,可以加上可选报头 "hangup-cause",用于说明挂断原因。

 

出局Socket – 让话务主动连接你

        拨号方案"socket"向指定IP:端口发起一条出局的TCP连接,这时第三方可以控制呼叫,这和连接mod_event_socket的入局控制完全一样。

        你必须先运行一个“服务”应用,它监听指定的IP:端口,并准备接收"socket"拨号方案APP发起的连接。一旦TCP连接建立,命令和输出内容和我们之前所见的完全一样。

        当你调用socket时,FreeSWITCH会自动把话务置为parked状态。

在拨号方案中调用socket的语法是:

<ip>:<port> [<keywords>]

 

下面是几个具体的使用实例:

<action application="socket" data="127.0.0.1:8084"/>

<action application="socket" data="127.0.0.1:8084 async"/>

<action application="socket" data="127.0.0.1:8084 full"/>

<action application="socket" data="127.0.0.1:8084 async full"/>

关键字full 和async 会改行为:

  • async关键字async说明所有命令将立即返回(异步执行),从而可以在执行命令堆栈的同时监视socket的事件。如果没有async关键字,那么每个event socket都会堵塞到执行完毕。
  • full关键字full说明对端将拥有event socket的完整命令集。这和入局event socket连接的命令集是一样的,因此,你可以执行API命令,获取全局事件,等等。如果没有full关键字,那么命令集将被限制为特定呼叫相关的范围。换句话说,如果没有full关键字,那么,发到这个socket的命令只能影响当前正在处理的通道。同样地,这个socket连接将只接收这条通道相关的事件。

 

ESL - Event Socket 开发库

        实际上,我不建议你采用底层的方式来与FreeSWITCH事件系统交互:我们已经花费很多精力来为你提供一个高层级的开发库,它适用于多种脚本语言和C/C++语言。

        ESL将为您省去许多繁琐的任务,比如管理TCP连接、解析事件等等。许多有用和流行的功能都已经存在,所以您不必重新发明轮子。

 

ESL 支持的语言

         ESL的开发设计理念是只提供一套SWIG接口,因此,无论选择哪种语言,它们的变量、方法,函数名和参数都是一样的。下面是支持的语言列表:

  • Java
  • Lua
  • managed (.NET)
  • perl
  • php
  • python
  • ruby
  • tcl
  • C

ESL 参考

        根据它们在各种编程语言的所有ESL实现中的可用性而论,以下是最重要的函数及其参数。

eslSetLogLevel

eslSetLogLevel($loglevel)

 

在程序中调用这个函数会导致你的应用程序抽STDOUT发送ESL相关的日志信息。

$loglevel指定日志级别,常见取值有:

  • 0 - EMERG
  • 1 - ALERT
  • 2 - CRIT
  • 3 - ERROR
  • 4 - WARNING
  • 5 - NOTICE
  • 6 - INFO
  • 7 - DEBUG

 

ESLevent Object

这是描述事件的对象,可以通过new()创建或接收。它定义的方法有:

new

new($event_type [, $event_subclass])

        实例化一个新的事件,参数指定事件类型。在自定义或需要的时候,可以指定子类型。

 

serialize

serialize([$format])

        把事件以冒号分隔的格式打印'name: value',格式类似于sip/email包(和'/events plain all'的显示方式一样)。如果需要,还可以指定其它输出格式。

$format 可选格式:

  • "xml"
  • "json"
  • "plain" (缺省)

 

setPriority

setPriority([$number])

设置事件的优先级。

 

getHeader

getHeader($header_name)

提取报头值,参数为报头名。

 

getBody

getBody()

提取事件包体内容。

 

getType

getType()

获取事件类型。

 

addBody

addBody($value)

向事件包体添加内容。这个方法可以多次调用。

 

addHeader

addHeader($header_name, $value)

向事件添加报头,参数为报头名和报头值。这个方法可以多次调用。

 

delHeader

delHeader($header_name)

按名字删除报头。

 

firstHeader

firstHeader()

把指针设为事件对象的第一个报头的指针,并返回key的名字。这个方法必须在nextHeader 之前调用。

 

nextHeader

nextHeader()

 

        把指针移动到下一个报头,并返回它的key名字。调用它之前,必须先调用firstHeader方法。如果已经处于最后一个报头,调用返回NULL。

 

ESLconnection Object

        这是描述连接FreeSWITCH所监听的socket的网络连接的对象。它可以通过new()创建。它提供的方法有:

 

new

new($host, $port, $password)

        初始化一个新的ESLconnection实例,三个参数分别为连接的主机、连接的端口、FreeSWITCH的授权密码。

        仅适用于"Inbound"模式。换句话说,这只是为了创建一个面向FreeSWITCH的网络连接对象,这个最初的连接并没有绑定任何呼叫或通道。它不会初始化通道信息(因为inbound没有绑定通道)。在一般语言中,这意味着调用getInfo()将永远返回NULL:

new($fd)

 

        使用现有的文件描述符$fd初始化一个新的ESLconnection 实例。它仅适用于Event Socket的Outbound连接Inbound连接调用,即使传递了合法的inbound socket,调用还是会失败。

        使用这个函数的标准方法是监听一个socket的传入连接,接受来自FreeSWITCH的连接,如果要监听多连接,那么fork一个新的进程副本,然后把socket的文件描述符传递给new($fd)。

 

socketDescriptor

socketDescriptor()

        如果连接对象已连接上,返回连接对象的UNIX 文件描述符。如果是outbound 模式中调用new($fd)实例化的连接对象,那么它们两者是同一个描述符。

 

connected

connected()

测试网络连接状态。已连接返回1,否则返回0。

 

getInfo

getInfo()

        当FreeSWITCH连接到一个"Event Socket Outbound"程序时,在初始连接建立之后,发送的第一个事件是"CHANNEL_DATA"事件。getInfo()会返回ESLevent对象,它包含这些通道数据。对于"Event Socket Inbound"连接来说,调用getInfo()返回NULL。

 

send

send($command)

 

        向FreeSWITCH发命令。

        它不等待命令的回复。你需要马上在循环中调用recvEvent或recvEventTimed,以获取命令回复。回复事件会有一个名为"content-type"的报头,头域值为"api/response"或"command/reply"。

如果要自动等待回复事件,用sendRecv()取代send()。

 

sendRecv

sendRecv($command)

 

        sendRecv($command)内部调用send($command),然后调用recvEvent(),最终返回一个ESLevent实例。recvEvent()是在一个循环中调用的,直到收到命令的回应事件才退出。这个事件有一个名为"content-type"的报头,报头值是"api/response"或"command/reply"。

        在recvEvent()等待命令应答期间收到的任何其它事件都会排队,并在程序中随后的recvEvent()调用中返回。

 

api

api($command[, $arguments])

 

        向FreeSWITCH 服务器发API命令。这个方法以阻塞模式工作,后续命令必须等待它执行返回。

api($command, $args) is identical to sendRecv("api $command $args").

 

bgapi

bgapi($command[, $arguments][,$custom_job_uuid])

 

        向FreeSWITCH服务器发后台执行的API命令。它是异步的,命令有自己的执行纯种,不会堵塞。

bgapi($command, $args) is identical to sendRecv("bgapi $command $args")

 

        下面是一个Perl代码片段,它展示了自定义Job-UUID 的处理逻辑(它对所有swig语言都一样):

 

my $command = shift;

my $args = join("", @ARGV);

my $con = new ESL::ESLconnection("127.0.0.1", "8021", "ClueCon"); my $e = $con->bgapi($command, $args, "my-job-id");

print $e->serialize("json");

 

下面是返回的代码:

{

"Event-Name":        "SOCKET_DATA",

"Content-Type": "command/reply",

"Reply-Text":        "+OK Job-UUID: my-job-id", "Job-UUID":                                      "my-job-id"

}

 

 

sendEvent

sendEvent($event)

向FreeSWITCH 事件系统注入一个事件。

 

recvEvent

recvEvent()

        接收FreeSWITCH发过来的事件。如果没有等待处理的事件,这个函数调用会挂起,一直等到下一个事件到达。

        如果在调用sendRecv()时有事件在队列中排队,那么调用recvEvent将返回队列中的头结点元素,同时把事件从队列中移出,然后按次序从读取后续事件。

 

recvEventTimed

recvEventTimed($milliseconds)

        和recvEvent()类似,不过它最多等待$milliseconds指定的时间。

        调用recvEventTimed(0)会立刻返回。这对于轮询事件很有用。

 

filter

filter($header, $value)

和"filter" 事件系统命令类似的功能函数。

 

events

events($event_type, $value)

和"events"事件系统命令类似的功能函数。

 

execute

execute($app[, $arg][, $uuid])

 

        执行一个拨号方案APP,并等待服务端的应答。对于没有锚定到通道上的socket连接(大部分是inbound的)来说,三个参数都是必须的----$uuid指定执行APP的通道。

        它返回一个包含服务端响应信息的ESLevent对象。这个ESLevent对象的getHeader("Reply- Text")方法返回服务端的应答消息。如果执行成功,返回消息会包含"+OK [Success Message]";失败的话,会有"-ERR [Error Message]"的内容。

 

executeAsync

executeAsync($app[, $arg][, $uuid])

        和execute功能一样,差别是执行是异步的,不等待服务端的应答。底层调用execute()时,会在消息中加上"async: true"报头,以此达到异步执行的目的。

 

setAsyncExecute

setAsyncExecute($value)

        强制一条socket连接进入异步模式。这个命令对在拨号方案中设置为"async"的outbound socket连接,和无效inbound socket连接是无效的。因为这些连接已经开启异步模式了。

$value 值为 1开启强制异步模式,0不强制。

        具体来说,调用setAsyncExecute(1)的操作方式是后续的所有execute()调用中,向通道发的消息都会携带"async: true"报头。其它ESL进程不受这个调用的影响。

 

setEventLock

setEventLock($value)

        强制一条socket连接进入同步模式。这个命令对拨号方案中没有设置"async"的outbound socket连接无效,因为这些连接已经是同步模式的。

$value 值为1开启强制同步模式,0不强制。

        具体来说,调用setEventLock(1)的操作方式是在后续的所有execute()调用中,向通道发的消息都会携带"event-lock: true"报头。其它ESL进程不受这个调用影响。

 

disconnect

disconnect()

关闭与FreeSWITCH 服务器间的socket连接。

 

安装ESL

        缺省条件下不安装ESL。你必须明确安装它,或编译它的源码。

        如果你是安装Debian的软件包,那么你需要安装libesl-perl、python-esl、freeswitch-mod-perl、freeswitch-mod-python,等等。

        如果你是自己编译源码,你可以到http://freeswitch.org/c onfluence搜索"Event Socket Library",以获取最新的依赖关系和安装指南。

 

 

ESL: Perl语言示例

        让我们看一个Perl实现的ESL实例,了解一下怎样连接FreeSWITCH服务器的事件系统并与之交互。

 

显示筛选后的事件

        一个命令行实用小工具,它连接FreeSWITCH,订阅所有事件(不要在繁忙的服务器上),然后只显示选定的类型,并根据选定的头中的值进行进一步筛选。

 
  1.  
    #!/usr/bin/perl require ESL;
  2.  
     
  3.  
    my $type = ".*"; my $header = ".*"; my $value = ".*";
  4.  
     
  5.  
     
  6.  
    #$type = "CUSTOM|CHANNEL.*|MESSAGE";
  7.  
    #$header = "Core-UUID|Event-Subclass|Channel-State|Channel-Call-State";
  8.  
    #$value = "HANGUP|ACTIVE|DOWN|CS_EXECUTE";
  9.  
     
  10.  
     
  11.  
    my $con = new ESL::ESLconnection("localhost", "8021", "ClueCon");
  12.  
    $con->events("plain", "all");
  13.  
     
  14.  
     
  15.  
    while($con->connected()) { my $e = $con->recvEvent(); if ($e) {
  16.  
    if(! ($e->getType() =~ /^($type)$/) ) { next;
  17.  
    }
  18.  
     
  19.  
    printf "Type: [%s]\n", $e->getType(); my $h = $e->firstHeader();
  20.  
     
  21.  
    while ($h) {
  22.  
    if( ( $h =~ /^($header)$/) && ($e->getHeader($h) =~ /^($value)$/) ){ printf "Header: [%s] = [%s]\n", $h, $e->getHeader($h);
  23.  
    }
  24.  
     
  25.  
    $h = $e->nextHeader();
  26.  
    }
  27.  
     
  28.  
    printf "Body: [%s]\n\n", $e->getBody;
  29.  
    }
  30.  
    }
 

 

使用 "api" and "bgapi"

      让我们记住:"api"会阻塞并等待结果;"bgapi"会立即返回,并给你一个uuid,后续你可以用它识别对应的结果。

 
  1.  
    #!/usr/bin/perl require ESL;
  2.  
     
  3.  
    my $con = ESL::ESLconnection->new("localhost", "8021", "ClueCon");
  4.  
     
  5.  
    $con->api('chat',
  6.  
    "sip|1011\@lab.opentelecomsolutions.com|1001\@lab.opentelecomsolutions.com| ciao a tutti1|text/plain");
  7.  
     
  8.  
    $con->bgapi('chat', "sip|1011\@lab.opentelecomsolutions.com|1001\@lab.opentelecomsolutions.com| ciao a tutti2|text/plain");
  9.  
     
  10.  
     
  11.  
     
 


使用 "sendEvent"

怎样触发一个事件:

 
  1.  
    #!/usr/bin/perl require ESL;
  2.  
     
  3.  
    my $con = ESL::ESLconnection->new("localhost", "8021", "ClueCon"); my $e = ESL::ESLevent->new("SEND_MESSAGE");
  4.  
     
  5.  
     
  6.  
    $e->addHeader("Content-Type", "text/plain");
  7.  
    $e->addHeader("User", '1001');
  8.  
    $e->addHeader("profile", 'internal');
  9.  
    $e->addHeader("host", 'lab.opentelecomsolutions.com');
  10.  
    $e->addBody("ciao a tutti3");
  11.  
    $con->sendEvent($e);
 

总结

        这一章,我们看到了如何以最大可能控制使用FreeSWITCH:我们利用它的神经系统,进入它内部的“物联网”;我们可以把它的特性当成实时工具箱。

        有两种方式可以连接事件系统:inbound和outbound,自我补充:同时监控管理多个呼叫的客户端使用inbound模式;需要通过你的服务应用控制某路通话,那就用outbound模式。

        我们只是触及了表面,但感觉是正确的:我们实际上可以用这种方式做最复杂和疯狂的事情。不走寻常路。

        下一章,我们将看到另一种在更高抽象层面上控制FreeSWITCH的方式:mod_httapi允许你的应用程序通过HTTP与FreeSWITCH交互。