基于muduo+mysql+jsoncpp的简易HTTPWebServer

news2025/3/11 15:34:31

一、项目介绍

        本项目基于C++语言、陈硕老师的muduo网络库、mysql数据库以及jsoncpp,服务器监听两个端口,一个端口用于处理http请求,另一个端口用于处理发送来的json数据。

        此项目在实现时,识别出车牌后打包为json数据发送给后端服务器保存在mydql数据库中并且显示在网页上。

二、主要模块介绍

1、基于muduo网络库的WebServer

        该WebServer基于muduo网络库与HTTP协议栈,muduo网络库主要负责socket连接的管理以及数据的管理,而在连接建立时,在连接的上下文context中初始化HTTPcontext,然后在接收到HTTP请求时,通过调用onMessage回调函数将接收到的数据保存在连接的HTTPcontext中并且处理HTTP请求,然后通过onRequest回调函数根据请求生成HTTP响应,在哈希表(与数据库同步)中获取所有车牌返回对应的HTML页面。

2、HTTP协议栈

        HTTP协议栈主要包括HTTPContext, HTTPRequest, HTTPResponse类
(1) HTTPContext类:主要用于逐行处理、储存HTTPRequest(HTTP请求),然后该类再储存在muduo的TCPConnection(连接类)中供回调函数使用,处理HTTP请求,可以避免TCP粘包的情况。
(2) HTTPRequest类:利用枚举类型储存HTTP请求方法,hashmap来储存一个连接的HTTP头部,string来储存请求的body。
(3) HTTPResponse类:默认返回400 NotFound,需要在WebServer中设置HTTP的onRequest回调,手动根据HTTP请求构造HTTP响应,需要设置状态码、状态码描述、响应头部、实体。

3、JsonServer

(1) JsonServer负责监听一个端口,在建立连接后,收到数据时,直接解析该Json是否完整,不完整就继续监听端口,并且将不完整的Json数据储存于连接的BUFFER中,在解析完整的Json表单后将BUFFER清空即可。
(2) 该server还包含一个SQLConnection,在解析到正确的Json后将车牌与ID储存于MySQL数据库中即可。

4、SQLConnection

通过RAII的手法控制SQL连接,保证SQL连接正确的生命周期,并且实现query、insert成员函数实现查询与插入的功能。

5、main模块

根据需求设置两个server的IP地址、端口、HTTP的onRequest的回调函数(生成符合需求的HTTP响应,可以理解为简易的cgi)

三、实现过程

1、手动实现HTTP协议栈

        实现一个简易HTTPWebServer光利用muduo网络库只能负责管理连接、数据,而实际应用层的HTTP协议栈需要自己实现,因此设计了HTTPContext, HTTPRequest, HTTPResponse。HTTPContext是每一个TCP连接对应的一次HTTP请求的一个上下文,包括请求,储存于muduo::net::TCPConnection的context中。HTTP解析的详细实现不必多说,HTTP协议是基础中的基础,代码中主要利用了C++内置的处理字符串的方法来解析HTTP请求,并利用unordered_map将解释出来的请求信息储存于HTTPRequest类的缓存中,而HTTPResponse则是专门为muduo网络库所设计,在设置好HTTP响应的请求行、请求头、实体后调用其appendToBuffer的成员函数然后从muduo网络库发送响应。代码如下:

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:
    //解析请求行
    bool processRequestLine(const char* begin, const char* end);

private:
    HttpRequestParseState state_;
    //解析结果保存在request_成员中
    HttpRequest request_;
};

#endif // HTTPCONTEXT_H

httpcontext.cpp

#include "httpcontext.h"

//解析请求行
bool HttpContext::processRequestLine(const char *begin, const char *end)
{
    bool succeed = false;
    const char *start = begin;
    const char *space = find(start, end, ' ');
    //设置请求方法 method_
    if(space != end && request_.setMethod(start, space))
    {
        start = space + 1;
        space = find(start, end, ' ');
        if(space != end)
        {
            //解析URI
            const char *question = find(start, space, '?');
            if(question != space)
            {
                request_.setPath(start, question);
                request_.setQuery(question, space);
            }
            else
            {
                request_.setPath(start, space);
            }

            //解析HTTP版本号
            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;
}

//解析请求头
bool HttpContext::parseRequest(Buffer *buf, Timestamp receiveTime)
{
    bool ok = true;
    bool hasMore = true;
    while(hasMore)
    {
        //解析请求行
        if(state_ == kExpectRequestLine)
        {
            const char *crlf = buf->findCRLF();
            if(crlf)
            {
                //开始解析请求行
                ok = processRequestLine(buf->peek(), crlf);
                if(ok)
                {
                    //解析成功
                    request_.setReceiveTime(receiveTime);
                    //回收请求行buffer
                    buf->retrieveUntil(crlf+2);
                    state_ = kExpectHeaders;
                }
                else
                {
                    hasMore = false;
                }
            }
            else
            {
                hasMore = false;
            }
        }
        //解析请求头
        else if(state_ == kExpectHeaders)
        {
            const char *crlf = buf->findCRLF();
            if(crlf)
            {
                //冒号
                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);//回收
            }
            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:
    //响应头
    unordered_map<string,string> headers_;
    //响应码
    HttpStatusCode statusCode_;
    //状态信息
    string statusMessage_;
    //是否keep_alive
    bool closeConnection_;
    //响应报文
    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];
    //构造响应行
    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需要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");
    //响应报文
    output->append(body_);
}


2、结合HTTP协议栈与MUDUO网络库实现HTTPWebServer

        muduo网络库是基于对象的,基于其设计服务器需要基于对象,在Server类中包含muoduo::net::TcpServer,然后注册连接、接收、断开等时刻的回调函数实现服务器的基础功能,主要HTTP设计代码如下:

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回调函数
    typedef function<void(const HttpRequest&,HttpResponse*)> HttpCallback;
    //构造、析构函数
    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();
}


//新连接回调
void HttpServer::onConnection(const TcpConnectionPtr &conn)
{
    if(conn->connected())
    {
        conn->setContext(HttpContext());
    }
}

//消息回调
void HttpServer::onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receiveTime)
{
    HttpContext *context = boost::any_cast<HttpContext>(conn->getMutableContext());
    //解析请求
    if(!context->parseRequest(buf, receiveTime))
    {
        conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
        conn->shutdown();
    }

    if(context->gotAll())
    {
        //请求解析完毕
        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);//构造响应
    httpCallback_(req, &response);
    Buffer buf;
    //此时response已经构造好,将向客户发送response添加到buffer中
    response.appendToBuffer(&buf);
    conn->send(&buf);
    //如果非Keep-Alive就关闭
    if(response.closeConnction())
    {
        conn->shutdown();
    }
}



3、JsonServer的实现

        JsonServer中还包含了SQLConnection,用于储存解析的Json数据到MySQL中。而具体实现还是基于muduo网络库,与HTTPServer类的设计大同小异,主要Json服务器设计代码如下:

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());
}
//将json数据解析并insert->mysql以及map中
void JsonProcess::parseJson(Buffer *buf, string ip)
{
    const char *str = buf->peek();
    //不处理粘包情况 FIX ME:
    string jsonStr(str, str+buf->readableBytes());

    //解析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();    //弃置
    int id = totalId++;
    string license_plate = jsonRoot["license_plate"].asString();
    LOG_INFO << "parse json result:" << id << ":" << license_plate;

    //查重
//    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);

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

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

    buf->retrieveAll();
}

4、SQLConnection的实现

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 = "root", string password = "111111", 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_;
    //未使用
    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 license_plate;"))
    {
        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_);     //深拷贝

    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_);     //深拷贝

//    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,mytime,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";
}

5、onRequest回调函数的实现

        在JsonWebServer初始化阶段会将本地内存的车牌哈希表进行相应的初始化,而新来的Json数据则会更新本地哈希表。然后具体onRequest回调函数中,生成的HTML页面会将本地哈希表的所有车牌插入其中,实现将数据库的车牌显示到浏览器网页的功能,并且使用unordered_set来进行去重操作。
  

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>车牌识别系统</title></head><body background=\"beijing.jpg\"><center><h1 style=\"color:#97CBFF;font-size:60px;\">车牌识别系统</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号</th><th>车牌号码</th><th>时间</th><th>IP地址</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>车牌识别</title></head><center><h1>车牌系统</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");
}

6、main函数的实现

        根据muduo网络库的要求,创建loop对象,绑定loop对象到服务器类中,然后根据需求设置两个server的IP地址、端口、HTTP的onRequest的回调函数(生成符合需求的HTTP响应),最后start服务器并且启动loop即可。

#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;

#if 0
void init_mysql()
{
    MYSQL *conn;
    MYSQL_RES *res;
    MYSQL_ROW row;
    char server[] = "localhost";
    char user[] = "wye";
    char password[] = "nimabi123";
    char database[] = "license_plate";

    conn = mysql_init(NULL);

    if(!mysql_real_connect(conn, server, user, password, database, 0, NULL, 0))
    {
        LOG_WARN << mysql_error(conn);
        exit(-1);
    }

    mysql_query(conn, "use license_plate;");

    mysql_query(conn, "set names utf8");

    mysql_query(conn, "select * from cars;");

    res = mysql_store_result(conn);     //深拷贝

    cout << "MYSQL Tables in license_plate:" << endl;

    int cols = mysql_num_fields(res);
    cout << "total columns:" << cols << endl;

    int rows = mysql_num_rows(res);
    cout << "total rows:" << rows << endl;

    for(int i=0;i<cols;++i)
    {
        cout << mysql_fetch_field(res)->name << " ";
    }
    cout << endl;

    while((row = mysql_fetch_row(res)) != NULL)
    {
        for(int i=0;i<cols;++i)
        {
            cout << row[i] << " ";
        }
        cout << endl;
    }

    mysql_free_result(res);
    mysql_close(conn);

    cout << "finished" << endl;
}
#endif

#if 0
void onRequest(const HttpRequest& req, HttpResponse *resp)
{
    LOG_INFO << CurrentThread::tid();
    if(req.method() != HttpRequest::GET)
    {
        resp->setStatusCode(HttpResponse::CODE_400);
        resp->setStatusMessage("Bad Request");
        resp->setCloseConnection(true);
    }
    else
    {
        ifstream inFile;
        string path = req.path();

        int it = path.find('.');
        if(it == string::npos)
        {
            inFile.open("index.html", ios_base::in | ios_base::binary);
            resp->setContentType("text/html");
        }
        else
        {
            string str = path.substr(it+1);
            if(str == "jpg")
            {
                inFile.open("bird.jpg", ios_base::in | ios_base::binary);
                resp->setContentType("image/jpg");
            }
            else if(str == "html")
            {
                inFile.open("index.html", ios_base::in | ios_base::binary);
                resp->setContentType("text/html");
            }
            else
            {
                resp->setStatusCode(HttpResponse::CODE_404);
                resp->setStatusMessage("Not Found");
                resp->setCloseConnection(true);
                return ;
            }

        }

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

        ostringstream oss;
        oss << inFile.rdbuf();
        string body(oss.str());


        string body;
        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();

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

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>车牌识别系统</title></head><body background=\"beijing.jpg\"><center><h1 style=\"color:#97CBFF;font-size:60px;\">车牌识别系统</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号</th><th>车牌号码</th><th>时间</th><th>IP地址</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>车牌识别</title></head><center><h1>车牌系统</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("127.0.0.1", 8080));
    server.setHttpCallback(onRequest);
    server.setThreadNum(numThreads);
    server.start();

    JsonProcess jsonProcess(&loop, InetAddress("127.0.0.1", 6001), globalMap);
    jsonProcess.start();

    loop.loop();
    return 0;
}

7、JsonProcess_Client的实现

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/Buffer.h"

using namespace muduo;
using namespace muduo::net;

class JsonProcessClient {
public:
    JsonProcessClient(EventLoop* loop, const InetAddress& serverAddr)
        : client_(loop, serverAddr, "JsonProcessClient"),
          buffer_() {
        client_.setConnectionCallback(
            std::bind(&JsonProcessClient::onConnection, this, _1));
        client_.setMessageCallback(
            std::bind(&JsonProcessClient::onMessage, this, _1, _2, _3));
    }

    void connect() {
        client_.connect();
    }

private:
    void onConnection(const TcpConnectionPtr& conn) {
        if (conn->connected()) {
            // 连接成功后,发送一条简单的 JSON 数据作为示例
            // 创建 JSON 对象
            Json::Value jsonData;
            jsonData["id"] = 1;
            jsonData["license_plate"] = "BCD123";
            jsonData["mytime"] = "2025-03-10 20:53:56";
            jsonData["ip"] = "192.168.1.100";

            // 将 JSON 对象转换为字符串
            Json::StreamWriterBuilder writerBuilder;
            std::string jsonString = Json::writeString(writerBuilder, jsonData);
            conn->send(jsonString);
        } else {
            std::cout << "Connection closed" << std::endl;
        }
    }

    void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) {
        buffer_.append(buf->peek(), buf->readableBytes());
        buf->retrieveAll();

        std::string receivedData(buffer_.peek(), buffer_.readableBytes());
        std::cout << "Received data from server: " << receivedData << std::endl;
        // 这里可以根据实际需求处理接收到的数据
    }

    TcpClient client_;
    Buffer buffer_;
};

int main() {
    EventLoop loop;
    InetAddress serverAddr("127.0.0.1", 6001);
    JsonProcessClient client(&loop, serverAddr);
    client.connect();
    loop.loop();
    return 0;
}

四、结果展示

五、数据库设计

+----+---------------+---------------------+-----------+
| id | license_plate | mytime              | ip        |
+----+---------------+---------------------+-----------+
|  1 | ABC123        | 2025-03-10 19:41:49 | 127.0.0.1 |
|  2 | BCD123        | 2025-03-10 20:53:48 | 127.0.0.1 |
+----+---------------+---------------------+-----------+

六、参考

C++练手项目(基于muduo网络库+mysql+jsoncpp的简易HTTPWebServer用于网页显示数据库后台数据_c++结合网络通信结合数据库等的项目-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2313280.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Go学习实战】03-2-博客查询及登录

【Go学习实战】03-2-博客查询及登录 读取数据库数据初始化数据库首页真实数据分类查询分类查询测试 文章查询文章查询测试 分类文章列表测试 登录功能登录页面登录接口获取json参数登录失败测试 md5加密jwt工具 登录成功测试 文章详情测试 读取数据库数据 因为我们之前的数据都…

《Python实战进阶》No20: 网络爬虫开发:Scrapy框架详解

No20: 网络爬虫开发&#xff1a;Scrapy框架详解 摘要 本文深入解析Scrapy核心架构&#xff0c;通过中间件链式处理、布隆过滤器增量爬取、Splash动态渲染、分布式指纹策略四大核心技术&#xff0c;结合政府数据爬取与动态API逆向工程实战案例&#xff0c;构建企业级爬虫系统。…

Linux:多线程(单例模式,其他常见的锁,读者写者问题)

目录 单例模式 什么是设计模式 单例模式介绍 饿汉实现方式和懒汉实现方式 其他常见的各种锁 自旋锁 读者写者问题 逻辑过程 接口介绍 单例模式 什么是设计模式 设计模式就是一些大佬在编写代码的过程中&#xff0c;针对一些经典常见场景&#xff0c;给定对应解决方案&…

【氮化镓】高输入功率应力诱导的GaN 在下的退化LNA退化

2019年,中国工程物理研究院电子工程研究所的Tong等人基于实验与第一性原理计算方法,研究了Ka波段GaN低噪声放大器(LNA)在高输入功率应力下的退化机制。实验结果表明,在27 GHz下施加1 W连续波(CW)输入功率应力后,LNA的增益下降约1 dB,噪声系数(NF)增加约0.7 dB。进一…

Javaweb后端文件上传@value注解

文件本地存储磁盘 阿里云oss准备工作 阿里云oss入门程序 要重启一下idea&#xff0c;上面有cmd 阿里云oss案例集成 优化 用spring中的value注解

git规范提交之commitizen conventional-changelog-cli 安装

一、引言 使用规范的提交信息可以让项目更加模块化、易于维护和理解&#xff0c;同时也便于自动化工具&#xff08;如发布工具或 Changelog 生成器&#xff09;解析和处理提交记录。 通过编写符合规范的提交消息&#xff0c;可以让团队和协作者更好地理解项目的变更历史和版本…

Java/Kotlin逆向基础与Smali语法精解

1. 法律警示与道德边界 1.1 司法判例深度剖析 案例一&#xff1a;2021年某游戏外挂团伙刑事案 犯罪手法&#xff1a;逆向《王者荣耀》通信协议&#xff0c;修改战斗数据包 技术细节&#xff1a;Hook libil2cpp.so的SendPacket函数 量刑依据&#xff1a;非法经营罪&#xff…

非软件开发项目快速上手:14款管理软件精选

文章介绍了以下14款项目管理系统&#xff1a;1.Worktile&#xff1b;2.Teambition&#xff1b;3.Microsoft Project&#xff1b;4.Forbes&#xff1b;5.WorkOtter&#xff1b;6.Trello&#xff1b;7.Smartsheet&#xff1b;8.Taiga&#xff1b;9.ClickUp&#xff1b;10.Monday.…

夸父工具箱(安卓版) 手机超强工具箱

如今&#xff0c;人们的互联网活动日益频繁&#xff0c;导致手机内存即便频繁清理&#xff0c;也会莫名其妙地迅速填满&#xff0c;许多无用的垃圾信息悄然占据空间。那么&#xff0c;如何有效应对这一难题呢&#xff1f;答案就是今天新推出的这款工具软件&#xff0c;它能从根…

混元图生视频-腾讯混元开源的图生视频模型

混元图生视频是什么 混元图生视频是腾讯混元推出的开源图生视频模型&#xff0c;用户可以通过上传一张图片进行简短描述&#xff0c;让图片动起来生成5秒的短视频。模型支持对口型、动作驱动和背景音效自动生成等功能。模型适用于写实、动漫和CGI等多种角色和场景&#xff0c;…

Debian系统grub新增启动项

参考链接 给grub添加自定义启动项_linux grub定制 启动项名称自定义-CSDN博客 www.cnblogs.com 1. boot里面的grub.cfg 使用vim打开boot里面的grub.cfg sudo vim /boot/grub/grub.cfg 这时候会看到文件最上方的提示 2. 真正配置grub的文件 从刚才看到的文件提示中&#x…

VSCode快捷键整理

VSCode快捷键整理 文章目录 VSCode快捷键整理1-VSCode 常用快捷键1-界面操作2-单词移动3-删除操作4-编程相关5-多光标操作6-文件、符号、函数跳转7-鼠标操作8-自动补全操作9-代码折叠操作 1-VSCode 常用快捷键 1-界面操作 文件资源管理器&#xff1a;Ctrl Shift E 跨文件搜…

刘火良 FreeRTOS内核实现与应用之1——列表学习

重要数据 节点的命名都以_ITEM后缀进行&#xff0c;链表取消了后缀&#xff0c;直接LIST 普通的节点数据类型 /* 节点结构体定义 */ struct xLIST_ITEM { TickType_t xItemValue; /* 辅助值&#xff0c;用于帮助节点做顺序排列 */ struct xLIST_I…

本地部署Navidrome个人云音乐平台随时随地畅听本地音乐文件

文章目录 前言1. 安装Docker2. 创建并启动Navidrome容器3. 公网远程访问本地Navidrome3.1 内网穿透工具安装3.2 创建远程连接公网地址3.3 使用固定公网地址远程访问 前言 今天我要给大家安利一个超酷的私有化音乐神器——Navidrome&#xff01;它不仅让你随时随地畅享本地音乐…

数据集构建与训练前准备

训练数据集目录结构与格式 作者笨蛋学法&#xff0c;先将其公式化&#xff0c;后面逐步自己进行修改&#xff0c;读者觉得看不懂可以理解成&#xff0c;由结果去推过程&#xff0c;下面的这个yaml文件就是结果&#xff0c;我们去推需要的文件夹(名字可以不固定&#xff0c;但是…

jenkins+ant+jmeter生成的测试报告空白

Jenkins能正常构建成功&#xff0c;但是打开Jenkins上的测试报告&#xff0c;则显示空白 在网上找了很多文章&#xff0c;结果跟别人对比测试报告的配置&#xff0c;发现自己跟别人写的不一样 所以跟着别人改&#xff0c;改成一样的再试试 结果&#xff0c;好家伙&#xff0…

利用阿里云Atlas地区选择器与Plotly.js实现数据可视化与交互

在数据科学与可视化领域&#xff0c;交互式图表和地图应用越来越成为数据分析和展示的重要手段。本文将介绍如何结合阿里云Atlas地区选择器与Plotly.js&#xff0c;创建动态交互式的数据可视化应用。 一、阿里云Atlas地区选择器简介 阿里云Atlas是阿里云的一款数据可视化产品…

linux安装java8 sdk,使用 tar.gz安装包手动安装

1. 下载 Java 8 SDK 首先&#xff0c;需要从 Oracle 的官方网站或 OpenJDK 的网站下载 Java 8 的 .tar.gz 文件。并上传到服务器 2. 解压 JDK 下载完成后&#xff0c;使用 tar 命令解压文件。打开服务器终端&#xff0c;然后使用以下命令&#xff1a; tar -xvzf jdk-8uXXX-…

6.聊天室环境安装 - Ubuntu22.04 - elasticsearch(es)的安装和使用

目录 介绍安装安装kibana安装ES客户端使用 介绍 Elasticsearch&#xff0c; 简称 ES&#xff0c;它是个开源分布式搜索引擎&#xff0c;它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自动发现&#xff0c;索引自动分片&#xff0c;索引副本机制&#xff0c;res…

【python爬虫】酷狗音乐爬取练习

注意&#xff1a;本次爬取的音乐仅有1分钟试听&#xff0c;仅作学习爬虫的原理&#xff0c;完整音乐需要自行下载客户端。 一、 初步分析 登陆酷狗音乐后随机选取一首歌&#xff0c;在请求里发现一段mp3文件&#xff0c;复制网址&#xff0c;确实是我们需要的url。 复制音频的…