C + + hand training project (simple HTTP webserver based on muduo network library + mysql+jsoncpp for web page display database background data)

Project introduction

Project github: github link
This project is based on C + + language, Mr. Chen Shuo's muduo network library, mysql database and json CPP. The server listens to two ports, one for processing http requests and the other for processing json data sent.
This project cooperates with a qt+opencv license plate recognition front-end program. After recognizing the license plate, it is packaged into json data and sent to the back-end server, saved in mysql database and displayed on the web page.

Background introduction

This project is a small project written for personal learning, without very detailed consideration of the very complete HTTP protocol stack and other contents. You can only listen to port 80 and use the POST method to transmit JSON data. However, considering that the information source does not necessarily implement the HTTP POST protocol, it is more convenient to listen to another port and only transmit JSON data.

Introduction to main modules

1. WebServer based on muduo Network Library:

The WebServer is based on the muduo network library and HTTP protocol stack. The muduo network library is mainly responsible for the management of socket connection and data. When the connection is established, initialize the HTTPcontext in the context of the connection, and then when receiving the HTTP request, save the received data in the HTTPcontext of the connection and process the HTTP request by calling the onMessage callback function, Then, the onRequest callback function generates an HTTP response according to the request, obtains all license plates in the hash table (synchronized with the database) and returns the corresponding HTML page.

2. HTTP protocol stack

The HTTP protocol stack mainly includes httpcontext, httprequest and httpresponse classes
(1) HTTPContext class: it is mainly used to process and store HTTPRequest (HTTP request) line by line, and then store this class in muduo's TCPConnection (connection class) for callback function to process HTTP request, which can avoid TCP packet sticking.
(2) HTTPRequest class: use the enumeration type to store the HTTP request method, hashmap to store the HTTP header of a connection, and string to store the body of the request.
(3) HTTPResponse class: 400 NotFound is returned by default. The onRequest callback of HTTP needs to be set in WebServer. The HTTP response is constructed manually according to the HTTP request. The status code, status code description, response header and entity need to be set.

3,JsonServer:

(1) JsonServer is responsible for listening to a port. After establishing the connection, when receiving the data, it will directly analyze whether the Json is complete. If it is incomplete, it will continue to listen to the port, and store the incomplete Json data in the connected BUFFER. After analyzing the complete Json form, it can clear the BUFFER.
(2) The server also contains an SQLConnection, which can store the license plate and ID in MySQL database after parsing the correct Json.

4,SQLConnection

Control the SQL connection through RAII to ensure the correct life cycle of SQL connection, and realize the query and insert member functions to realize the functions of query and insert.

5. main module

Set the IP address, port and onRequest callback function of HTTP of the two server s according to the requirements (generate HTTP response that meets the requirements, which can be understood as simple cgi)

Implementation process

1. Determine how to implement an HTTP webserver

At the beginning of the project, you need to determine how to implement a simple HTTP WebServer in C + + language. You can use basic socket programming + manual implementation of HTTP protocol stack from the bottom; You can use off the shelf WebServer, such as lighttpd, apache, nginx and other servers + cgi programs. However, in the end, I chose to use Mr. Chen Shuo's muduo network library + manually implement HTTP protocol stack to implement a WebServer from the application level. The reasons are as follows: first, I have implemented a WebServer (github:tinyWebServer) from the bottom, using the non blocking IO model + reactor to simulate the event processing mode of the Proctor, so I don't want to implement the project with copy code, Secondly, the muduo network library has many details worth learning, such as how to correctly and efficiently manage socket connections. Different from the previous model of implementing WebServer, the muduo network library uses the event processing mode of the model of master reactor + Slave reactor, per thread one loop.

2. Manually implement HTTP protocol stack

PS: reference here nearXDU boss's article

The implementation of a simple HTTP webserver can only manage connections and data by using muduo network library, while the HTTP protocol stack of the actual application layer needs to be implemented by itself. Therefore, HTTPContext, httprequest and httpresponse are designed. HTTPContext is a context of an HTTP request corresponding to each TCP connection, including the request, and is stored in the context of muduo::net::TCPConnection. Needless to say, HTTP protocol is the foundation of the foundation. The code mainly uses the built-in string processing method of C + + to parse HTTP requests and unordered_ The request information stored by map will be stored in the cache of HTTPRequest class, while HTTPResponse is specially designed for muduo network library. After setting up the request line, request header and entity of HTTP response, it will call its appendToBuffer member function and send response from muduo network library. The code is as follows:
httpcontext.h

#ifndef HTTPCONTEXT_H
#define HTTPCONTEXT_H

#include <iostream>
#include <algorithm>
#include <muduo/net/Buffer.h>
#include "httprequest.h"

using namespace std;
using namespace muduo;
using namespace muduo::net;

class HttpContext
{
public:
    enum HttpRequestParseState
    {
        kExpectRequestLine,
        kExpectHeaders,
        kExpectBody,
        kGotAll
    };

    HttpContext():state_(kExpectRequestLine)
    {

    }

    bool parseRequest(Buffer *buf, Timestamp receiveTime);

    bool gotAll() const
    {
        return state_ == kGotAll;
    }

    void reset()
    {
        state_ = kExpectRequestLine;
        HttpRequest dummy;
        request_.swap(dummy);
    }

    const HttpRequest& request() const
    {
        return request_;
    }

private:
    //Parse request line
    bool processRequestLine(const char* begin, const char* end);

private:
    HttpRequestParseState state_;
    //The parsing results are saved in request_ Among members
    HttpRequest request_;
};

#endif // HTTPCONTEXT_H

httpcontext.cpp:

#include "httpcontext.h"

//Parse request line
bool HttpContext::processRequestLine(const char *begin, const char *end)
{
    bool succeed = false;
    const char *start = begin;
    const char *space = find(start, end, ' ');
    //Set request method_
    if(space != end && request_.setMethod(start, space))
    {
        start = space + 1;
        space = find(start, end, ' ');
        if(space != end)
        {
            //Resolve URI
            const char *question = find(start, space, '?');
            if(question != space)
            {
                request_.setPath(start, question);
                request_.setQuery(question, space);
            }
            else
            {
                request_.setPath(start, space);
            }

            //Resolve HTTP version number
            start = space + 1;
            succeed = end-start == 8 && equal(start, end-1, "HTTP/1.");
            if(succeed)
            {
                if(*(end-1) == '1')
                {
                    request_.setVersion(HttpRequest::HTTP11);
                }
                else if(*(end-1) == '0')
                {
                    request_.setVersion(HttpRequest::HTTP10);
                }
                else
                {
                    succeed = false;
                }
            }
        }
    }

    return succeed;
}

//Parse request header
bool HttpContext::parseRequest(Buffer *buf, Timestamp receiveTime)
{
    bool ok = true;
    bool hasMore = true;
    while(hasMore)
    {
        //Parse request line
        if(state_ == kExpectRequestLine)
        {
            const char *crlf = buf->findCRLF();
            if(crlf)
            {
                //Start parsing request line
                ok = processRequestLine(buf->peek(), crlf);
                if(ok)
                {
                    //Parsing succeeded
                    request_.setReceiveTime(receiveTime);
                    //Reclaim request line buffer
                    buf->retrieveUntil(crlf+2);
                    state_ = kExpectHeaders;
                }
                else
                {
                    hasMore = false;
                }
            }
            else
            {
                hasMore = false;
            }
        }
        //Parse request header
        else if(state_ == kExpectHeaders)
        {
            const char *crlf = buf->findCRLF();
            if(crlf)
            {
                //colon
                const char *colon = find(buf->peek(), crlf, ':');
                if(colon != crlf)
                {
                    request_.addHeader(buf->peek(), colon, crlf);
                }
                else
                {
                    //empty line, end of header
                    //FIXME:
                    state_ = kGotAll;
                    hasMore = false;
                }
                buf->retrieveUntil(crlf+2);//recovery
            }
            else
            {
                hasMore = false;
            }
        }
        else if(state_ == kExpectBody)
        {
            cout << "HttpContext: parse body" << endl;
        }
    }//end while

    return ok;
}

httprequest.h

#ifndef HTTPREQUEST_H
#define HTTPREQUEST_H

#include <iostream>
#include <muduo/base/Timestamp.h>
#include <string>
#include <unordered_map>

using namespace std;
using namespace muduo;

class HttpRequest
{
public:
    enum Method
    {
        INVALID,
        GET,
        POST,
        HEAD,
        PUT,
        DELETE
    };
    enum Version
    {
        UNKNOWN,
        HTTP10,
        HTTP11
    };

    HttpRequest();

    void setVersion(Version v);
    Version getVersion() const;

    bool setMethod(const char *start, const char *end);
    Method method() const;
    const char* methodString() const;

    void setPath(const char* start, const char* end);
    const string& path() const;

    void setQuery(const char *start, const char *end);
    const string& query() const;

    void setReceiveTime(Timestamp t);
    Timestamp receiveTime() const;

    void addHeader(const char *start, const char *colon, const char *end);
    string getHeader(const string &field) const;

    const unordered_map<string,string>& headers() const;
    void swap(HttpRequest& that);

private:
    Method method_;
    Version version_;
    string path_;
    string query_;
    Timestamp receiveTime_;
    unordered_map<string,string> headers_;
};

#endif // HTTPREQUEST_H

httprequest.cpp

#include "httprequest.h"

HttpRequest::HttpRequest():method_(INVALID),version_(UNKNOWN)
{

}

void HttpRequest::setVersion(HttpRequest::Version v)
{
    version_ = v;
}

HttpRequest::Version HttpRequest::getVersion() const
{
    return version_;
}

bool HttpRequest::setMethod(const char *start, const char *end)
{
    assert(method_ == INVALID);
    string m(start,end);
    if(m == "GET")
    {
        method_ = GET;
    }
    else if(m == "POST")
    {
        method_ = POST;
    }
    else if(m == "HEAD")
    {
        method_ = HEAD;
    }
    else if(m == "PUT")
    {
        method_ = PUT;
    }
    else if(m == "DELETE")
    {
        method_ = DELETE;
    }
    else
    {
        method_ = INVALID;
    }

    return method_ != INVALID;
}

HttpRequest::Method HttpRequest::method() const
{
    return method_;
}

const char *HttpRequest::methodString() const
{
    const char *result = "UNKNOWN";
    switch(method_)
    {
    case GET:
        result = "GET";
        break;
    case POST:
        result = "POST";
        break;
    case HEAD:
        result = "HEAD";
        break;
    case PUT:
        result = "PUT";
        break;
    case DELETE:
        result = "DELETE";
        break;
    default:
        break;
    }

    return result;
}

void HttpRequest::setPath(const char *start, const char *end)
{
    path_.assign(start,end);
}

const string &HttpRequest::path() const
{
    return path_;
}

void HttpRequest::setQuery(const char *start, const char *end)
{
    query_.assign(start,end);
}

const string &HttpRequest::query() const
{
    return query_;
}

void HttpRequest::setReceiveTime(Timestamp t)
{
    receiveTime_ = t;
}

Timestamp HttpRequest::receiveTime() const
{
    return receiveTime_;
}

void HttpRequest::addHeader(const char *start, const char *colon, const char *end)
{
    string field(start,colon);
    ++colon;
    while(colon < end && isspace(*colon))
        ++colon;

    string value(colon,end);
    while(!value.empty() && isspace(value[value.size()-1]))
        value.resize(value.size()-1);

    headers_[field] = value;
}

string HttpRequest::getHeader(const string &field) const
{
    string result;
    unordered_map<string, string>::const_iterator it = headers_.find(field);
    if(it != headers_.end())
        result = it->second;

    return result;
}

const unordered_map<string, string> &HttpRequest::headers() const
{
    return headers_;
}

void HttpRequest::swap(HttpRequest &that)
{
    std::swap(method_, that.method_);
    path_.swap(that.path_);
    query_.swap(that.query_);
    receiveTime_.swap(that.receiveTime_);
    headers_.swap(that.headers_);
}

httpresponse.h

#ifndef HTTPRESPONSE_H
#define HTTPRESPONSE_H

#include <iostream>
#include <string>
#include <unordered_map>
#include <muduo/net/Buffer.h>

using namespace std;
using namespace muduo;
using namespace muduo::net;

class HttpResponse
{
public:
    enum HttpStatusCode
    {
        CODE_UNKNOWN,
        CODE_200 = 200,
        CODE_301 = 301,
        CODE_400 = 400,
        CODE_404 = 404
    };

    explicit HttpResponse(bool close):statusCode_(CODE_UNKNOWN),closeConnection_(close)
    {

    }
    void setStatusCode(HttpStatusCode code);
    void setStatusMessage(const string &message);
    void setCloseConnection(bool on);
    bool closeConnction() const;
    void setContentType(const string &contentType);
    void addHeader(const string &key, const string &value);
    void setBody(const string &body);
    void appendToBuffer(Buffer *output) const;


private:
    //Response header
    unordered_map<string,string> headers_;
    //Response code
    HttpStatusCode statusCode_;
    //status information 
    string statusMessage_;
    //keep_alive
    bool closeConnection_;
    //response message
    string body_;
};

#endif // HTTPRESPONSE_H

httpresponse.cpp

#include "httpresponse.h"

void HttpResponse::setStatusCode(HttpResponse::HttpStatusCode code)
{
    statusCode_ = code;
}

void HttpResponse::setStatusMessage(const string &message)
{
    statusMessage_ = message;
}

void HttpResponse::setCloseConnection(bool on)
{
    closeConnection_ = on;
}

bool HttpResponse::closeConnction() const
{
    return closeConnection_;
}

void HttpResponse::setContentType(const string &contentType)
{
    addHeader("Content-Type", contentType);
}

void HttpResponse::addHeader(const string &key, const string &value)
{
    headers_[key] = value;
}

void HttpResponse::setBody(const string &body)
{
    body_ = body;
}

void HttpResponse::appendToBuffer(Buffer *output) const
{
    char buf[32];
    //Construct response line
    snprintf(buf, sizeof(buf), "HTTP/1.1 %d ", statusCode_);
    output->append(buf);
    output->append(statusMessage_);
    output->append("\r\n");

    if(closeConnection_)
    {
        output->append("Connection: close\r\n");
    }
    else
    {
        //Keep alive requires content length
        snprintf(buf, sizeof(buf), "Content-Length: %zd\r\n", body_.size());
        output->append(buf);
        output->append("Connection: Keep-Alive\r\n");
    }

    for(auto it = headers_.begin(); it != headers_.end(); ++it)
    {
        output->append(it->first);
        output->append(": ");
        output->append(it->second);
        output->append("\r\n");
    }

    output->append("\r\n");
    //response message
    output->append(body_);
}

3. Combining HTTP protocol stack and muduo network library to realize HTTP webserver

PS: you can register a timer here to actively close the timeout link
The muduo network library is object-based. The design of the Server based on it needs to be object-based. Include muoduo::net::TcpServer in the Server class, and then register the callback functions at the time of connection, reception and disconnection to realize the basic functions of the Server. The main HTTP design codes are as follows:
httpserver.h

#ifndef HTTPSERVER_H
#define HTTPSERVER_H

#include <muduo/net/TcpServer.h>
#include <muduo/base/Logging.h>
#include <muduo/net/EventLoop.h>
#include <iostream>
#include <functional>
#include <string>
#include <muduo/net/Buffer.h>
#include "httpcontext.h"
#include "httprequest.h"
#include "httpresponse.h"

using namespace std;
using namespace muduo;
using namespace muduo::net;


class HttpServer
{
public:
    //http callback function
    typedef function<void(const HttpRequest&,HttpResponse*)> HttpCallback;
    //Construction and destructor
    explicit HttpServer(EventLoop* loop,const InetAddress& listenAddr);
    ~HttpServer();

    EventLoop* getLoop() const { return server_.getLoop(); }

    void setHttpCallback(const HttpCallback& cb)
    {
        httpCallback_ = cb;
    }

    void setThreadNum(const int numThreads)
    {
        server_.setThreadNum(numThreads);
    }

    void start();


private:
    void onConnection(const TcpConnectionPtr &conn);

    void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receiveTime);

    void onRequest(const TcpConnectionPtr &conn,const HttpRequest&);

private:
    TcpServer server_;
    HttpCallback httpCallback_;
};

#endif // HTTPSERVER_H

httpserver.cpp

#include "httpserver.h"

void defaultHttpCallback(const HttpRequest&,HttpResponse* resp)
{
    resp->setStatusCode(HttpResponse::CODE_400);
    resp->setStatusMessage("Not Found");
    resp->setCloseConnection(true);
}

HttpServer::HttpServer(EventLoop *loop, const InetAddress &listenAddr):server_(loop, listenAddr, "wyeHttpServer"), httpCallback_(defaultHttpCallback)
{
    server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, placeholders::_1));
    server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, placeholders::_1, placeholders::_2, placeholders::_3));
}

HttpServer::~HttpServer()
{

}

void HttpServer::start()
{
    LOG_WARN << "HttpServer[" << server_.name() << "] starts listenning on " << server_.ipPort();
    server_.start();
}


//New connection callback
void HttpServer::onConnection(const TcpConnectionPtr &conn)
{
    if(conn->connected())
    {
        conn->setContext(HttpContext());
    }
}

//Message callback
void HttpServer::onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receiveTime)
{
    HttpContext *context = boost::any_cast<HttpContext>(conn->getMutableContext());
    //Parse request
    if(!context->parseRequest(buf, receiveTime))
    {
        conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
        conn->shutdown();
    }

    if(context->gotAll())
    {
        //Request parsing completed
        onRequest(conn, context->request());
        context->reset();
    }
}

void HttpServer::onRequest(const TcpConnectionPtr &conn, const HttpRequest &req)
{
    const string &connection = req.getHeader("Connection");
    bool close = connection == "close" || (req.getVersion() == HttpRequest::HTTP10 && connection != "Keep-Alive");
    HttpResponse response(close);//Structural response
    httpCallback_(req, &response);
    Buffer buf;
    //At this point, the response has been constructed, and the response sent to the customer is added to the buffer
    response.appendToBuffer(&buf);
    conn->send(&buf);
    //If it's not keep alive, close it
    if(response.closeConnction())
    {
        conn->shutdown();
    }
}

4. Implementation of JsonServer:

There are many ways to parse Json. At first, I wanted to use the Boost library to parse, but before version 1.75, I needed Boost:: property to parse Json_ Tree, the operation is more complex. An excellent piece of code should be written with better wheels and will be easy to understand. Therefore, it is recommended, and I also use the Jsoncpp library to parse Json data. Then, JsonServer also includes SQLConnection, which is used to store the parsed Json data into MySQL. The specific implementation is based on muduo network library, which is similar to the design of HTTPServer class. The main Json server design codes are as follows:
jsonprocess.h

#ifndef JSONPROCESS_H
#define JSONPROCESS_H
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpServer.h>
#include <muduo/net/Buffer.h>
#include <muduo/base/Logging.h>
#include <functional>
#include <map>
#include <unordered_set>
#include <jsoncpp/json/json.h>
#include "sqlconnection.h"

using namespace muduo;
using namespace muduo::net;
using namespace std;

class JsonProcess
{
public:
    explicit JsonProcess(EventLoop *loop,const InetAddress& listenAddr, map<int,vector<string>> &mp);
    ~JsonProcess()
    {
        sqlConnection_.disconnectFromSqlServer();
    }

    void start();

private:
    void onConnection(const TcpConnectionPtr &conn);

    void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp t);

    void parseJson(Buffer *buf, string ip);

    const map<int,vector<string>>& getJsonMap()
    {
        return jsonMap;
    }

public:
    int totalId;

private:
    TcpServer server_;
    SqlConnection sqlConnection_;
    map<int,vector<string>> &jsonMap;
    unordered_set<string> st;
};

#endif // JSONPROCESS_H

jsonprocess.cpp

#include "jsonprocess.h"
#include <iostream>

JsonProcess::JsonProcess(EventLoop *loop, const InetAddress &listenAddr, map<int,vector<string>> &mp):server_(loop, listenAddr, "JsonProcess"), jsonMap(mp),totalId(0)
{
    server_.setMessageCallback(std::bind(&JsonProcess::onMessage, this, placeholders::_1, placeholders::_2, placeholders::_3));

    server_.setConnectionCallback(std::bind(&JsonProcess::onConnection, this, placeholders::_1));

    sqlConnection_.connectToSqlServer();

    sqlConnection_.query(jsonMap, totalId);

//    for(auto it=jsonMap.begin(); it!=jsonMap.end(); ++it)
//    {
//        st.insert(it->second);
//    }
}

void JsonProcess::start()
{
    LOG_INFO << "HttpServer[" << server_.name() << "] starts listenning on " << server_.ipPort();
    server_.start();
}

void JsonProcess::onConnection(const TcpConnectionPtr &conn)
{
    LOG_INFO << "New JsonProcess Connection: " << conn->peerAddress().toIpPort();
}

void JsonProcess::onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp t)
{
    LOG_INFO << "New Json Message";
    parseJson(buf, conn->peerAddress().toIp());
}

void JsonProcess::parseJson(Buffer *buf, string ip)
{
    const char *str = buf->peek();
    //Do not handle sticking package FIX ME:
    string jsonStr(str, str+buf->readableBytes());

    //Parsing json
    Json::Value jsonRoot;
    Json::Reader jsonReader;
    if(!jsonReader.parse(jsonStr, jsonRoot))
    {
        LOG_WARN << "Json Message is not completed";
        return ;
    }
//    int id = jsonRoot["id"].asInt();    // Discard
    int id = totalId++;
    string license_plate = jsonRoot["license_plate"].asString();
    LOG_INFO << "parse json result:" << id << ":" << license_plate;

    //Check duplicate
//    if(st.find(license_plate) != st.end())
//    {
//        LOG_WARN << "license already existed!!";
//        buf->retrieveAll();
//        return ;
//    }
//    else
//        st.insert(license_plate);

    long long myTime = Timestamp::now().secondsSinceEpoch();
    Timestamp t((myTime+28800)*1e6);

    //Insert into mysql
    if(!sqlConnection_.insert(id, license_plate, myTime, ip))
    {
        LOG_WARN << "insert to sql fail!!";
        return ;
    }

    //Insert into map
    jsonMap[id] = vector<string>{license_plate, t.toFormattedString(), ip};
    LOG_INFO << id << ":" << license_plate;

    buf->retrieveAll();
}

5. Implementation of SQLConnection:

It mainly uses the means of obtaining RAII to ensure the life cycle of SQL connection. Query and insert operations are very standard C++SQL operations. The main codes of SQLConnection are as follows:
sqlconnection.h

#ifndef SQLCONNECTION_H
#define SQLCONNECTION_H

#include <map>
#include <vector>
#include <string>
#include <muduo/base/Logging.h>
#include <mysql/mysql.h>

using namespace std;
using namespace muduo;

class SqlConnection
{
public:
    SqlConnection(string server = "localhost", string user = "wye", string password = "nimabi123", string database = "license_plate");

    ~SqlConnection();

    bool connectToSqlServer();

    bool query(map<int,vector<string>> &mp, int& totalId);

    bool insert(int id, string license_plate, long long myTime, string ip);

    void disconnectFromSqlServer();
private:
    MYSQL *conn_;
    //
    string server_;
    string user_;
    string password_;
    string database_;
    //not used
    bool isConnected_;
    //
    MYSQL_RES *res;
    //
    MYSQL_ROW row;
    //
    int cols;
};

#endif // SQLCONNECTION_H

sqlconnection.cpp

#include "sqlconnection.h"

SqlConnection::SqlConnection(string server, string user, string password, string database):server_(server), user_(user), password_(password), database_(database), isConnected_(false)
{
    conn_ = mysql_init(NULL);
}

SqlConnection::~SqlConnection()
{
    disconnectFromSqlServer();
}

bool SqlConnection::connectToSqlServer()
{
    if(!mysql_real_connect(conn_, server_.c_str(), user_.c_str(), password_.c_str(), database_.c_str(), 0, NULL, 0))
    {
        LOG_WARN << mysql_error(conn_);
        return false;
    }

    if(mysql_query(conn_, "use licensePlate;"))
    {
        LOG_WARN << mysql_error(conn_);
        return false;
    }

    if(mysql_query(conn_, "set names utf8"))
    {
        LOG_WARN << mysql_error(conn_);
        return false;
    }

    if(mysql_query(conn_, "select * from cars;"))
    {
        LOG_WARN << mysql_error(conn_);
        return false;
    }

    res = mysql_store_result(conn_);     //Deep copy

    cols = mysql_num_fields(res);

    mysql_free_result(res);

    LOG_WARN << "Connect to MYSQL";
    return true;
}

bool SqlConnection::query(map<int, vector<string>> &mp, int& totalId)
{
    if(mysql_query(conn_, "SELECT id,license_plate,unix_timestamp(time),ip FROM cars;"))
    {
        LOG_WARN << mysql_error(conn_);
        return false;
    }

    res = mysql_store_result(conn_);     //Deep copy

//    int rows = mysql_num_rows(res);

    while((row = mysql_fetch_row(res)) != NULL)
    {
        int id;
        string license_plate;
        Timestamp myTime;
        string ip;
        for(int i=0;i<cols;++i)
        {
            switch(i)
            {
            case 0:
            {
                id = atoi(row[i]);
                break;
            }
            case 1:
            {
                license_plate = row[i];
                break;
            }
            case 2:
            {
                Timestamp t((atoll(row[i]) + 28800)*10e5);
//                LOG_INFO << t.secondsSinceEpoch()-28800;
                myTime.swap(t);
                break;
            }
            case 3:
            {
                ip = row[i];
                break;
            }
            }
        }
        mp[id] = vector<string>{license_plate, myTime.toFormattedString(), ip};
        totalId = ++id;
    }


    mysql_free_result(res);
    return true;
}

bool SqlConnection::insert(int id, string license_plate, long long myTime, string ip)
{
//    time_t myTime = Timestamp::now().secondsSinceEpoch();

    string sqlInsert = "insert into cars(license_plate,time,ip) values(\"";
    sqlInsert += license_plate;
    sqlInsert += "\",FROM_UNIXTIME(";
    sqlInsert += to_string(myTime);
    sqlInsert += "),\"";
    sqlInsert += ip;
    sqlInsert += "\");";
    if(mysql_query(conn_, sqlInsert.c_str()))
    {
        LOG_WARN << mysql_error(conn_);
        return false;
    }

    return true;
}

void SqlConnection::disconnectFromSqlServer()
{
    mysql_free_result(res);
    mysql_close(conn_);
    LOG_WARN << "Disconnect from sqlserver";
}

6. onRequest callback function / implementation of simple cgi:

In the initialization phase of JsonWebServer, the license plate hash table in local memory will be initialized accordingly, and the new Json data will update the local hash table. Then, in the specific onRequest callback function, the generated HTML page will insert all the license plates of the local hash table into it, realize the function of displaying the license plates of the database to the browser web page, and use unordered_set to perform the de duplication operation.
Callback function (simple cgi):

void onRequest(const HttpRequest& req, HttpResponse *resp)
{
    if(req.method() != HttpRequest::GET)
    {
        resp->setCloseConnection(true);
        resp->setStatusCode(HttpResponse::CODE_400);
        resp->setStatusMessage("Bad Request");
        return ;
    }

    string body;

    ifstream inFile;
    string path = req.path();
    int it = path.find('.');
    if(it != string::npos)
    {
        inFile.open("beijing.jpg", ios_base::in | ios_base::binary);
        resp->setContentType("image/jpg");

        if(!inFile.is_open())
        {
            resp->setStatusCode(HttpResponse::CODE_404);
            resp->setStatusMessage("Not Found");
            resp->setCloseConnection(true);
            return ;
        }

        char buf[1024];
        memset(buf, 0, sizeof(buf));
        while(!inFile.eof())
        {
            inFile.read(buf,sizeof(buf));
            body += string(buf,buf+sizeof(buf));
            memset(buf, 0, sizeof(buf));
        }
        inFile.close();
    }
    else
    {
        body += "<html><head><meta charset=\"utf8\"><title>License plate recognition system</title></head><body background=\"beijing.jpg\"><center><h1 style=\"color:#97CBFF;font-size:60px;\">License plate recognition system</h1></center><div style=\"width:15%;height:100px;float:left;\"></div><div style=\"width:70%;background-color:rgba(0,0,0,0.5);float:left;\"><table border=\"1\" border-color=\"#97CBFF\" style=\"width:100%; color:white;\ "> < tr > < th > ID number < / th > < th > license plate number < / th > < th > time < / th > < th > IP address < / th > < / TR >";
        int idx = 1;
        for(auto it=globalMap.begin();it!=globalMap.end();++it)
        {
            body += "<tr>";
            body += string("<td>") + to_string(idx++) + "</td>";
            body += string("<td>") + (it->second)[0] + "</td>";
            body += string("<td>") + (it->second)[1] + "</td>";
            body += string("<td>") + (it->second)[2] + "</td>";
            body += "</tr>";
        }
        body += "</table></div><div style = \"width:15%;height:100px;float:left;\"></div></body></html>";
        resp->setContentType("text/html");
    }

//    Body + = "< HTML > < head > < meta charset =" utf8 \ "> < title > license plate recognition < / Title > < / head > < center > < H1 > license plate system < / H1 > < / center > < ol style = \" color: blue \ ">";
//    for(auto it=globalMap.begin();it!=globalMap.end();++it)
//    {
//        body += string("<li>") + (it->second).first + " " + (it->second).second + "</li>";
//    }
//    body += "</ol><body></body></html>";


    resp->setBody(body);
    resp->setStatusCode(HttpResponse::CODE_200);
    resp->setStatusMessage("OK");
}

7. Implementation of main function:

According to the requirements of muduo network library, create a loop object, bind the loop object to the server class, and then set the IP address, port and onRequest callback function of HTTP of the two servers according to the requirements (generate an HTTP response that meets the requirements, which can be understood as a simple cgi), and finally start the server and start the loop.
main.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"
#include "httpserver.h"
#include "httpresponse.h"
#include "httprequest.h"
#include "jsonprocess.h"

using namespace muduo;
using namespace muduo::net;
using namespace std;


map<int,vector<string>> globalMap;

void onRequest(const HttpRequest& req, HttpResponse *resp)
{
    if(req.method() != HttpRequest::GET)
    {
        resp->setCloseConnection(true);
        resp->setStatusCode(HttpResponse::CODE_400);
        resp->setStatusMessage("Bad Request");
        return ;
    }

    string body;

    ifstream inFile;
    string path = req.path();
    int it = path.find('.');
    if(it != string::npos)
    {
        inFile.open("beijing.jpg", ios_base::in | ios_base::binary);
        resp->setContentType("image/jpg");

        if(!inFile.is_open())
        {
            resp->setStatusCode(HttpResponse::CODE_404);
            resp->setStatusMessage("Not Found");
            resp->setCloseConnection(true);
            return ;
        }

        char buf[1024];
        memset(buf, 0, sizeof(buf));
        while(!inFile.eof())
        {
            inFile.read(buf,sizeof(buf));
            body += string(buf,buf+sizeof(buf));
            memset(buf, 0, sizeof(buf));
        }
        inFile.close();
    }
    else
    {
        body += "<html><head><meta charset=\"utf8\"><title>License plate recognition system</title></head><body background=\"beijing.jpg\"><center><h1 style=\"color:#97CBFF;font-size:60px;\">License plate recognition system</h1></center><div style=\"width:15%;height:100px;float:left;\"></div><div style=\"width:70%;background-color:rgba(0,0,0,0.5);float:left;\"><table border=\"1\" border-color=\"#97CBFF\" style=\"width:100%; color:white;\ "> < tr > < th > ID number < / th > < th > license plate number < / th > < th > time < / th > < th > IP address < / th > < / TR >";
        int idx = 1;
        for(auto it=globalMap.begin();it!=globalMap.end();++it)
        {
            body += "<tr>";
            body += string("<td>") + to_string(idx++) + "</td>";
            body += string("<td>") + (it->second)[0] + "</td>";
            body += string("<td>") + (it->second)[1] + "</td>";
            body += string("<td>") + (it->second)[2] + "</td>";
            body += "</tr>";
        }
        body += "</table></div><div style = \"width:15%;height:100px;float:left;\"></div></body></html>";
        resp->setContentType("text/html");
    }

//    Body + = "< HTML > < head > < meta charset =" utf8 \ "> < title > license plate recognition < / Title > < / head > < center > < H1 > license plate system < / H1 > < / center > < ol style = \" color: blue \ ">";
//    for(auto it=globalMap.begin();it!=globalMap.end();++it)
//    {
//        body += string("<li>") + (it->second).first + " " + (it->second).second + "</li>";
//    }
//    body += "</ol><body></body></html>";


    resp->setBody(body);
    resp->setStatusCode(HttpResponse::CODE_200);
    resp->setStatusMessage("OK");
}

int main()
{
    LOG_INFO << "Hello muduo!";

    int numThreads = 2;
    EventLoop loop;

    HttpServer server(&loop, InetAddress("192.168.137.208", 51234));
    server.setHttpCallback(onRequest);
    server.setThreadNum(numThreads);
    server.start();

    JsonProcess jsonProcess(&loop, InetAddress("192.168.137.208", 51233), globalMap);
    jsonProcess.start();

    loop.loop();
    return 0;
}

Result display


summary

The small project shared this time uses the muduo network library. The network library of C + + is relatively rare, only muduo and boost There are few examples of aceduo and asduo, which can be used as the basis for the actual network research project.

reference resources

Linux multithreaded server programming: using muduo C + + Network Library -- Chen Shuo
Learn to write HTTP server from scratch (6) use muduo Network Library

Keywords: C++ Database network socket muduo

Added by clarky08 on Wed, 02 Feb 2022 03:21:07 +0200