aboutsummaryrefslogblamecommitdiff
path: root/README.rst
blob: 64298de28a10de991b6d8f876c44a50b005c63e7 (plain) (tree)
1
2
3
4
5
6
7
8
9


                                                       

                                                              

 

        




                                                                         
                                           



                                                                        
                                                                                     

                                                                                

                                                                        
 


               



















                                                                                     

                                                                            
                                                     
 


                                                                                

                    







              

                          


                   















































                                                                         



                                  























































































                                                                              
Salticidae: a Minimal C++ asynchronous network library.
=======================================================

.. image:: https://img.shields.io/badge/License-MIT-yellow.svg
   :target: https://opensource.org/licenses/MIT


Features
--------

- Simplicity. The library is self-contained, small in code base, and only
  relies on libevent and libcrypo (OpenSSL, for SHA256 purpose).

- Clarity. With moderate use of C++ template and new features, the vast
  majority of the code is self-documenting.

- Layered design. You can use network abstraction from the lowest socket
  connection level to the highest P2P network level.

- Performance. The implementation strives to incur very little overhead in processing
  network I/O, and avoid unnecessary memory copies thanks to the move semantics.

- Utilities. The library also provides with some useful gadgets, such as
  command-line parser, libevent abstraction, etc.

Functionalities
---------------

- ``ConnPool``: byte level connection pool implementation, ``ConnPool::Conn`` (or
  ``ConnPool::conn_t``) objects represent connections to which one can
  send/receive a stream of binary data asynchronously.

- ``MsgNetwork<OpcodeType>``: message level network pool implementation,
  ``MsgNetwork::Conn`` (or ``MsgNetwork::cont_t``) objects represent channels to
  which one can send/receive predefined messages asynchronously. Message
  handler functions are registered by ``reg_handler()`` and invoked upon
  receiving a new message.  ``OpcodeType`` is the type used for identifying
  message types. A valid message class must have:
  
  - a static member ``opcode`` typed ``OpcodeType`` as the message type identifier
  - a member ``serialized`` typed ``DataStream`` which contains the serialized data
    of the message.

  - a constructor ``MsgType(DataStream &&)`` that parse the message from stream.

Based on ``MsgNetwork``, salticidae provides the following higher level abstractions:

- ``PeerNetwork<OpcodeType>``: simple P2P network pool implementation. It will
  ensure exactly one underlying bi-directional connection is established per
  added peer, and retry the connection when it is broken. Ping-pong messages
  are utilized to test the connectivity periodically.

- ``ClientNetwork<OpcodeType>``: simple client-server network pool
  implementation. A server who initially calls ``listen()`` will accept the
  incoming client messages, while a client simply calls ``connect()`` to connect
  to a known server.

Dependencies
------------

- CMake >= 3.9
- C++14
- libevent
- libcrypto

Example (MsgNetwork layer)
--------------------------

.. code-block:: cpp

  #include <cstdio>
  #include <string>
  #include <functional>
  
  #include "salticidae/msg.h"
  #include "salticidae/event.h"
  #include "salticidae/network.h"
  #include "salticidae/stream.h"
  
  using salticidae::NetAddr;
  using salticidae::DataStream;
  using salticidae::MsgNetwork;
  using salticidae::htole;
  using salticidae::letoh;
  using std::placeholders::_1;
  using std::placeholders::_2;
  using opcode_t = uint8_t;
  
  /** Hello Message. */
  struct MsgHello {
      static const opcode_t opcode = 0x0;
      DataStream serialized;
      std::string name;
      std::string text;
      /** Defines how to serialize the msg. */
      MsgHello(const std::string &name,
              const std::string &text) {
          serialized << htole((uint32_t)name.length());
          serialized << name << text;
      }
      /** Defines how to parse the msg. */
      MsgHello(DataStream &&s) {
          uint32_t len;
          s >> len;
          len = letoh(len);
          name = std::string((const char *)s.get_data_inplace(len), len);
          len = s.size();
          text = std::string((const char *)s.get_data_inplace(len), len);
      }
  };
  
  /** Acknowledgement Message. */
  struct MsgAck {
      static const opcode_t opcode = 0x1;
      DataStream serialized;
      MsgAck() {}
      MsgAck(DataStream &&s) {}
  };

  const opcode_t MsgHello::opcode;
  const opcode_t MsgAck::opcode;

  using MsgNetworkByteOp = MsgNetwork<opcode_t>;
  
  struct MyNet: public MsgNetworkByteOp {
      const std::string name;
      const NetAddr peer;
  
      MyNet(const salticidae::EventContext &ec,
              const std::string name,
              const NetAddr &peer):
              MsgNetwork<opcode_t>(ec, 10, 1.0, 4096),
              name(name),
              peer(peer) {
          /* message handler could be a bound method */
          reg_handler(salticidae::handler_bind(
              &MyNet::on_receive_hello, this, _1, _2));
      }
  
      struct Conn: public MsgNetworkByteOp::Conn {
          MyNet *get_net() { return static_cast<MyNet *>(get_pool()); }
          salticidae::RcObj<Conn> self() {
              return salticidae::static_pointer_cast<Conn>(
                  MsgNetworkByteOp::Conn::self());
          }
  
          void on_setup() override {
              auto net = get_net();
              if (get_mode() == ACTIVE)
              {
                  printf("[%s] Connected, sending hello.\n",
                          net->name.c_str());
                  /* send the first message through this connection */
                  net->send_msg(MsgHello(net->name, "Hello there!"), self());
              }
              else
                  printf("[%s] Passively connected, waiting for greetings.\n",
                          net->name.c_str());
          }
          void on_teardown() override {
              auto net = get_net();
              printf("[%s] Disconnected, retrying.\n", net->name.c_str());
              /* try to reconnect to the same address */
              net->connect(get_addr());
          }
      };
      using conn_t = salticidae::RcObj<Conn>;
  
      salticidae::ConnPool::Conn *create_conn() override {
          return new Conn();
      }
  
      void on_receive_hello(MsgHello &&msg, conn_t conn) {
          printf("[%s] %s says %s\n",
                  name.c_str(),
                  msg.name.c_str(), msg.text.c_str());
          /* send acknowledgement */
          send_msg(MsgAck(), conn);
      }
  };
  
      
  void on_receive_ack(MsgAck &&msg, MyNet::conn_t conn) {
      auto net = conn->get_net();
      printf("[%s] the peer knows\n", net->name.c_str());
  }
  
  salticidae::EventContext ec;
  NetAddr alice_addr("127.0.0.1:1234");
  NetAddr bob_addr("127.0.0.1:1235");
  
  int main() {
      /* test two nodes */
      MyNet alice(ec, "Alice", bob_addr);
      MyNet bob(ec, "Bob", alice_addr);
  
      /* message handler could be a normal function */
      alice.reg_handler(on_receive_ack);
      bob.reg_handler(on_receive_ack);
  
      alice.listen(alice_addr);
      bob.listen(bob_addr);
  
      /* first attempt */
      alice.connect(bob_addr);
      bob.connect(alice_addr);
  
      ec.dispatch();
      return 0;
  }