如何解决Asterisk AMI获取活跃呼叫与通道数的异常问题?
Asterisk AMI 获取活跃呼叫与通道数问题解决方案
问题分析
- 事件未订阅:默认登录AMI后,服务端不会主动推送
ChannelStateChange等通道相关事件,必须显式发送订阅指令。 - 事件监听目标错误:当前代码监听的
CoreShowChannel是主动查询CoreShowChannels后的返回条目,而非实时通道状态变更事件;实时状态变更需监听ChannelStateChange事件。 - 事件解析逻辑缺陷:AMI事件以「多行内容+空行」为分隔单位,逐行扫描的方式无法完整捕获事件上下文,导致
ChannelStateDesc字段解析失败。
解决方案
方案1:实时事件监听(推荐)
通过订阅ChannelStateChange事件实现实时状态更新:
步骤1:订阅通道事件
登录AMI成功后,立即发送事件订阅指令,指定监听通道相关事件:
// 登录后添加事件订阅 fmt.Fprintf(conn, "Action: Events\r\nEventMask: Channel\r\n\r\n")
EventMask: Channel会订阅所有通道生命周期相关事件,包括ChannelStateChange。
步骤2:修正事件收集逻辑
修改server.go的扫描逻辑,将多行事件内容收集完整后再解析:
func ConnectToAMI(address, username, password string) error { conn, err := net.Dial("tcp", address) if err != nil { return err } defer conn.Close() // 登录请求 fmt.Fprintf(conn, "Action: Login\r\nUsername: %s\r\nSecret: %s\r\n\r\n", username, password) // 订阅通道事件 fmt.Fprintf(conn, "Action: Events\r\nEventMask: Channel\r\n\r\n") peerStatus := &data.PeerStatus{} callStatus := &data.CallStatus{} var eventBuffer []string scanner := bufio.NewScanner(conn) for scanner.Scan() { line := scanner.Text() fmt.Println(line) if line == "" { // 空行代表一个事件结束,开始解析 if len(eventBuffer) > 0 { eventContent := strings.Join(eventBuffer, "\r\n") switch { case strings.Contains(eventContent, "Event: PeerStatus"): data.GetPeerStatus(eventContent, peerStatus) fmt.Printf("Active peers: %d | Inactive peers: %d\n", peerStatus.Active, peerStatus.Inactive) case strings.Contains(eventContent, "Event: ChannelStateChange"): data.GetChannelStatus(eventContent, callStatus) fmt.Printf("Active calls: %d | Active channels: %d\n", callStatus.ActiveCalls, callStatus.ActiveChannels) } eventBuffer = nil } } else { eventBuffer = append(eventBuffer, line) } } return scanner.Err() }
步骤3:优化通道状态解析
修正data.GetChannelStatus逻辑,确保正确提取ChannelStateDesc,同时建议通过Uniqueid跟踪通道避免重复计数:
func GetChannelStatus(event string, callStatus *CallStatus) { var state string var uniqueID string for _, line := range strings.Split(event, "\r\n") { switch { case strings.HasPrefix(line, "ChannelStateDesc: "): state = strings.TrimSpace(strings.TrimPrefix(line, "ChannelStateDesc: ")) case strings.HasPrefix(line, "Uniqueid: "): uniqueID = strings.TrimSpace(strings.TrimPrefix(line, "Uniqueid: ")) } } // 建议维护map记录每个通道的当前状态,避免重复加减 // 示例:if state == "Up" && !channelMap[uniqueID] { ... } callStatus.UpdateCallStatus(state) }
方案2:主动查询(CoreShowChannelsComplete)
若需要一次性获取当前通道/呼叫总数,可使用CoreShowChannels指令,通过CoreShowChannelsComplete事件直接获取统计值:
步骤1:发送查询指令
在登录后或定时发送CoreShowChannels请求:
fmt.Fprintf(conn, "Action: CoreShowChannels\r\n\r\n")
步骤2:解析返回结果
在事件处理逻辑中添加CoreShowChannelsComplete解析:
// 在事件解析的switch中添加 case strings.Contains(eventContent, "Event: CoreShowChannelsComplete"): var totalChannels, activeChannels int for _, line := range strings.Split(eventContent, "\r\n") { switch { case strings.HasPrefix(line, "TotalChannels: "): fmt.Sscanf(strings.TrimSpace(strings.TrimPrefix(line, "TotalChannels: ")), "%d", &totalChannels) case strings.HasPrefix(line, "ActiveChannels: "): fmt.Sscanf(strings.TrimSpace(strings.TrimPrefix(line, "ActiveChannels: ")), "%d", &activeChannels) } } callStatus.ActiveChannels = activeChannels // 活跃呼叫数通常为活跃通道数的一半(双向呼叫场景),需根据实际业务调整 callStatus.ActiveCalls = activeChannels / 2 fmt.Printf("Active calls: %d | Active channels: %d\n", callStatus.ActiveCalls, callStatus.ActiveChannels)
关键注意事项
- AMI权限配置:确保
manager.conf中给当前用户配置了足够权限,例如read = channel,core - 重复计数规避:实时事件监听时,必须通过
Uniqueid跟踪每个通道的状态,避免同一通道多次触发导致计数偏差 - 版本兼容性:不同Asterisk版本的AMI事件字段可能存在差异,建议参考对应版本官方文档
内容的提问来源于stack exchange,提问作者Vedo
相关产品推荐
相关产品推荐

