2011-08-12 9 views
6

Obecnie mam bardzo prosty boost :: asio że serwer wysyła aktualizację statusu po podłączeniu (za pomocą Google bufory proto):Boost ASIO dla serwera synchronizacji utrzymanie sesji TCP otwarty (z google buforów proto)

try 
{ 
    boost::asio::io_service io_service; 
    tcp::acceptor acceptor(io_service,tcp::endpoint(tcp::v4(), 13)); 
    for (;;) 
    { 
    tcp::socket socket(io_service); 
    acceptor.accept(socket); 
    ... 
    std::stringstream message; 
    protoMsg.SerializeToOstream(&message); 
    boost::system::error_code ignored_error; 
    boost::asio::write(socket, boost::asio::buffer(message.str()), ignored_error); 
    } 
} 
catch (std::exception& e) { } 

Chciałbym przedłużyć go do pierwszego odczytu po zaakceptowaniu nowego połączenia, sprawdzić, jakie żądanie zostało odebrane, i wysłać różne wiadomości w zależności od tego komunikatu. Chciałbym również, aby połączenie TCP było otwarte, aby klient nie musiał się ponownie łączyć, i chciałby obsługiwać wielu klientów (niewiele, może 2 lub 3).

Spojrzałem na kilka przykładów na boost asio, a mianowicie the async time tcp server i chat server, ale oba są nieco ponad moją głową tbh. Nie rozumiem nawet, czy potrzebuję serwera asynchronicznego. Sądzę, że mogłem po prostu przeczytać po acceptor.accept(socket), ale myślę, że nie będę słuchał dalej. I jeśli wejdę w pętlę, to myślę, że oznaczałoby to, że poradzę sobie tylko z jednym klientem. Więc myślę, że to oznacza, że ​​muszę iść asynchronicznie? Czy jest prostszy przykład, może nie jest to 250 linii kodu? Czy po prostu muszę przegryźć te przykłady? Dzięki

Odpowiedz

12

Przykłady, o których wspominasz w dokumentacji Boost.Asio, są całkiem niezłe, aby zobaczyć, jak działają. Masz rację, że początkowo może to wydawać się nieco trudne do zrozumienia, szczególnie jeśli nie znasz tych pojęć. Zalecam jednak, aby zacząć od przykładu serwera czatu i uzyskać go na swoim komputerze. Pozwoli to bliżej przyjrzeć się wszystkim i zacząć zmieniać rzeczy, aby dowiedzieć się, jak to działa. Pozwól, że poprowadzę Cię przez kilka rzeczy, które uważam za ważne, aby zacząć.

Z Twojego opisu wynika, że ​​serwer czatu daje dobry punkt wyjścia, ponieważ ma już podobne elementy, których potrzebujesz. Posiadanie serwera asynchronicznego jest tym, czego potrzebujesz, ponieważ wtedy możesz łatwo obsłużyć wielu klientów z jednym wątkiem. Od samego początku nic zbyt skomplikowanego.

Uproszczony, asynchroniczny w tym przypadku oznacza, że ​​serwer działa poza kolejką, pobierając procedurę obsługi (zadanie) i wykonuje ją. Jeśli nie ma nic w kolejce, po prostu czeka na coś, co zostanie umieszczone w kolejce. W twoim przypadku oznacza to, że może to być połączenie od klienta, nowe odczytanie wiadomości od klienta lub coś podobnego. Aby to zadziałało, należy skonfigurować każdy program obsługi (funkcję obsługującą reakcję na określone zdarzenie).

Pozwól mi wyjaśnić trochę za pomocą kodu z przykładu serwera czatu.

W klasie server source file widać klasę chat_server, która wywołuje start_accept w konstruktorze. Tutaj konfiguruje się moduł akceptujący.

void start_accept() 
{ 
    chat_session_ptr new_session(new chat_session(io_service_, room_)); // 1 
    acceptor_.async_accept(new_session->socket(),      // 2 
     boost::bind(&chat_server::handle_accept, this, new_session,  // 3 
      boost::asio::placeholders::error));       // 4 
} 

Linia 1: a chat_session obiekt jest tworzony co stanowi jedną sesję między klientem a serwerem. Sesja jest tworzona dla akceptacji (żaden klient jeszcze się nie połączył).

Linia 2: asynchroniczny przyjąć do gniazdka ...

Linia 3 ... związany zadzwonić chat_server::handle_accept kiedy to się stanie. Sesja jest przekazywana do wykorzystania przez pierwszego klienta, który się łączy.

Teraz, gdy patrzymy na handle_accept widzimy, że po połączeniu się z klientem wywoływana jest sesja start (po prostu uruchamia się rzeczy między serwerem a tym klientem).Wreszcie, nowa akceptacja jest znakomita, jeśli inni klienci również chcą się połączyć.

void handle_accept(chat_session_ptr session, 
        const boost::system::error_code& error) 
{ 
    if (!error) 
    { 
     session->start(); 
    } 
    start_accept(); 
} 

To jest to, co chcesz również mieć. Znakomita akceptacja dla połączeń przychodzących. A jeśli wielu klientów może się połączyć, zawsze powinno być jedno z nich, tak aby serwer mógł obsłużyć akceptację.

Sposób interakcji serwera i klienta (ów) znajduje się w sesji i można wykonać ten sam projekt i zmodyfikować go, aby zrobić to, co chcesz. Wspomniałeś, że serwer musi patrzeć na to, co jest wysyłane i robić różne rzeczy. Spójrz na chat_session i funkcję start, która została wywołana przez serwer w handle_accept.

void start() 
{ 
    room_.join(shared_from_this()); 
    boost::asio::async_read(socket_, 
     boost::asio::buffer(read_msg_.data(), chat_message::header_length), 
     boost::bind(
      &chat_session::handle_read_header, shared_from_this(), 
      boost::asio::placeholders::error)); 
} 

Ważne jest połączenie pod numer boost::asio::async_read. Tego też chcesz. Zapewnia to znakomity odczyt na gnieździe, dzięki czemu serwer może odczytać, co wysyła klient. Istnieje funkcja obsługi (funkcja), która jest związana z tym zdarzeniem chat_session::handle_read_header. Będzie to wywoływane za każdym razem, gdy serwer odczyta coś na gnieździe. W tej funkcji obsługi możesz zacząć umieszczać swój kod, aby określić, co zrobić, gdy zostanie wysłana określona wiadomość i tak dalej.

Ważne jest, aby podczas wywoływania asynchronicznych funkcji boost :: asio rzeczy nie występowały w tym wywołaniu (tzn. Gniazdo nie zostanie odczytane, jeśli wywołasz funkcję odczytaną). To jest aspekt asynchroniczny. Po prostu rejestrujesz coś, a twój kod jest wywoływany, gdy tak się dzieje. Dlatego, gdy ten odczyt zostanie wywołany, natychmiast zwróci się i wrócisz do serwera (jeśli podążysz za tym, jak się nazywają). A jeśli pamiętasz, nazywamy również start_accept, aby skonfigurować inną akceptację asynchroniczną. W tym momencie masz dwa wyjątkowe programy obsługi oczekujące na połączenie z innym klientem lub na pierwszym kliencie, który coś wysyła. W zależności od tego, co dzieje się najpierw, ten konkretny moduł obsługi zostanie wywołany.

Ważne jest również zrozumienie, że za każdym razem, gdy coś jest uruchomione, działa nieprzerwanie, dopóki nie zostanie wykonane wszystko, co musi zrobić. Inne programy obsługi muszą poczekać, nawet jeśli istnieją wyjątkowe zdarzenia, które je wyzwalają.

Wreszcie, aby uruchomić serwer, musisz mieć io_service, który jest centralnym pojęciem w Asio.

io_service.run(); 

To jest jedna linia, którą można zobaczyć w funkcji main. To właśnie mówi, że wątek (tylko jeden w przykładzie) powinien uruchomić usługę io, która jest kolejką, w której procedury obsługi są zapisywane, gdy jest praca do wykonania. Gdy nic, usługa io_ tylko czeka (oczywiście blokując główny wątek).

Mam nadzieję, że pomoże ci to zacząć od tego, co chcesz robić. Jest wiele rzeczy, które możesz zrobić i rzeczy do nauki. Uważam to za świetny kawałek oprogramowania! Powodzenia!

+0

Pozdrawiamy dzięki. Masz rację, naprawdę muszę je przegryźć. Jedną z rzeczy, której nie rozumiem, jest silne wykorzystanie wspólnych wskaźników w przykładach. Gdzie są przechowywane? Magicznie w asio? Jeśli połączenie zginie, czy zostaną automatycznie wyczyszczone? Dzięki – Cookie

+0

Używanie współdzielonych wskaźników polega na utrzymywaniu pewnych obiektów przy życiu "wewnątrz" uchwytów. Asio ma jedynie handler (funkcja pobrana od boost :: bind) i tam jest powiązany wspólny wskaźnik. W ten sposób obiekt w udostępnionym wskaźniku jest utrzymywany przy życiu tak długo, jak żyje przewodnik, co jest ważne. Sesja jest przekazywana do wszystkich kryteriów boost: bind as shared_from_this() w klasie sesji, co jest całkiem fajne, ponieważ całe życie należy do asynchronicznych procedur obsługi przez cały czas, które tak będą obsługiwać czyszczenie w przypadku błąd. – murrekatt

3

W przypadku ktokolwiek inny chce to zrobić, tutaj jest minimalna, aby uzyskać powyżej dzieje: (podobny do tutoriali, ale nieco krótsze i nieco inny)

class Session : public boost::enable_shared_from_this<Session> 
{ 
    tcp::socket socket; 
    char buf[1000]; 
public: 
    Session(boost::asio::io_service& io_service) 
     : socket(io_service) { } 
    tcp::socket& SocketRef() { return socket; } 
    void Read() { 
     boost::asio::async_read(socket,boost::asio::buffer(buf),boost::asio::transfer_at_least(1),boost::bind(&Session::Handle_Read,shared_from_this(),boost::asio::placeholders::error)); 
    } 
    void Handle_Read(const boost::system::error_code& error) { 
     if (!error) 
     { 
      //read from buffer and handle requests 
      //if you want to write sth, you can do it sync. here: e.g. boost::asio::write(socket, ..., ignored_error); 
      Read(); 
     } 
    } 
}; 

typedef boost::shared_ptr<Session> SessionPtr; 

class Server 
{ 
    boost::asio::io_service io_service; 
    tcp::acceptor acceptor; 
public: 
    Server() : acceptor(io_service,tcp::endpoint(tcp::v4(), 13)) { } 
    ~Server() { } 
    void operator()() { StartAccept(); io_service.run(); } 
    void StartAccept() { 
     SessionPtr session_ptr(new Session(io_service)); 
     acceptor.async_accept(session_ptr->SocketRef(),boost::bind(&Server::HandleAccept,this,session_ptr,boost::asio::placeholders::error)); 
    } 
    void HandleAccept(SessionPtr session,const boost::system::error_code& error) { 
     if (!error) 
      session->Read(); 
     StartAccept(); 
    } 
}; 

Z tego co ja zebrane metodą prób oraz błąd i odczyt: Wywołuję go w trybie operator()(), aby można było uruchomić go w tle w dodatkowym wątku. Uruchomiono jedną instancję serwera. Aby obsłużyć wielu klientów, potrzebna jest dodatkowa klasa, nazwałem ją klasą sesji. Aby asio mógł czyścić martwe sesje, potrzebujesz współużytkowanego wskaźnika, jak wskazano powyżej. W przeciwnym razie kod powinien zacząć.