Boost ASIO, asynchrone Responses



  • ich will einen Simulator für eine Test-Hardware(properitär, keine Doku) schreiben
    dem man per TCP/IP Anfragen sendet und Ergebnisse erhält

    basierende auf dem ASIO C++03 Chat-Server Beispiel:
    http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/example/cpp03/chat/chat_message.hpp
    http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/example/cpp03/chat/chat_server.cpp

    Befehle (zur vereinfachung als String-Kommandos - bei mir sind es aber Binärpakete)
    request -> response
    "job;create;name" -> "job;create;status"
    "job;read;name;size" -> "job;read;data;status"
    "job;write;name;size;data" -> "job;write;status"
    "job;delete;name" -> "job;delete;status"

    jeder Befehl hat:
    -ein eigenes Request und ein Response Paket (mit max 512 Bytes)
    -einen eindeutige Job-Nr im Request/Response

    es koennen bis zu 10 Requests "gleichzeitig" am Server anstehen (bei mehr muss ich "blockieren" bis mind. 1 laufender Request abgearbeitet ist)
    ich wuerde gerne mit statische Puffern arbeiten (so wenig Heap allokationen, Latenz wie moeglich) soll auch als Belastungstest für
    die Firmware (des Clients) fungieren

    also z.B. das kommt per handle_read_body an den Server

    requests
    t=0ms: "0;create;name1" (dauer 2 sekunden)
    t=100ms: "1;read;name2;2" (dauer 1 sekunde)
    t=200ms: "2;create;name3" (dauer 3 sekunde)
    t=300ms: "3;write;name3;2;{1,2}" (dauer 1 sekunde)
    ...

    und so(ungefähr) die Antworten an den Client

    response:
    t=1100ms: "1;read;data;status"
    t=1300ms: "3;job;write;status"
    t=2000ms: "0;create;status"
    t=3200ms: "2;create;status"

    nun zu meiner Frage:

    so wie ich das verstehe brauche ich nur einen Read-Puffer (so wie im Chat-Beispiel) aber n Write-Puffer für die Antworten

    in handle_read_body kann ich über die Job-Nr einen Puffer für meine Antwort belegen (Liste mit freien Puffern, mit Job-Nr belege usw.)
    und hier könnte ich auch blockieren wenn schon 10 Requests anstehen (Requests sammeln, verwerfen...)
    und ich würde dann, nach Abschluss des Jobs, den belegten Puffer in handle_write wieder fuer andere verfuegbar machen - da fehlt mir im handle_write aber die Job-Nr
    hat jemand eine Idee wie ich das mit ASIO lösen kann?



  • eine Idee die ich noch hatte war das ich beim Response async_write keine allgemeinen sondern einen Job-Spezifischen handle_write-Methode anmelde

    -----

    job_t
    {
      int allocated_for_job;
    
      void handle_write(const boost::system::error_code& error)
      {
        allocated_for_job = 0; // freigeben
      } 
    
      puffer[512]
    };
    
    job_t g_jobs[max_jobs = 10];
    

    -----

    ::handle_read_body
    1. request erkennen
    2. freier job-platz my_job aus g_jobs(=> allocated_for_job == 0);
    3. response in my_job.puffer zusammebauen
    4. write response
    boost::asio::async_write(socket_,
    boost::asio::buffer(my_job.puffer),
    (my_job.puffer.length()),
    boost::bind(&job_t::handle_write, my_job,
    boost::asio::placeholders::error));

    ist das was?



  • Gast3 schrieb:

    da fehlt mir im handle_write aber die Job-Nr

    Kannst du nicht die Job-Nr. beim async_write(..) an den Completion-Handler binden?

    Von der Idee her so:

    void do_write()
    {
      auto job_number = ...
    
      boost::asio::async_write(.., ...,
        std::bind(&handle_write, std::placeholders::_1, std::placeholders::_2, job_number))
    }
    
    void handle_write(const boost::system::error_code& ec, std::size_t bytes_transferred, int job_number)
    {
      // ..
    }
    

    Natürlich können do_write(..) und handle_write(..) auch eine Member-Funktionen sein.



  • Kannst du nicht die Job-Nr. beim async_write(..)
    an den Completion-Handler binden?

    geht auch

    es könnte aber sein das ich gleich einen spezifischen Job fuer den Request machen der dann auch die ganze Semantik (welche Daten, wie sieht der Response aus usw.) versteht

    ich hätte noch eine Frage zu dem freien Job(Platz) finden/freigeben

    muss ich das threadsafe machen oder ist das wenn ich das Finden im handle_read_body und das Freigeben im handle_write machen schon durch das Design von ASIO gegeben?



  • Das hängt davon ab, welche Strategie du wählst. Das einfachste ist es io_service.run() von genau einem Thread aus aufzurufen - das führt dazu, dass alle Handler (die von diesem io_service verarbeitet werden) auf eben diesem einen Thread aufgerufen werden. So ist keine extra Synchronisation nötig. Es ist die Strategie 1 io_service mit 1 Thread. Sobald 1 io_service mit mehreren Threads betrieben wird musst du synchronisieren - z. B. mit den strands von Boost.Asio, die dafür sorgen, dass die zu einem strand zugehörigen Handler nie gleichzeitig aufgerufen werden.



  • Das hängt davon ab, welche Strategie du wählst. Das einfachste ist es io_service.run() von genau einem Thread aus aufzurufen - das führt dazu, dass alle Handler (die von diesem io_service verarbeitet werden) auf eben diesem einen Thread aufgerufen werden. So ist keine extra Synchronisation nötig.

    so mache ich es gerade - also keine Synchronisation

    viele Dank für deine Hilfe

    und zu strands habe ich noch folgenden gefunden: http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/tutorial/tuttimer5.html

    The single threaded approach is usually the best place to start when developing applications using asio. The downside is the limitations it places on programs, particularly servers, including:

    Poor responsiveness when handlers can take a long time to complete.
    An inability to scale on multiprocessor systems.


Log in to reply