Twisted / Starpy FastAgi 应用仅正确处理一个请求

2 投票
1 回答
1212 浏览
提问于 2025-04-17 15:50

我正在开发一个FastAgi应用,用于实现Asterisk的IVR(交互式语音应答),使用的是Starpy和Twisted。目前这个应用在只有一个电话打进来的时候运行得很好。

但是,当第二个电话接入后,Asterisk发来的所有后续响应都只发送给第二个电话:第一个电话的音频流却在第二个电话上被听到,Hangup()命令会断开第二个电话(第一个电话则保持连接,直到从软电话手动断开)。

我使用的是X-lite软电话,搭配Elastix系统。我的FastAgi服务器是在一台Windows笔记本上。为了路由电话,我在Extensions_custom.conf文件中添加了以下内容:

exten => 2000,1,AGI(agi://10.0.0.7:4573)
exten => 2000,n,Hangup()

这是我在服务器上设置的协议:

logging.basicConfig()
fastagi.log.setLevel( logging.DEBUG )
f = fastagi.FastAGIFactory(MyIVRApplication())
reactor.listenTCP(4573, f, 50, '10.0.0.167') 
reactor.run()

我使用Starpy提供的一个示例来构建我的应用,叫做DialPlan(),这个应用只是播放应用被访问的次数。即使是运行这个示例应用时,也出现了同样的问题,只能正确处理一个电话。

我尝试过无线网络,但使用有线网络时也是同样的情况。我试过在虚拟机中运行Asterisk(在VirtualBox里)和在实体机器上,结果都是一样。我还在一部安卓手机上安装了不同的软电话,结果也是如此。我还使用了一部实体IP电话,结果依旧。唯一没尝试的就是把我的FastAgi服务器换到一台Linux机器上,而不是用我的Windows笔记本。

任何帮助都将不胜感激。

提前感谢。

Hector

编辑:

我添加了来自Asterisk的AGI调试日志。从中可以看出,在第二个电话接入后,所有的发送和接收命令都是从第二个通道发送/接收的。Asterisk再也没有与第一个通道进行通信,而第一个通道保持连接。

== Using SIP RTP TOS bits 184
== Using SIP RTP CoS mark 5
-- Executing [2000@from-internal:1] AGI("SIP/4001-00000006", "agi://10.0.0.167") in new stack
AGI Tx >> agi_network: yes
<SIP/4001-00000006>AGI Tx >> agi_request: agi://10.0.0.167
<SIP/4001-00000006>AGI Tx >> agi_channel: SIP/4001-00000006
<SIP/4001-00000006>AGI Tx >> agi_language: en
<SIP/4001-00000006>AGI Tx >> agi_type: SIP
<SIP/4001-00000006>AGI Tx >> agi_uniqueid: 1360854557.6
<SIP/4001-00000006>AGI Tx >> agi_version: 1.8.11.0
<SIP/4001-00000006>AGI Tx >> agi_callerid: 4001
<SIP/4001-00000006>AGI Tx >> agi_calleridname: device
<SIP/4001-00000006>AGI Tx >> agi_callingpres: 0
<SIP/4001-00000006>AGI Tx >> agi_callingani2: 0
<SIP/4001-00000006>AGI Tx >> agi_callington: 0
<SIP/4001-00000006>AGI Tx >> agi_callingtns: 0
<SIP/4001-00000006>AGI Tx >> agi_dnid: 2000
<SIP/4001-00000006>AGI Tx >> agi_rdnis: unknown
<SIP/4001-00000006>AGI Tx >> agi_context: from-internal
<SIP/4001-00000006>AGI Tx >> agi_extension: 2000
<SIP/4001-00000006>AGI Tx >> agi_priority: 1
<SIP/4001-00000006>AGI Tx >> agi_enhanced: 0.0
<SIP/4001-00000006>AGI Tx >> agi_accountcode:
<SIP/4001-00000006>AGI Tx >> agi_threadid: -1219851376
<SIP/4001-00000006>AGI Tx >>
<SIP/4001-00000006>AGI Rx << ANSWER
<SIP/4001-00000006>AGI Tx >> 200 result=0
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/bienvenida" '' 0
    -- Playing 'custom/bienvenida' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=24402
<SIP/4001-00000006>AGI Rx << GET DATA "custom/cuando_este_listo" 5000.0 1
    -- <SIP/4001-00000006> Playing 'custom/cuando_este_listo.slin' (language 'en')
<SIP/4001-00000006>AGI Tx >> 200 result=
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/14" '' 0
    -- Playing 'custom/14' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=7028
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/menos" '' 0
    -- Playing 'custom/menos' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=6762
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/9" '' 0
    -- Playing 'custom/9' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=5666
<SIP/4001-00000006>AGI Rx << GET DATA "" 5000.0 2
  == Using SIP RTP TOS bits 184
  == Using SIP RTP CoS mark 5
    -- Executing [2000@from-internal:1] AGI("SIP/4002-00000007", "agi://10.0.0.167") in new stack
AGI Tx >> agi_network: yes
<SIP/4002-00000007>AGI Tx >> agi_request: agi://10.0.0.167
<SIP/4002-00000007>AGI Tx >> agi_channel: SIP/4002-00000007
<SIP/4002-00000007>AGI Tx >> agi_language: en
<SIP/4002-00000007>AGI Tx >> agi_type: SIP
<SIP/4002-00000007>AGI Tx >> agi_uniqueid: 1360854568.7
<SIP/4002-00000007>AGI Tx >> agi_version: 1.8.11.0
<SIP/4002-00000007>AGI Tx >> agi_callerid: 4002
<SIP/4002-00000007>AGI Tx >> agi_calleridname: device
<SIP/4002-00000007>AGI Tx >> agi_callingpres: 0
<SIP/4002-00000007>AGI Tx >> agi_callingani2: 0
<SIP/4002-00000007>AGI Tx >> agi_callington: 0
<SIP/4002-00000007>AGI Tx >> agi_callingtns: 0
<SIP/4002-00000007>AGI Tx >> agi_dnid: 2000
<SIP/4002-00000007>AGI Tx >> agi_rdnis: unknown
<SIP/4002-00000007>AGI Tx >> agi_context: from-internal
<SIP/4002-00000007>AGI Tx >> agi_extension: 2000
<SIP/4002-00000007>AGI Tx >> agi_priority: 1
<SIP/4002-00000007>AGI Tx >> agi_enhanced: 0.0
<SIP/4002-00000007>AGI Tx >> agi_accountcode:
<SIP/4002-00000007>AGI Tx >> agi_threadid: -1220097136
<SIP/4002-00000007>AGI Tx >>
<SIP/4002-00000007>AGI Rx << ANSWER
<SIP/4002-00000007>AGI Tx >> 200 result=0
<SIP/4002-00000007>AGI Rx << STREAM FILE "custom/bienvenida" '' 0
    -- Playing 'custom/bienvenida' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result= (timeout)
<SIP/4002-00000007>AGI Rx << STREAM FILE "custom/respuesta_incorrecta" '' 0
    -- Playing 'custom/respuesta_incorrecta' (escape_digits='') (sample_offset 0)
<SIP/4002-00000007>AGI Tx >> 200 result=0 endpos=14260
<SIP/4002-00000007>AGI Rx << GET DATA "custom/cuando_este_listo" 5000.0 1
    -- <SIP/4002-00000007> Playing 'custom/cuando_este_listo.slin' (language 'en')
<SIP/4002-00000007>AGI Tx >> 200 result=
    -- <SIP/4002-00000007>AGI Script agi://10.0.0.167 completed, returning 0
<SIP/4002-00000007>AGI Tx >> HANGUP
    -- Executing [2000@from-internal:2] Hangup("SIP/4002-00000007", "") in new stack
  == Spawn extension (from-internal, 2000, 2) exited non-zero on 'SIP/4002-00000007'
    -- Executing [h@from-internal:1] Macro("SIP/4002-00000007", "hangupcall") in new stack
    -- Executing [s@macro-hangupcall:1] GotoIf("SIP/4002-00000007", "1?endmixmoncheck") in new stack
    -- Goto (macro-hangupcall,s,9)
    -- Executing [s@macro-hangupcall:9] NoOp("SIP/4002-00000007", "End of MIXMON check") in new stack
    -- Executing [s@macro-hangupcall:10] GotoIf("SIP/4002-00000007", "1?nomeetmemon") in new stack
    -- Goto (macro-hangupcall,s,15)
    -- Executing [s@macro-hangupcall:15] NoOp("SIP/4002-00000007", "MEETME_RECORDINGFILE=") in new stack
    -- Executing [s@macro-hangupcall:16] GotoIf("SIP/4002-00000007", "1?noautomon") in new stack
    -- Goto (macro-hangupcall,s,18)
    -- Executing [s@macro-hangupcall:18] NoOp("SIP/4002-00000007", "TOUCH_MONITOR_OUTPUT=") in new stack
    -- Executing [s@macro-hangupcall:19] GotoIf("SIP/4002-00000007", "1?noautomon2") in new stack
    -- Goto (macro-hangupcall,s,25)
    -- Executing [s@macro-hangupcall:25] NoOp("SIP/4002-00000007", "MONITOR_FILENAME=") in new stack
    -- Executing [s@macro-hangupcall:26] GotoIf("SIP/4002-00000007", "1?skiprg") in new stack
    -- Goto (macro-hangupcall,s,29)
    -- Executing [s@macro-hangupcall:29] GotoIf("SIP/4002-00000007", "1?skipblkvm") in new stack
    -- Goto (macro-hangupcall,s,32)
    -- Executing [s@macro-hangupcall:32] GotoIf("SIP/4002-00000007", "1?theend") in new stack
    -- Goto (macro-hangupcall,s,34)
    -- Executing [s@macro-hangupcall:34] Hangup("SIP/4002-00000007", "") in new stack
  == Spawn extension (macro-hangupcall, s, 34) exited non-zero on 'SIP/4002-00000007' in macro 'hangupcall'
  == Spawn extension (from-internal, h, 1) exited non-zero on 'SIP/4002-00000007'
localhost*CLI>

编辑:

这是我用作参考的代码。它是Starpy库中的一个示例应用。当我使用这个应用时,得到的结果也是一样。我放这个是因为我的应用有点太大了。

#! /usr/bin/env python
"""Read digits from the user in various ways..."""
from twisted.internet import reactor, defer
from starpy import fastagi, error
import logging, time

log = logging.getLogger( 'hellofastagi' )

class DialPlan( object ):
    """Stupid little application to report how many times it's been accessed"""
    def __init__( self ):
        self.count = 0
    def __call__( self, agi ):
        """Store the AGI instance for later usage, kick off our operations"""
        self.agi = agi 
        return self.start()
    def start( self ):
        """Begin the dial-plan-like operations"""
        return self.agi.answer().addCallbacks( self.onAnswered, self.answerFailure )
    def answerFailure( self, reason ):
        """Deal with a failure to answer"""
        log.warn( 
            """Unable to answer channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )
        self.agi.finish()
    def onAnswered( self, resultLine ):
        """We've managed to answer the channel, yay!"""
        self.count += 1
        return self.agi.wait( 2.0 ).addCallback( self.onWaited )
    def onWaited( self, result ):
        """We've finished waiting, tell the user the number"""
        return self.agi.sayNumber( self.count, '*' ).addErrback(
            self.onNumberFailed,
        ).addCallbacks(
            self.onFinished, self.onFinished,
        )
    def onFinished( self, resultLine ):
        """We said the number correctly, hang up on the user"""
        return self.agi.finish()
    def onNumberFailed( self, reason ):
        """We were unable to read the number to the user"""
        log.warn( 
            """Unable to read number to user on channel %r: %s""",
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )

    def onHangupFailure( self, reason ):
        """Failed trying to hang up"""
        log.warn( 
            """Unable to hang up channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )

if __name__ == "__main__":
    logging.basicConfig()
    fastagi.log.setLevel( logging.DEBUG )
    f = fastagi.FastAGIFactory(DialPlan())
    reactor.listenTCP(4573, f, 50, '10.0.0.167') # only binding on local interface
    reactor.run()

1 个回答

1

谢谢你提供的详细信息:

现在你这个设置明显有个问题:

Dialplan() 是在第一次传给 FastAGIFactory 的时候初始化的。从那以后,你一直在重复使用同一个类的实例。每当有新的电话进来时,Dialplan().__call__() 方法就会被调用,而 Dialplan().agi 这个参数会被最后一次的调用覆盖。

根据你使用的示例,我建议你尝试以下方法,然后再继续:

#! /usr/bin/env python
"""Read digits from the user in various ways..."""
from twisted.internet import reactor, defer
from starpy import fastagi, error
import logging, time

log = logging.getLogger( 'hellofastagi' )




class DialPlan( object ):
    """Stupid little application to report how many times it's been accessed"""
    def __init__( self,application, agi ):
        self.application = application
        self.agi = agi
    def start( self ):
        """Begin the dial-plan-like operations"""
        return self.agi.answer().addCallbacks( self.onAnswered, self.answerFailure )
    def answerFailure( self, reason ):
        """Deal with a failure to answer"""
        log.warn( 
            """Unable to answer channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )
        self.agi.finish()
    def onAnswered( self, resultLine ):
        """We've managed to answer the channel, yay!"""
        return self.agi.wait( 2.0 ).addCallback( self.onWaited )
    def onWaited( self, result ):
        """We've finished waiting, tell the user the number"""
        return self.agi.sayNumber( self.application.count, '*' ).addErrback(
            self.onNumberFailed,
        ).addCallbacks(
            self.onFinished, self.onFinished,
        )
    def onFinished( self, resultLine ):
        """We said the number correctly, hang up on the user"""
        return self.agi.finish()
    def onNumberFailed( self, reason ):
        """We were unable to read the number to the user"""
        log.warn( 
            """Unable to read number to user on channel %r: %s""",
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )

    def onHangupFailure( self, reason ):
        """Failed trying to hang up"""
        log.warn( 
            """Unable to hang up channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )


class CallCounterApplication(object):
    def __init__( self ):
        self.count = 0

    def __call__(self,agi):
        self.count = self.count+1
        dp = Dialplan(self,agi)
        return dp.start()


if __name__ == "__main__":
    logging.basicConfig()
    fastagi.log.setLevel( logging.DEBUG )
    f = fastagi.FastAGIFactory(CallCounterApplication())
    reactor.listenTCP(4573, f, 50, '10.0.0.167') # only binding on local interface
    reactor.run()

我在这里做的是创建了一个应用容器,每次成功接到电话时都会创建一个新的 Dialplan() 实例。

编辑:请注意,由于我没有可用的 Asterisk 设备,这个代码没有经过测试,直接复制粘贴可能无法运行。但原理是一样的。

撰写回答