Giter Club home page Giter Club logo

ninja-turtle's Introduction

TurtleChat Logo


Turtle

Turtle is a C++17-based lightweight network library for web server on Linux. It abstracts the tedious manipulations on the socket into elegant and reusable classes. It allows a fast server side setup where the custom business logic could be specified for each client TCP connection in the form of a callback function. It now supports HTTP GET/HEAD request and response as well.

Highlight

  • Set non-blocking socket and edge-trigger handling mode to support high concurrency workload.
  • Adopt the 'one reactor per thread' philosophy by Shuo Chen with thread pool management.
  • Achieve low coupling and high extensible framework.
  • Allow users to build custom server by only providing 2 callback functions.
  • Support HTTP GET/HEAD request & response.
  • Support dynamic CGI request & response.
  • Support Caching mechanism.
  • Support MySQL Database interaction.
  • Support Timer to kill inactive client connections to save system resources.
  • Support asynchronous consumer-producer logging.
  • Unit test coverage by Catch2 framework.

System Diagram

System Architecture New

The above system architecture diagram briefly shows how the Turtle framework works on a high level.

  1. The basic unit is a Connection which contains a Socket and a Buffer for bytes in-and-out. Users register a callback function for each connection.
  2. The system starts with an Acceptor, which contains one acceptor connection. It builds connection for each new client, and distribute the workload to one of the Loopers.
  3. Each Poller is associated with exactly one Looper. It does nothing but epoll, and returns a collection of event-ready connections back to the Looper.
  4. The Looper is the main brain of the system. It registers new client connection into the Poller, and upon the Poller returns back event-ready connections, it fetches their callback functions and execute them.
  5. The ThreadPool manages how many Loopers are there in the system to avoid over-subscription.
  6. Optionally there exists a Cache layer using LRU policy with tunable storage size parameters.

The Turtle core network part is around 1000 lines of code, and the HTTP+CGI module is another 700 lines.

Docker

If you are not a Linux system but still want to try out the Turtle on Linux for fun, we provide a Vagrant File to provision the Linux Docker. Notice as of current, Turtle is compatible with Linux and MacOS for build.

  1. Install Vagrant and Docker. For macOS, you may use homebrew to install Vagrant but do not use homebrew to install Docker. Instead, download Docker Desktop from the link above.

  2. Start the Docker application in the background

  3. Drag out the Vagrantfile and place it in parallel with the Turtle project folder. For example, consider the following file structure:

/Turtle_Wrapper
    - /Turtle
    - /Vagrantfile
  1. cd to the Turtle_Wrapper folder and run command vagrant up --provider=docker. This step should take a few minutes to build up the environment and install all the necessary tool chains.

  2. Enter the docker environment by vagrant ssh developer

  3. cd to the directory /vagrant/Turtle. This directory is in sync with the original ./Turtlefolder. You may modify the source code and its effect will be propagated to the docker's folder as well.

  4. Follow the steps in next section to build up the project.

Build

You may build the project using CMake.

Once you are at the root directory of this project, execute the followings:

// Setup environment (Linux)
$ sh setup/setup.sh
$ sudo systemctl start mysql 
$ sudo mysql < setup/setup.sql  // setup the default mysql role for testing

// Build, multiple build options
$ mkdir build
$ cd build
$ cmake .. // default is with logging, no timer
$ cmake -DLOG_LEVEL=NOLOG .. // no logging
$ cmake -DTIMER=3000 .. // enable timer expiration of 3000 milliseconds
$ make

// Format & Style Check & Line Count
$ make format
$ make cpplint
$ make linecount

API Style

The classes in the Turtle library are designed with the focus of decoupling firmly in mind. Most of the components can be taken out alone or a few together and used independently, especially those components in the network core module.

Let's take an example from the most basic Socket class, assuming that we just want to borrow the Turtle library to avoid the cumbersome steps of socket establishment. First, let's take a look at the main interface of the Socket class:

/**
 * This Socket class abstracts the operations on a socket file descriptor
 * It can be used to build client or server
 * and is compatible with both Ipv4 and Ipv6
 * */
class Socket {
 public:
  Socket() noexcept;
  auto GetFd() const noexcept -> int;

  /* client: one step, directly connect */
  void Connect(NetAddress &server_address);

  /* server: three steps, bind + listen + accept */
  void Bind(NetAddress &server_address, bool set_reusable = true);

  /* enter listen mode */
  void Listen();

  /* accept a new client connection request and record its address info */
  auto Accept(NetAddress &client_address) -> int;

 private:
  int fd_{-1}; 
};

With such an interface, we can quickly and easily build client and server sockets in a few lines:

#include "core/net_address.h"
#include "core/socket.h"

// local Ipv4 address at 8080 port
NetAddress local_address("127.0.0.1", 8080, Protocol::Ipv4);

// build a client
Socket client_sock;
client_sock.Connect(local_address);

// build a server
Socket server_sock;
server_sock.Bind(local_address);
server_sock.Listen();

// accept 1 new client connection request
// client_address will be filled with new client's ip info
NetAddress client_address;
int client_fd = server_sock.Accept(client_address);

There are many other components in Turtle that are easy to decouple and use separately. You can view the source code and use them according to needs.

Usage

General

To setup a general custom server, user should create an instance of Turtle Server and then only needs to provide two callback functions:

  1. OnAccept(Connection *): A function to do extra business logic when accepting a new client connection.
  2. OnHandle(Connection *): A function to serve an existing client's request.

Notice that most of common functionality for accepting a new client connection is already implemented and supported in the Acceptor::BaseAcceptCallback, including socket accept, setup and put it under monitor of the Poller.

The function provided in OnAccept(Connection *) by users will be augmented into the base version and called as well. There is no base version for the OnHandle(Connection *). Users must specify one before they can call Begin() on the server.

Let's walk through an example of traditional echo server in less than 20 lines:

#include "core/Turtle_server.h"

int main() {
  Turtle_SERVER::NetAddress local_address("0.0.0.0", 20080);
  Turtle_SERVER::TurtleServer echo_server(local_address);
  echo_server
      .OnHandle([&](Turtle_SERVER::Connection* client_conn) {
        int from_fd = client_conn->GetFd();
        auto [read, exit] = client_conn->Recv();
        if (exit) {
          client_conn->GetLooper()->DeleteConnection(from_fd);
          // client_conn ptr is invalid below here, do not touch it again
          return;
        }
        if (read) {
          client_conn->WriteToWriteBuffer(client_conn->ReadAsString());
          client_conn->Send();
          client_conn->ClearReadBuffer();
        }
      })
      .Begin();
  return 0;
}

The demo of this echo server and client is provided under the ./demo/echo folder for your reference. In the build directory, you can execute the following and try it out.

$ make echo_server
$ make echo_client

// in one terminal
$ ./echo_server

// in another terminal
$ ./echo_client

There are also simple Redis-like KV-Store demo provided under ./demo/kvstore to illustrate various usages of Turtle.

HTTP

The HTTP server demo is under ./src/http folder for reference as well. It supports GET and HEAD methods. A simple HTTP server could be set up in less than 50 lines with the help of Turtle core and http module.

CGI

The CGI module is built upon HTTP server and executes in the traditional parent-child cross-process way. After parsing the arguments, the Cgier fork a child process to execute the cgi program and communicate back the result to parent process through a shared temporary file.

It assumes the cgi program resides under a /cgi-bin folder and arguments are separated by &. For example, if there is a remote CGI program int add(int a, int b) that adds up two integers. To compute 1+2=3, The HTTP request line should be

GET /cgi-bin/add&1&2 HTTP/1.1

Database

Since database is an indispensable part of many web applications, Turtle also supports basic interactions with databases, MySQL in specific. We wrap the official MySQL C++ Connector into simple connections that can execute queries and return back results. Users of Turtle may consider plug in this component when implementing custom service callback functions.

The relevant source code is under db folder. Users may refer to setup.sql and mysqler_test for setup and simple usage reference.

For a bare minimal use example, suppose on port 3306 of localhost the root user with password root in the database test_db has a table user of two fields firstname and lastname. We could update and query as follows:

#include <string>
#include "db/mysqler.h"

/* for convenience reason */
using Turtle_SERVER::DB::MySqler;

int main(int argc, char* argv[]) {
    // init a db connection
    MySqler mysqler = MySqler("127.0.0.1", 3306, "root", "root", "test_db");
    // insert a new user Barack Obama, synchronously
    std::string command_insert = "INSERT INTO user (firstname, lastname) VALUES ('Barack', 'Obama');";
    mysqler.ExecuteBlocking(command_insert);
    // query for whose firstname is Barack, asynchronously via std::async
    std::string command_query = "SELECT firstname, lastname FROM user WHERE firstname = 'Barack';"
    auto fut = mysqler.ExecuteQueryNonBlocking(command_query);      
    sql::ResultSet result_set = fut.get();  // execute
    // maybe many people has firstname Barack, iterator
    size_t return_size = result_set->rowsCount();
    while (result_set->next()) {
        // the corresponding lastname for this user
        std::string lastname = result_set->getString("lastname");
    }
    return 0;
}

Logging

Logging is supported in an asynchronous consumer-producer fashion with Singleton pattern. Callers non-blockingly produce logs, and a background worker thread periodically takes care of all the logs produced since its last wakeup in a FIFO fashion. The exact way to "take care" of the logs is up to customization by strategy plugin. The default is to write to a log file on disk. And you may also tune refresh interval length or refresh log count.

Four levels of logging is available in terms of macros:

  • LOG_INFO
  • LOG_WARNING
  • LOG_ERROR
  • LOG_FATAL

You may disable any logging by passing the flag -DLOG_LEVEL=NOLOG in CMake build.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.