Interaktion mit dem Asterisk-Server über eine Java-Anwendung

Interaktion mit einem Asterisk-Server von einer Java-Anwendung über das Asterisk Managment Interface (AMI)


Wenn Sie gerade erst mit der Forschung in diesem Bereich beginnen, erscheint Ihnen die Interaktion mit diesem Server möglicherweise etwas verwirrend, wie es mir einst erschien.

Um nicht im Stil einer Antwortfrage in den Foren nach den erforderlichen Informationen zu suchen, füge ich ein kleines Tutorial zur Interaktion mit dem Asterisk-Server von Java hinzu.

Wichtig: Ich gehe davon aus, dass Sie nach dem Schreiben von Code bereits über einen funktionierenden Asterisk-Server verfügen, auf den Sie zugreifen können.

1) Was soll man wählen, um das Arbeiten bequemer zu machen?

Auf jeden Fall - Asterisk Managment Interface (AMI): Diese Schnittstelle verfügt über eine Reihe von Funktionen, mit denen Sie einen Anruf tätigen, Ereignisse vom Server in Echtzeit abhören, einen Anrufstatus empfangen und gegebenenfalls unterbrechen können.

2) Welche Bibliothek soll verbunden werden?

Dieser:

<dependency> <groupId>org.asteriskjava</groupId> <artifactId>asterisk-java</artifactId> <version>2.0.4</version> </dependency> 

3) Welche Konfigurationen müssen auf dem Server angezeigt werden?

extensions.conf - die Konfiguration, die den Diaplan beschreibt. Sie werden ihn ständig kontaktieren. In einer verständlicheren Sprache enthält es Skripte darüber, was der Server tun wird, wenn er einen Anruf an eine bestimmte Nummer erhält. Zuerst wird ein bestimmter Kontext im Diaplan durchsucht - er wird in eckige Klammern geschrieben, wonach eine Nummer unter dem Tag dieses Kontexts gesucht wird, für den Sie Kontakt aufnehmen.

manager.conf - Konfiguration mit Benutzer und Passwort für Ihren Asterisk-Server

Der Inhalt dieser Konfiguration sollte ungefähr wie folgt sein:

 [user_name] secret = password read = all write = all deny=0.0.0.0/0.0.0.0 permit=0.0.0.0/255.255.255.0 

  • Benutzername - Benutzername
  • Geheimnis - Passwort dafür
  • verweigern - IP-Adressen, denen unter diesem Benutzer der Zugriff verweigert wird
  • Erlaubnis - Zugang zu dem erlaubt ist. Stellen Sie sicher, dass Sie die IP-Adresse angeben, mit der Sie sich in der Genehmigung in Verbindung setzen, da die Aster Ihre Anfrage zurückweisen kann.

sip.conf - alle Amtsleitungen sind hier registriert. Ein Trunk ist ein Telefon, von dem aus wir einen Kunden anrufen.

4) Wo soll ich mit dem Schreiben von Code beginnen?

Es gibt zwei Möglichkeiten: Sie müssen entweder eine Aktion auf dem Asterisk-Server ausführen oder Ereignisse auf dem Server abhören. Unsere Sequenz umfasst beides.

Wir beschreiben den Aktionsplan:

  1. Öffnen Sie die Verbindung zum Server.
  2. Wir beschreiben das Arbeitsszenario;
  3. Wir hören Ereignisse;
  4. Schließen Sie die Verbindung.

Dementsprechend wird die Verbindung beim Erstellen des DefaultAsteriskServer-Objekts initialisiert:

 import org.asteriskjava.live.AsteriskServer; import org.asteriskjava.live.DefaultAsteriskServer; 

 AsteriskServer asteriskServer = new DefaultAsteriskServer(HOSTNAME, USERNAME, PASSWORD); asteriskServer.initialize(); 

Nach dem Öffnen der Verbindung müssen wir den Benutzer anrufen. Wir werden dies ein Aktionsszenario nennen. Die Beschreibung des Szenarios erfolgt in einer separaten Klasse:

 /** *    */ public class ScenarioCall extends OriginateAction { private final Logger log = LoggerFactory.getLogger(ScenarioCall.class); private String TRUNK; private final String PHONE_FOR_RINGING; private final String EXTEN_FOR_APP; private final String CONTEXT_FOR_APP; public ScenarioCall(String trunk, String phoneForRinging, String extension, String context) { this.TRUNK = trunk; this.PHONE_FOR_RINGING = phoneForRinging; this.EXTEN_FOR_APP = extension; this.CONTEXT_FOR_APP = context; this.init(); } /** *         OriginateAction */ private void init() { //  String callId = ValidValues.getValidCallId(this.PHONE_FOR_RINGING); //    String channelAsterisk = ValidValues.getValidChannel(this.TRUNK, this.PHONE_FOR_RINGING); this.setContext(CONTEXT_FOR_APP); this.setExten(EXTEN_FOR_APP); this.setPriority(1); this.setAsync(true); this.setCallerId(callId); this.setChannel(channelAsterisk); log.info("Create Scenario Call: phone '{}',chanel '{}',context '{}',extension '{}'", callId, channelAsterisk, CONTEXT_FOR_APP, EXTEN_FOR_APP); } } 

Was müssen wir in diesem Szenario verstehen? Zunächst wird eine Amtsleitungsverbindung erstellt. Eine Amtsleitung ist die Nummer, von der aus Sie den Teilnehmer anrufen. Danach wird eine Verbindung zwischen der Amtsleitung und dem Teilnehmer hergestellt, danach besteht die Verbindung zwischen dem Teilnehmer und dem, den Sie sonst noch benötigen.

Es ist in dieser Reihenfolge.

 this.setContext(CONTEXT_FOR_APP) 
Der übertragene Wert: Der Kontext, in dem wir nach der Telefonnummer suchen, mit der Sie den Teilnehmer verknüpfen möchten (von extensions.conf).

 this.setExten(EXTEN_FOR_APP) 
Übertragener Wert: Das Skript, das ausgeführt wird, nachdem Sie den Abonnenten kontaktiert haben (von extensions.conf).

 this.setCallerId(callId) 
Übertragungswert: die Nummer unseres Abonnenten

 this.setChannel(channelAsterisk) 
Übertragungswert: etablierter Kommunikationskanal, sieht normalerweise so aus: Amtsleitungsname / Telefonbenutzer.

Wo kann man nach trunk_name suchen? Auf dem Sternchen-Server befindet sich eine sip.conf-Konfiguration - alle Amtsleitungen sind dort registriert.

Anruf erstellen:

 if (asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.CONNECTED) || asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.CONNECTING) || asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.INITIAL)) { try { ScenarioCall scenarioCall = new ScenarioCall(trank, phone, extension, context); CallBack callBackForScenarioCall = new CallBack(); asteriskServer.originateAsync(scenarioCall, callBackForScenarioCall); } catch (ManagerCommunicationException e) { //   , StateConnection    RECONNECTING,     } } 

Wir haben einen Anruf erstellt, aber wie kann man ihn dynamisch verfolgen?

Hierfür werden zwei Dinge getan: Eine Instanz der CallBack-Klasse wird in der originateAsync-Methode übergeben
und ein Listener wird an den Server gehängt, der alles zusammenführt, was uns passiert.

Ein Listener wird benötigt, da die CallBack-Klasse Sie nicht über das Ende des Anrufs benachrichtigt, wenn der Benutzer bereits gesprochen hat, und Sie auch nicht darüber informiert, dass der Benutzer noch an einen anderen Ort übertragen kann.

 /** *        asteriskConnection.originateAsync  *  CallBack -     ,     *  originateAsync. CallBack      , *       ,    onNoAnswer ,  *     onBusy,     ,  onFailure,  . *        . ,      *      (      ,    ) */ public class CallBack implements OriginateCallback { /** *     PRERING,       * OriginateCallback -    */ private ChannelState resultCall = ChannelState.PRERING; /** *    ,   .         */ @Override public void onDialing(AsteriskChannel asteriskChannel) { //   ,  resultCall, //   asteriskChannel    null, //    resultCall   //   } /** *   .      *      6 - setStatus */ @Override public void onSuccess(AsteriskChannel asteriskChannel) { //   , asteriskChannel   null, // asteriskChannel.getState()      ChannelState.UP //   } /** *      ,    *      7 - setStatus () */ @Override public void onNoAnswer(AsteriskChannel asteriskChannel) { //   , //   asteriskChannel    null, //    resultCall   //   } /** *   *      7 - setStatus () */ @Override public void onBusy(AsteriskChannel asteriskChannel) { //   , //   asteriskChannel    null, //    resultCall   //   } /** *      */ @Override public void onFailure(LiveException e) { //     , //     , // onFailure      } } 

Wie hänge ich einen Hörer an ein Sternchen?

Erstellen Sie dazu eine implementierende Klasse AsteriskServerListener, PropertyChangeListener.
Für die erstellte Verbindung implementieren wir über eine Instanz der AsteriskConnection-Klasse:

  this.asteriskConnection.addAsteriskServerListener(this.callBackEventListener); 

this.callBackEventListener - eine Instanz unserer Listener-Klasse, geboren aus:

 ** *    Asterisk *  PropertyChangeListener   ,     . *  AsteriskServerListener   ,     AsteriskConnection. */ public class CallBackEventListener implements AsteriskServerListener, PropertyChangeListener { public void onNewAsteriskChannel(AsteriskChannel channel) { channel.addPropertyChangeListener(this); } public void onNewMeetMeUser(MeetMeUser user) { user.addPropertyChangeListener(this); } public void onNewQueueEntry(AsteriskQueueEntry user) { user.addPropertyChangeListener(this); } public void onNewAgent(AsteriskAgent asteriskAgent) { asteriskAgent.addPropertyChangeListener(this); } /** *    .   {@link PropertyChangeEvent} *    , *        , *        CallBack * * @param propertyChangeEvent      */ public void propertyChange(PropertyChangeEvent propertyChangeEvent) { findEventEndCall(propertyChangeEvent); } private void findEventEndCall(PropertyChangeEvent event) { if (event.getSource() instanceof AsteriskChannel) { AsteriskChannel callBackChannel = (AsteriskChannel) event.getSource(); String callId = getStringWithOnlyDigits(callBackChannel.getCallerId().toString()); callId = ValidValues.getValidCallId(callId); if (callBackChannel.getState().toString().equals("HUNGUP") && event.getOldValue().toString().contains("RINGING")) { //      callBackChannel.removePropertyChangeListener(this); //     } else if (callBackChannel.getState().toString().equals("HUNGUP") && event.getOldValue().toString().contains("UP")) { //     callBackChannel.removePropertyChangeListener(this); //     } else if (callBackChannel.getState().toString().equals("HUNGUP")) { //      callBackChannel.removePropertyChangeListener(this); //     } } } private String getStringWithOnlyDigits(String strForParse) { String result = ""; if (strForParse != null && !strForParse.isEmpty()) { CharMatcher ASCII_DIGITS = CharMatcher.anyOf("<>").precomputed(); result = ASCII_DIGITS.removeFrom(strForParse.replaceAll("[^0-9?!]", "")); } return result; } } 

Ich rate Ihnen von Anfang an, nur zu versprechen, was zu propertyChange kommt, und sich PropertyChangeEvent anzuschauen. Es wird ein verdammt großer Haufen von allem sein, was auf dem Server passiert. Es werden überhaupt keine Informationen gefiltert. Daher die Schlussfolgerung: Hängen Sie den Hörer so wenig wie möglich auf. Nicht für jeden Aufruf, da dies meines Erachtens auch in der OriginateCallback-Klasse möglich ist. Das ist nutzlos. Sehen Sie sich an, welche PropertyChangeEvent-Objekte zu Ihnen kommen, welche Art von Feldern Sie benötigen und welche Sie benötigen. Weiter - Willkommen in der Welt der Informationsverarbeitung.

Ein bisschen über Datenvalidierung.

In OriginateAction.setChannel wird trunk_name / phone_user übergeben
phone_user - Wenn es russisch ist, sollte es mit einer Zahl von acht beginnen, wenn die internationale Nummer mit einem Pluszeichen versehen ist.

In OriginateAction.setCallerId wird die Telefonnummer des Clients übertragen.
dann kommt es in CallBackEventListener in callBackChannel.getCallerId ().

Wird es so nehmen:

 String callId = getStringWithOnlyDigits(callBackChannel.getCallerId().toString()); 

Vergessen Sie am Ende nicht:

 asteriskServer.shutdown(); 

Wenn Sie einen Anruf unterbrechen müssen, dann entweder in der CallBackEventListener-Klasse
zu dem bestehenden Kommunikationskanal führen wir durch:

 callBackChannel.hangup(); 

Ein so einfaches Tutorial hat sich herausgestellt. Auf den ersten Blick ist es natürlich sehr einfach, aber glauben Sie mir, es braucht viel Zeit und Nerven, um Informationen zu finden, alle Methoden zu verschieben und die Arbeit zu verlassen.

Viel Glück mit Ihren Asterisk-Servern!

Weiterführende Literatur:

1) Asterisk-Java-Tutorial

2) Asterisk Managment Interface (AMI)

Source: https://habr.com/ru/post/de469491/


All Articles