[PYTHON] Machen Sie den SIP-Server so kurz wie möglich (in der Mitte der Erklärung).

Früher war ich VoIP-Ingenieur auf der Serverseite und auf der Steuersignalseite. Wenn ich also eine neue Sprache lerne, erstelle ich zuerst einen SIP-Server.

In diesem Artikel werde ich schreiben, dass Sie, wenn Sie mindestens so viel implementieren, es als SIP-Server betreiben können, wenn der andere Teilnehmer RFC3261 ordnungsgemäß befolgt.

Die Sprache verwendet Python. Es ist 2.7, weil es ein Code ist, den ich vor ungefähr 10 Jahren geschrieben habe. Ich denke, es wäre cooler, wenn es von einem brillanten Programmierer geschrieben würde, aber es funktioniert immer noch, also lesen Sie es bitte.

Empfang des SIP-Signals

Laut RFC3261 muss TCP unterstützt werden, aber UDP muss auch unterstützt werden. Erwarten Sie daher, dass die andere Partei mit UDP spricht und nur UDP unterstützt. Zu Mit UDP müssen Sie sich im Grunde keine Gedanken über Signalunterbrechungen machen (bei TCP fließen die Daten langsam, sodass Sie genau sehen müssen, wo die Signale getrennt sind), sodass das Programm effektiv vereinfacht wird. ist.

Ich denke, dass es für die meisten Sprachen gleich ist, aber ich werde einen Socket für UDP erstellen, diesen Socket an die IP und den Port binden und dann von diesem Socket aus weiterempfehlen. Dies ist das Bild im Code.

class Proxy:

  def __init__(self, ip, port):
    self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    self.sock.bind((ip, port))
    
  def run(self):
    while True:
      buf, addr = self.sock.recvfrom(0xffff)
      #Dies enthält das von buf empfangene Signal und die Adresse der anderen Partei, die an addr gesendet wurde.

Es ist einfach. Diese Buchse wird auch für die Signalübertragung an einem anderen Ort verwendet Die Empfangspuffergröße ist auf 0xffff festgelegt, da sie aufgrund der UDP-Spezifikationen nur bis zu dieser Größe empfangen werden kann. (Da die Größe den Header-Teil des UDP-Pakets enthält, ist die Datengröße, die empfangen werden kann, etwas kleiner, aber auf einen Blick ist 0xffff leichter zu erkennen, daher ist es mir egal.)

SIP-Signalanalyse (vorerst ungefähr)

Analysieren Sie das empfangene Signal grob, um die weitere Verarbeitung zu erleichtern. Persönlich packe ich hier geheime Techniken. Nun, es kann etwas sein, an das jeder denken kann, solange er etwas recherchiert.

Da davon ausgegangen wird, dass die andere Partei das richtige SIP-Signal sendet, können Sie außerdem einen Fehler verursachen, wenn Sie darauf zielen und es senden.

Das ganze Bild ist so.

class Message:

  def __init__(self, buf):
    buf = re.sub(r'^((\r\n)|(\r)|(\n))*', "", buf)
    m = re.search(r'((\r\n\r\n)|(\r\r)|(\n\n))', buf)
    self.body = buf[m.end():]
    buf = re.sub(r'\n[ \t]+',' ', re.sub(r'\r\n?', "\n", buf[:m.start()]))
    ary = buf.split("\n")
    m = re.match(r'(([A-Z]+) ([^ ]+) )?SIP\/2\.0( (\d+) ([^\n]+))?', ary[0])
    self.method, self.requri, self.stcode, self.reason = \
      m.group(2), m.group(3), m.group(5), m.group(6)
    self.hdrs = []
    for buf in ary[1:]:
      name, buf = re.split(r'\s*:\s*', buf, 1)
      self.hdrs.append(Header(name, re.split(r'\s*,\s*', buf)))

Ich werde jeden Teil erklären.

Bei RFC3261 dürfen zu Beginn des SIP-Signals mehrere CRLFs vorhanden sein. CRLF ist entweder \ r, \ n oder \ r \ n. Dies sind keine aussagekräftigen Informationen, daher werde ich sie vorerst löschen. \ R \ n, \ r oder \ n kann als regulärer Python-Ausdruck geschrieben werden.

r'(\r\n)|(\r)|(\n)'

Außerdem schreibe ich lieber mehr Klammern als nötig, damit ich verschiedene Dinge klar verstehen kann.

Wenn es 0 oder mehr gibt

r'((\r\n)|(\r)|(\n))*'

Ich werde dich anrufen. Wenn Sie die Bedingung am Anfang hinzufügen

r'^((\r\n)|(\r)|(\n))*'

Wird sein. Wenn Sie diesen regulären Ausdruck verwenden, um buf zu ersetzen,

buf = re.sub(r'^((\r\n)|(\r)|(\n))*', '', buf)

Es wird sein.

Suchen Sie als Nächstes die Leerzeile, die den Header und den Body trennt, und teilen Sie sie in zwei Teile.

Die Leerzeile wird als zwei aufeinanderfolgende CRLFs ausgedrückt, die als regulärer Ausdruck ausgedrückt werden können.

r'((\r\n\r\n)|(\r\r)|(\n\n))'

Wenn Sie nach dieser Zeichenfolge aus dem SIP-Signal suchen und den Teil dahinter als Körper ausschneiden

m = re.search(r'((\r\n\r\n)|(\r\r)|(\n\n))', buf)
self.body = buf[m.end():]

ist geworden.

Übrigens habe ich CRLF bisher als \ r \ n, \ r oder \ n gesehen, aber es ist kein Ärger, also werde ich es hier durch \ n ersetzen.

Hätten wir es dann nicht früher ersetzen sollen? Wenn der Hauptteil jedoch Informationen enthält, die sinnvoll sind, \ r \ n zu sein, und wenn Sie die Datenlänge mithilfe des Inhaltslängen-Headers korrekt anzeigen möchten Es wäre ein Problem, wenn der Körper neu geschrieben würde. Ersetzen Sie daher nur die anderen Teile als den Körper.

Es wird durch \ n ersetzt, also \ r \ n oder \ r. Dies bedeutet, dass möglicherweise \ r gefolgt von \ n ist. Wenn Sie dies also mit einem regulären Ausdruck ausdrücken,

r'¥r¥n?'

nicht wahr.

Ich habe herausgefunden, wie viele der Daten, die kurz bevor dies der Header ist, empfangen wurden.

re.sub(r'\r\n?', '\n', buf[:m.start()])

Kann durch ersetzt werden.

Übrigens ist der SIP-Header problematisch gestaltet, so dass CRLF in die Mitte aufgenommen werden kann. Die CRLF in der Mitte hat keine Bedeutung, sie dient nur dem Aussehen. Wer würde sich darüber freuen? Dies wird kein Hindernis bei der Verarbeitung sein, daher möchte ich es irgendwie löschen. Wenn sich in der Mitte des Headers eine CRLF befindet, ist es glücklicherweise eine Regel, WSP (Leerzeichen, ein oder mehrere Leerzeichen oder Tabulatoren mit halber Breite) danach zu setzen. Da CRLF im vorherigen Prozess durch \ n ersetzt wurde, werden \ n gefolgt von einem oder mehreren Leerzeichen oder Tabulatoren durch ein einzelnes Leerzeichen mit halber Breite einschließlich dieses Leerzeichens oder Tabulators ersetzt.

Der reguläre Ausdruck ist

r'¥n[ ¥t]+'

nicht wahr.

Der Prozess der Konvertierung der CRLF, die ich zuvor in \ n geschrieben habe, wird in einer Zeile geschrieben.

buf = re.sub(r'\n[ \t]+',' ', re.sub(r'\r\n?', '\n', buf[:m.start()]))

Es wird schön sein. Nachdem der Header und die Startzeile zuvor bereits in einer durch \ n getrennten Zeile enthalten sind, teilen Sie sie in ein Array.

ary = buf.split("\n")

Es ist besser, jedes Element in diesem Bereich zu verarbeiten. Das nullte Element ist die Startlinie, und das erste und nachfolgende Element sind die Kopfzeile.

Analysieren Sie als Nächstes die Startlinie. Es gibt zwei Arten von Startzeilen, Anforderungszeilen und Statuszeilen.

Request-Line hat eine Form, in der nur Methode, Request-URI und SIP-Version mit einem Zwischenraum halber Breite ausgerichtet sind, die Methode mehrere alphabetische Zeichen der oberen und halben Breite enthält und der Request-URI verschiedene Zeichen zulässt. Leerzeichen mit mindestens halber Breite sind jedoch nicht zulässig.

r'([A-Z]+) ([^ ]+) SIP\/2\.0'

Status-Line ist eine Form, in der SIP-Version, Status-Code und Reason-Phrase mit einem Abstand von halber Breite dazwischen angeordnet sind.

r'SIP\/2\.0 (\d+) ([^\n]+)'

Mit Ausnahme des letzten Zeilenumbruchs ist der Zeilenumbruch bereits durch Teilen jedes Zeilenumbruchs verschwunden, sodass es kein Problem gibt, selbst wenn er als beliebige Zeichenfolge analysiert wird. Machen Sie keinen Fehler, wenn Sie an einer Stelle kopieren und einfügen, an der Sie nicht davon ausgehen können, dass keine Zeilenumbrüche vorliegen.

Request-Line hat am Ende die SIP-Version und Status-Line am Anfang die SIP-Version, sodass Sie alles auf einmal analysieren können.

r'(([A-Z]+) ([^ ]+) )?SIP\/2\.0( (\d+) ([^\n]+))?'

Wenn Sie damit analysieren und das Ergebnis behalten

m = re.match(r'(([A-Z]+) ([^ ]+) )?SIP\/2\.0( (\d+) ([^\n]+))?', ary[0])
self.method, self.requri, self.stcode, self.reason = m.group(2), m.group(3), m.group(5), m.group(6)

Analysieren Sie den Header weiter. Da die Rohdaten des Headers aus der ersten Zeile des Arrays mit dem Namen ary enthalten sind,

for buf in ary[1:]:
  pass #Analysieren Sie buf hier

Ich werde es so analysieren. Der Header hat den Headernamen HCOLON, gefolgt vom Headerwert. HCOLON bedeutet, dass vor und nach dem Doppelpunkt (:) möglicherweise ein Leerzeichen steht oder nicht. Teilen Sie es also zuerst durch eine Zeichenfolge mit Leerzeichen vor und nach dem Doppelpunkt.

for buf in ary[1:]:
  name, buf = re.split(r'\s*:\s*', buf, 1)

Da die Elemente des Header-Werts durch Kommas getrennt sind, trennen Sie sie dort.

re.split(r'\s*,\s*', buf)

Dies ist jedoch nicht wirklich cool und es ist fehlerhaft, wenn einige der Header-Werte Kommas in der doppelten Zeichenfolge enthalten. Sie können dies tun, indem Sie einen etwas schwierigeren regulären Ausdruck schreiben.

Erstellen Sie eine solche Klasse, um die Analyseergebnisse zu speichern

class Header:
  def __init__(self, name, vals):
    self.name, self.vals = name, vals

Ich habe mich entschlossen, das Ergebnis der Header-Analyse in diese zu setzen

self.hdrs = []
for buf in ary[1:]:
  name, buf = re.split(r'\s*:\s*', buf, 1)
  self.hdrs.append(Header(name, re.split(r'\s*,\s*', buf)))

Übrigens, wenn Sie sie alle zusammenhalten

class Message:

  def __init__(self, buf):
    buf = re.sub(r'^((\r\n)|(\r)|(\n))*', "", buf)
    m = re.search(r'((\r\n\r\n)|(\r\r)|(\n\n))', buf)
    self.body = buf[m.end():]
    buf = re.sub(r'\n[ \t]+',' ', re.sub(r'\r\n?', "\n", buf[:m.start()]))
    ary = buf.split("\n")
    m = re.match(r'(([A-Z]+) ([^ ]+) )?SIP\/2\.0( (\d+) ([^\n]+))?', ary[0])
    self.method, self.requri, self.stcode, self.reason = \
      m.group(2), m.group(3), m.group(5), m.group(6)
    self.hdrs = []
    for buf in ary[1:]:
      name, buf = re.split(r'\s*:\s*', buf, 1)
      self.hdrs.append(Header(name, re.split(r'\s*,\s*', buf)))

ist geworden.

Ein Absatz

Ich bin müde, also mache ich das nächste Mal weiter. Ich bin erschöpft und die Erklärung ist bereits angemessen, daher werde ich sie bald aktualisieren. Das fertige Produkt ist wie folgt.

https://github.com/zurustar/xylitol/blob/master/xylitol.py

Passen Sie die Priorität bei anderen Artikelaktualisierungen abhängig von der Anzahl der Likes an

Recommended Posts

Machen Sie den SIP-Server so kurz wie möglich (in der Mitte der Erklärung).
Kopieren Sie die Liste in Python
Machen Sie den Fortschritt von dd in der Fortschrittsanzeige sichtbar
In der Mitte der Entwicklung werden wir Alembic vorstellen
Machen Sie den Standardwert des Arguments unveränderlich (Artikelerklärung)
Die weltweit am einfachsten zu verstehende Erklärung, wie LINE BOT erstellt wird (3) [Zusammenarbeit mit dem Server mit Git]
Wartung der Django + MongoDB-Entwicklungsumgebung (mitten im Schreiben)
Machen Sie die Funktion zum Zeichnen japanischer Schriftarten in OpenCV allgemein
Unterdrücken Sie Seitenumbrüche in der Mitte einer Tabelle mit Sphinx Single HTML
Die Geschichte der Teilnahme an AtCoder
Die Geschichte des "Lochs" in der Akte
Die Geschichte des erneuten Bereitstellens des Anwendungsservers
Ergänzung zur Erklärung von vscode
Erläuterung und Implementierung des in Slack, HipChat und IRC verwendeten XMPP-Protokolls
Wenn Sie einen Singleton in Python möchten, stellen Sie sich das Modul als Singleton vor
[Einführung in Python] Eine ausführliche Erklärung der in Python verwendeten Zeichenkettentypen!