Understanding TinyWebServer from scratch

The best way to understand a project is to start from the main function!

#include "config.h"

// argc: the number of command line parameters argv: the value of the command line parameter
int main(int argc, char *argv[])
{
    //Database information that needs to be modified, login name, password, library name
    string user = "root";
    string passwd = "root";
    string databasename = "qgydb";

    //Command line parsing
    Config config;
    config.parse_arg(argc, argv);

    WebServer server;

    //initialization
    server.init(config.PORT, user, passwd, databasename, config.LOGWrite,
                config.OPT_LINGER, config.TRIGMode, config.sql_num, config.thread_num,
                config.close_log, config.actor_model);
    

    //log
    server.log_write();

    //database
    server.sql_pool();

    //Thread Pool
    server.thread_pool();

    //trigger mode
    server.trig_mode();

    //monitor
    server.eventListen();

    //run
    server.eventLoop();

    return 0;
}

Let’s look at the first three lines first, which is to configure our database information, that is, where the WebServer compares the data after receiving the data sent by the browser. Just fill in these three lines according to your own database.

The fourth and fifth lines are mainly used to parse command line parameters, corresponding to what the author calls “personalized operation”

…….

Enter config.h, mainly about parameter variables, constructors, destructors and command line parsing for personalized operation.

parse_arg() function. Next, let’s see how the parse_arg() function parses the command line in config.cpp.

void Config::parse_arg(int argc, char*argv[]){
    int opt;
    const char *str = "p:l:m:o:s:t:c:a:";
    while ((opt = getopt(argc, argv, str)) != -1)
    {
        switch (opt)
        {
        case 'p':
        {
            PORT = atoi(optarg);
            break;
        }
        case 'l':
        {
            LOGWrite = atoi(optarg);
            break;
        }
        case 'm':
        {
            TRIGMode = atoi(optarg);
            break;
        }
        case 'o':
        {
            OPT_LINGER = atoi(optarg);
            break;
        }
        case 's':
        {
            sql_num = atoi(optarg);
            break;
        }
        case 't':
        {
            thread_num = atoi(optarg);
            break;
        }
        case 'c':
        {
            close_log = atoi(optarg);
            break;
        }
        case 'a':
        {
            actor_model = atoi(optarg);
            break;
        }
        default:
            break;
        }
    }
}

First of all, the parameter ‘argc’ of the function represents the number of command line parameters, and ‘argv’ is an array of pointers to character arrays, that is, command line parameters. Secondly, the str string is used to define the desired command line options. Each letter corresponds to a separate option. The colon after the letter indicates that the option requires a parameter value. For example, the ‘p’ option requires a parameter, which can be expressed like this:’ -p 1234′. The while loop uses the ‘getopt’ function to parse the command line parameters one by one. The ‘getopt’ function will return the letter of the next option. If there is none, it will return -1, indicating that the parsing is completed. Use switch to perform corresponding operations on the currently parsed options. Among them, the atoi() function (this function comes with the standard C library) converts the parameter value into the corresponding integer, and then assigns it to the corresponding variable (the variable here is also Those are the variables in config.h).

The sixth line is to create a WebServer object, which is the core of the entire project. Lines 7 and 89 are the initialization of the WebServer object (assignment of variables). The initialization parameters happen to be the command line parameters we just parsed in config.cpp.

The tenth line performs the log writing work of the server. Enter WebServer to view the log_write() function.

void WebServer::log_write()
{
    if (0 == m_close_log)
    {
        //Initialize log
        if (1 == m_log_write)
            Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 800);
        else
            Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 0);
    }
}

if(0 == m_close_log), check whether the member variable ‘m_clost_log’ is 0. If it is not 0, it means not to close logging and continue to perform subsequent log initialization operations. Then enable different log writing methods according to the value of m_log_write.

‘Log::get_instance()->init();’, get_instance() is a member function of the Log class, used to obtain the instance of the log record, the init() function is related to the specified log file parameter. Note: The way to obtain instances here is singleton mode + lazy mode (simply put, singleton mode ensures that a class has only one instance and provides a global access point to access the instance. Lazy mode is only used when needed. The instance will be created instead of creating the instance as soon as the program is run. This is actually easy to explain! If a server has multiple loggers, this will not only waste resources, but also cause a lot of confusion!!)

Line 11 initializes the database connection pool. Enter WebServer to view the sql_pool() function.

void WebServer::sql_pool()
{
    //Initialize database connection pool
    m_connPool = connection_pool::GetInstance();
    m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log);

    //Initialize database reading table
    users->initmysql_result(m_connPool);
}

Similar to log initialization, first obtain the instance of the database connection pool through the member function, and then initialize the database connection pool.

‘users -> initmysql_result(m_connPool);’ is a public member function of the http_conn class, used to initialize database connection-related information.


The ‘http_conn’ class represents the processing logic of HTTP connections and requests, and is used to process HTTP requests from clients and generate HTTP responses.

Line 12 initializes the thread pool

Function specific code:

Create a thread pool object. The parameter m_actormodel is the parameter obtained after the command line is parsed. m_connPool is the database connection pool object created in the previous line. m_thread_num is the parameter obtained after the command line is parsed.

Constructor of threadpool class:

The thirteenth line is to set the trigger mode

Function specific code:

void WebServer::trig_mode()
{
    //LT + LT
    if (0 == m_TRIGMode)
    {
        m_LISTENTrigmode = 0;
        m_CONNTrigmode = 0;
    }
    //LT + ET
    else if (1 == m_TRIGMode)
    {
        m_LISTENTrigmode = 0;
        m_CONNTrigmode = 1;
    }
    //ET + LT
    else if (2 == m_TRIGMode)
    {
        m_LISTENTrigmode = 1;
        m_CONNTrigmode = 0;
    }
    //ET + ET
    else if (3 == m_TRIGMode)
    {
        m_LISTENTrigmode = 1;
        m_CONNTrigmode = 1;
    }
}

It is mainly based on the m_TRIGMode parsed from the command line to set the triggering mode of the event (LT mode means that the event will always trigger when ready, ET mode means that the event will only trigger once when the status changes)

The fourteenth line is to enable server monitoring, which means that the server is in a pending state and is waiting for the client to connect.

Function specific code:

void WebServer::eventListen()
{
    //Basic steps of network programming
    // m_listenfd stores the file descriptor of the listening socket
    m_listenfd = socket(PF_INET, SOCK_STREAM, 0);
    // assert assertion to check whether the socket creation is successful. If the creation fails, the program terminates
    assert(m_listenfd >= 0);

    //Close the connection gracefully
    if (0 == m_OPT_LINGER)
    {
        // Do not enable graceful closing of the connection, set the so_linger socket option, and close the connection immediately
        struct linger tmp = {0, 1};
        setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, & amp;tmp, sizeof(tmp));
    }
    else if (1 == m_OPT_LINGER)
    {
        // Enable graceful closing of the connection, set the so_linger socket option, and wait for the unsent data to be sent before closing the connection.
        struct linger tmp = {1, 1};
        setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, & amp;tmp, sizeof(tmp));
    }

    int ret = 0;
    //Create and configure the server address structure address
    struct sockaddr_in address;
    bzero( & amp;address, sizeof(address));
    address.sin_family = AF_INET; //Set the address family to AF_INET, which means using IPv4 addresses
    address.sin_addr.s_addr = htonl(INADDR_ANY); // Set the IP address to INADDR_ANY, which means binding to all available network interfaces
    address.sin_port = htons(m_port);

    int flag = 1;
    // Enable the SO_REUSEADDR socket option to be able to reuse the bound port when the server restarts
    setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, & flag, sizeof(flag));
    // Bind the socket to the specified address structure, which binds the server's listening socket to the specified port.
    ret = bind(m_listenfd, (struct sockaddr *) & amp;address, sizeof(address));
    assert(ret >= 0);
    //Call the listen function to start listening for connection requests, and set the maximum waiting connection queue length to 5
    ret = listen(m_listenfd, 5);
    assert(ret >= 0);

    //Initialize instance utils
    utils.init(TIMESLOT);

    //epoll creates kernel event table
    //Declare an array events to store epoll events, the size of the array is MAX_EVENT_NUMBER
    epoll_event events[MAX_EVENT_NUMBER];
    // Created an epoll kernel event table and stored its file descriptor in the m_epollfd member variable
    m_epollfd = epoll_create(5);
    assert(m_epollfd != -1);

    // Call the addfd function of the utils object, add the listening socket m_listenfd to the epoll event table, and specify the event listening mode as m_LISTENTrigmode
    utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode);
    //Set member variables of the http_conn class
    http_conn::m_epollfd = m_epollfd;

    // Create a UNIX domain full-duplex socket pair, in which two socket descriptors are stored in the m_pipefd array for inter-process communication.
    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);
    assert(ret != -1);
    //Set m_pipefd[1] to non-blocking mode to improve the efficiency of pipe writing.
    utils.setnonblocking(m_pipefd[1]);
    // Add m_pipefd[0] to the epoll event table for listening to pipe read events.
    utils.addfd(m_epollfd, m_pipefd[0], false, 0);

    // Set up to ignore the SIGPIPE signal. This is to prevent the SIGPIPE signal from being triggered when writing data to a closed socket and causing the program to exit.
    utils.addsig(SIGPIPE, SIG_IGN);
    // Set the SIGALRM signal processing function to utils::sig_handler, and specify the false parameter to indicate not to re-enable signal processing.
    utils.addsig(SIGALRM, utils.sig_handler, false);
    // Set the SIGALRM signal processing function to utils::sig_handler, and specify the false parameter to indicate not to re-enable signal processing.
    utils.addsig(SIGTERM, utils.sig_handler, false);

    //Set a timer to trigger the SIGALRM signal every TIMESLOT seconds, which will be used for processing scheduled tasks.
    alarm(TIMESLOT);

    //Tool class, signal and descriptor basic operations
    Utils::u_pipefd = m_pipefd;
    Utils::u_epollfd = m_epollfd;
}

Among them, Utils is a member object of webserver. This class is a tool class for handling non-blocking I/O, signal processing, timing and other tasks.

Specific functions to initialize instance utils:

void Utils::init(int timeslot)
{
    m_TIMESLOT = timeslot;
}

m_TIMESLOT represents a time interval, used to configure the trigger interval of scheduled tasks.

The last line is to run the server.

Function specific code:

//This event loop continuously waits for the occurrence of various events, and then performs corresponding processing according to the event type, including processing new client connections, processing signals, processing read and write events, etc.
// When the timer times out, the processing of the timer event will also be triggered. The loop will continue to run until stop_server becomes true, usually initiated by an external mechanism, indicating that the server wants to
// Stop running
void WebServer::eventLoop()
{
    //Whether a timer timeout occurred
    bool timeout = false;
    //Whether to stop the server
    bool stop_server = false;

    while (!stop_server)
    {
        // The epoll_wait function waits for events to occur. m_epollfd is an epoll file descriptor, events is an array used to store events, and MAX_EVENT_NUMBER is the maximum
        //The number of events, -1 means waiting until an event occurs. number stores the number of events that occurred
        int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);

        // If the return value is less than 0 and the error code is not EINTR (indicating that it was interrupted by a signal), print the error message and exit the loop
        if (number < 0 & amp; & amp; errno != EINTR)
        {
            LOG_ERROR("%s", "epoll failure");
            break;
        }

        // Traverse the events that occurred
        for (int i = 0; i < number; i + + )
        {
            // Get the file descriptor of the event
            int sockfd = events[i].data.fd;

            //Handle new client connections
            // If the event is triggered by the server listening socket m_listenfd, it means that there is a new client connection request, and enter the branch that handles the new client connection.
            if (sockfd == m_listenfd)
            {
                //The purpose of this function is to process new client connection data and return a Boolean flag indicating the processing result.
                bool flag = dealclinetdata();
                //If the processing result flag is false, skip the current loop iteration and continue processing the next event.
                if (false == flag)
                    continue;
            }
            // When the socket is closed or an error occurs, remove the corresponding timer
            else if (events[i].events & amp; (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
            {
                //The server closes the connection and removes the corresponding timer
                util_timer *timer = users_timer[sockfd].timer;
                deal_timer(timer, sockfd);
            }
          
            // Handle signal events read from the pipe, used to process signals
            else if ((sockfd == m_pipefd[0]) & amp; & amp; (events[i].events & amp; EPOLLIN))
            {
                bool flag = dealwithsignal(timeout, stop_server);
                // An error occurs when processing signal events, and the error information is recorded through the log.
                if (false == flag)
                    LOG_ERROR("%s", "dealclientdata failure");
            }
            //Process the data received on the client connection
            else if (events[i].events & amp; EPOLLIN)
            {
                dealwithread(sockfd);
            }
            else if (events[i].events & amp; EPOLLOUT)
            {
                dealwithwrite(sockfd);
            }
        }
        // timeout is true, indicating that a timer timeout has occurred. utils.timer_handler() will be called to process the timer event and print relevant information.
        if(timeout)
        {
            utils.timer_handler();

            LOG_INFO("%s", "timer tick");

            timeout = false;
        }
    }
}

At this point, we have a general understanding of the functions of the modules and functions involved in the project. Later, we will specifically understand each module function and the patterns involved in the module (including the importance and functions of the patterns).

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Cloud native entry-level skills treeHomepageOverview 16353 people are learning the system