8 Stimmen

Wie man eine flexible Erlang-Protokollstack-Erstellungs-API entwirft

Unzufrieden mit meinem derzeitigen Ansatz versuche ich gerade, die Art und Weise, wie ich Protokollstapel in Erlang aufbaue, neu zu gestalten. Die Funktion nach Wichtigkeit geordnet:

  1. Leistung

  2. Flexibilität und Implementierungsgeschwindigkeit beim Hinzufügen neuer Protokollvarianten

  3. Es würde der Entwicklung helfen, die Protokollvarianten von der Shell aus zu erkunden

Mein derzeitiges Modell ( wie in dieser Frage bereits beschrieben ) an seine Grenzen stößt, abgesehen von der hässlichen Asymmetrie von Senden() per Funktionsaufruf und Empfangen per Nachricht.

Das Gesamtbild der gesamten Protokollmaschine sieht folgendermaßen aus:

Unterer Teil:

  • Es gibt mehrere Ports oder vielleicht auch manchmal ein gen_tcp am Ende jedes Stacks (es gibt mehrere identische Stacks für unabhängige Kanäle, also können wir hier nicht zu statisch sein und nur Prozesse registrieren, wir müssen überall Pids übergeben.

  • Zusätzlich zu den Ports gibt es einige Module, die von einem Supervisor verwaltet werden (der mit dem System gestartet wird und bei Abwesenheit von Fehlern die ganze Lebensdauer über erhalten bleibt).

Oberer Teil:

  • Ausgelöst durch das Auftreten von Ereignissen (im allgemeinen Sinn, nicht im event_handler-Sinn) sind verbindungsorientierte Protokollenden (z. B. mit connect() y close() Semantik.

  • Das obere Ende des Protokollstapels kann wahrscheinlich nur dynamisch gestartet werden, da die übereinander gestapelten Module, die den Stapel bilden, dynamisch konfigurierbar sind und sich von Verbindung zu Verbindung ändern können.

  • Derzeit geplant wäre die Übergabe einer Liste von Modulnamen + optionalen Parametern aus der Toplevel, die beim connect() auf dem Stapel aufgerufen wird.

  • Die Prozesse auf der obersten Ebene werden miteinander verknüpft, so dass, wenn hier etwas schief geht, die gesamte Verbindung ausfällt.

Arten von Modulen und Arten der Kommunikation zwischen ihnen

Es gibt verschiedene Arten von Modulen, die bisher gefunden wurden:

  • Zustandslose Filtermodule

  • Module mit Status, einige passen zu gen_server, einige zu gen_fsm, aber die meisten werden wahrscheinlich einfache Serverschleifen sein, da selektiver Empfang oft nützlich ist und den Code vereinfacht.

Arten der Kommunikation zwischen Schichten:

  • Unabhängiges Senden und Empfangen von Paketen (von außen gesehen unabhängig)

  • Synchrone Aufrufe, die etwas senden, blockieren, bis eine Antwort erfolgt, und dann das Ergebnis als Rückgabewert zurückgeben.

  • Multiplexer, die mehrere Module ansprechen (das ist meine Definition, um die Diskussion zu erleichtern)

  • Demultiplexer, die verschiedene Befestigungspunkte (derzeit nach Atomen benannt) haben, um mit nach oben gerichteten Modulen zu kommunizieren.

Derzeit befinden sich meine einzigen Demultiplexer im statischen unteren Teil des Stapels und nicht im dynamisch erzeugten oberen Teil. Multiplexer befinden sich derzeit nur im oberen Teil.

In den Antworten und Kommentaren zu der von mir verlinkten Frage habe ich gehört, dass die API im Allgemeinen nur aus Funktionen und nicht aus Nachrichten bestehen sollte, und ich stimme dem zu, sofern ich nicht vom Gegenteil überzeugt bin.

Bitte entschuldigen Sie die langatmige Erklärung des Problems, aber ich denke, es ist immer noch von allgemeinem Nutzen für alle Arten von Protokollimplementierungen.

Ich werde in den Antworten schreiben, was ich bisher geplant habe und auch die daraus resultierende Implementierung und meine Erfahrungen damit später erläutern, um hier etwas allgemein Nützliches zu erreichen.

2voto

Peer Stritzinger Punkte 8052

Ich werde das, was ich bisher geplant habe, als Teil der Antworten einbringen:

  • connect wird eine Liste von Modulen übergeben, die wie eine Propliste aussieht, wenn es sich um Parameter handelt, z.B:

    connect([module1, module2, {module3, [params3]}], param0, further_params)

    Jede Schicht zieht den Kopf ab und ruft die nächsten Schichten auf, sich zu verbinden.

  • connect() "gibt irgendwie" lustige Referenzen nach oben und/oder unten weiter

    • send für asynchrones Senden auf dem Stapel wird von der untergeordneten Verbindungsebene zurückgegeben
    • recv für den asynchronen Empfang auf dem Stapel wird als Parameter an die untergeordnete Ebene connect übergeben
    • Aufruf zum Senden der Synchronisierung und Warten auf eine Antwort -- ich bin nicht sicher, wie diese zu behandeln sind, wahrscheinlich auch von der niedrigeren Ebene connect zurückgegeben
  • Multiplexer-Routinglisten könnten wie folgt aussehen

    connect([module1, multiplexer, [[m_a_1, m_a_2, {m_a_3, [param_a_3]}], 
                                    [m_b_1, m_b_2],
                                    [{m_c_1, [param_c_1]}, m_c_2]], param0, 
                                                                    further_params]).

Derzeit habe ich beschlossen, dass es keine extra Funktion für synchrone Aufrufe geben wird, ich verwende einfach send dafür.

Es gibt ein Beispiel für die Umsetzung der Idee in diesem Fall für ein Modul, das zustandslos ist: encode/1 y decode/1 einige Vorwärts- und Rückwärtstransformationen an Paketen vornehmen, z. B. die binäre Darstellung in einen Datensatz parsen und zurück:

connect(Chan, [Down|Rest], [], Recv_fun) ->
    {Down_module, Param} = case Down of
                               {F, P} -> {F, P};
                               F when is_atom (F) -> {F, []}
                           end,
    Send_fun = Down_module:connect(Chan, Rest, Param,
                                   fun(Packet) -> recv(Packet, Recv_fun) end),
    {ok, fun(Packet) -> send(Packet, Send_fun) end}.

send(Packet, Send_fun) ->
    Send_fun(encode(Packet)).

recv(Packet, Recv_fun) ->
    Recv_fun(decode(Packet)).

Sobald ich ein zustandsorientiertes Beispiel habe, werde ich es auch veröffentlichen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X