2017-01-06 2 views
1

Je voulais écrire un wrapper sur le boost ASIO pour une implémentation tcp client/serveur. L'interface de boost ASIO est vraiment sympa, mais la raison d'avoir wrapper est de pouvoir remplacer la boucle d'événement par autre chose. Dans notre cas, nous avons juste besoin d'invoquer la même fonction de gestionnaire pour chaque lecture asynchrone, l'application n'a pas besoin de passer le gestionnaire pour chaque appel asyncRead. Il est donc utile d'enregistrer le gestionnaire pour une fois. Une manière que j'ai essayée est comme ceci -Boost ASIO avec CRTP ou PBCP ou Duck-type

template <class Connection> 
struct TCPClient { // implements the interface with ASIO 

Connection *_connection; 

void setConnection (Connection *connection) 
{ 
    _connection = connection; 
} 

void asyncRead() 
{ 
    _socket.async_read_some(boost::asio::null_buffers(), 
       [this] (ErrorType err, unsigned a) { 

        if (_connection) _connection->handleRead(err); 
        if (!err) asyncRead(); 
       }); 
} 

};

que je pouvais faire la même avec CRTP

class MyConnection : public TCPClient<MyConnection> { 
    void readHandler (TCPClient::ErrType err) 
    { 
    } 
}; 

Et en classe TCPClient la AsyncRead sera

void asyncRead() 
{ 
    _socket.async_read_some(boost::asio::null_buffers(), 
       [this] (ErrorType err, unsigned a) { 
        ((Connection *)this)->handleRead(err); 
        if (!err) asyncRead(); 
       }); 
} 

Ce dans le cas est utile que la durée de vie de la TCPClient et la connexion est le même.

Ou PBCP

template <typename Connection> 
class TCPClient : public Connection { 

void asyncRead() 
{ 
    _socket.async_read_some(boost::asio::null_buffers(), 
       [this] (ErrorType err, unsigned a) { 
        Connection::handleRead(err); 
        if (!err) asyncRead(); 
       }); 
} 
}; 

Je ne pense pas vraiment il y a une IS-A par rapport b/w tcpclient et connexion. Je suis confus si l'un de ces schémas est bon. (Aussi je me demande pourquoi ASIO n'a pas de schéma où il met en cache le gestionnaire une fois et l'appelle à chaque fois.) En cas de lecture asynchrone, normalement il n'y a pas de contexte à renvoyer. Boost ASIO copier le gestionnaire de lecture à chaque fois + allocation de mémoire pour stocker est vraiment mauvais.Ainsi, en fonction des résultats de test, nous pouvons avoir à changer la boucle d'événement à quelque chose de personnalisé)

+2

Asio fournit des crochets pour contrôler la mémoire du gestionnaire allocation (voir [Custom Memory Allocation] (http://www.boost.org/doc/libs/1_63_0/doc/html/boost_asio.html#boost_asio.overview.core.allocation]). Asio est conçu pour servir de base à d'autres abstractions, et le paradigme _one-callback-per-operation_ facilite la mise en œuvre d'autres paradigmes. Avant d'écrire une boucle d'événement personnalisée, je vous encourage fortement à considérer [libuv] (https://github.com/libuv/libuv), qui prend en charge le fait que le même rappel soit appelé chaque fois que des données sont disponibles. –

+1

Je n'ai jamais entendu parler de PBCP. Qu'Est-ce que c'est? Google, assurément, n'en sait rien. (Si je devais faire une extension d'acronyme du code, je devinerais "Parameterized Base Class Pattern"? Nous appellerions cela Mixin) – sehe

+0

@sehe. Oui c'est ça. – MGH

Répondre

0

J'ai fait un peu de travail dans ce sens. Dans votre classe CRTP Base, vous pouvez essayer de créer une méthode paramétrée par template qui appelle la classe dérivée et définit un std::function contenant un lamba, qui doit être passé à async_read/write.

Classe de base:

template <class Connection> 
struct TCPClient { // implements the interface with ASIO 

std::function<void(const boost::system::error&) handler{}; 

void registerConnectionHandler(std::function<void(const boost::system::error&)> &&impl) 
{ 
    static_cast<MyConnection*>(this)->registerConnectionHandler(std::forward<decltype(impl)>(impl)); 
}  

void asyncRead() 
{ 
    _socket.async_read_some(boost::asio::null_buffers(), handler); 
} 

}; 

Dans la classe dérivée:

class MyConnection : public TCPClient<MyConnection> { 
public: 
    void registerConnectionHandler(std::function<void(const boost::system::error&)> &&impl) 
    { 
     handler = std::move(impl); 
    } 
};  

Une autre façon de le faire est de mettre en œuvre le gestionnaire dans la classe dérivée, sans utiliser le registerConnectionHandler avec un std :: fonction, qui est probablement le meilleur moyen de le faire:

class MyConnection : public TCPClient<MyConnection> { 
public: 
    void registerConnectionHandler() 
    { 
     handler = [this](const boost::system::error &err) 
     { 
     // your stuff here 
     }; 
    } 
};