C++练手小项目(基于muduo网络库+mysql+jsoncpp的简易HTTPWebServer用于网页显示数据库后台数据
2022/1/30 19:04:20
本文主要是介绍C++练手小项目(基于muduo网络库+mysql+jsoncpp的简易HTTPWebServer用于网页显示数据库后台数据,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
基于muduo网络库+mysql+jsoncpp的简易HTTPWebServer
- 项目介绍
- 背景介绍
- 主要模块介绍
- 1、基于muduo网络库的WebServer:
- 2、HTTP协议栈
- 3、JsonServer:
- 4、SQLConnection
- 5、main模块
- 实现过程
- 1、确定如何实现一个HTTPWebServer
- 2、手动实现HTTP协议栈
- 3、结合HTTP协议栈与muduo网络库实现HTTPWebServer
- 4、JsonServer的实现:
- 5、SQLConnection的实现:
- 6、onRequest回调函数/简易cgi的实现:
- 7、main函数的实现:
- 结果展示
- 总结
- 参考
项目介绍
项目github:github链接
本项目基于C++语言、陈硕老师的muduo网络库、mysql数据库以及jsoncpp,服务器监听两个端口,一个端口用于处理http请求、另一个端口用于处理发送来的json数据。
此项目是配合一个qt+opencv车牌识别前端程序,识别出车牌后打包为json数据发送给后端服务器保存在mysql数据库中并且显示在网页上。
背景介绍
本项目为个人学习所编写的小项目,没有非常细致地考虑到非常完整的HTTP协议栈等内容。可只监听80号端口使用POST方法来传输JSON数据,但是考虑到信息来源不一定实现了HTTP的POST协议,因此另监听一个端口只传输JSON数据比较方便。
主要模块介绍
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、确定如何实现一个HTTPWebServer
在刚开始项目时,需要确定如何用C++语言实现一个简单的HTTPWebServer,可以用基础的socket编程+手动实现HTTP协议栈从底层实现;可以使用现成的WebServer,如lighttpd、apache、nginx等服务器+cgi程序实现。但是最终我选择使用陈硕老师的muduo网络库+手动实现HTTP协议栈从应用层面实现一个WebServer,原因如下:首先之前我自己就已经从底层实现过一个WebServer(github:tinyWebServer),使用的是非阻塞IO模型+reactor模拟proactor的事件处理模式,因此并不想copy代码实现该项目,其次muduo网络库有非常多值得学习的细节,例如如何正确高效地管理socket连接、不同于我之前实现WebServer的模型,muduo网络库使用主reactor+从reactor,per thread one loop的模型的事件处理模式。
2、手动实现HTTP协议栈
PS:此处参考了nearXDU大佬的文章
实现一个简易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_); }
3、结合HTTP协议栈与muduo网络库实现HTTPWebServer
PS:可以在此注册定时器来主动关闭超时链接
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(); } }
4、JsonServer的实现:
解析Json有很多方法,最初想使用Boost库来解析,但是在1.75版本前解析Json需要用到boost::property_tree,操作较为复杂,一段优秀的代码应该是使用更好的轮子写出来的,并且会是通俗易懂的。因此推荐,并且我也使用了Jsoncpp库来解析Json数据。然后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()); } 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(); }
5、SQLConnection的实现:
主要用得到RAII的手段,保证SQL连接的生命周期,查询、插入操作则是很标准的C++SQL操作,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 = "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_; //未使用 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_); //深拷贝 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,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回调函数/简易cgi的实现:
在JsonWebServer初始化阶段会将本地内存的车牌哈希表进行相应的初始化,而新来的Json数据则会更新本地哈希表。然后具体onRequest回调函数中,生成的HTML页面会将本地哈希表的所有车牌插入其中,实现将数据库的车牌显示到浏览器网页的功能,并且使用unordered_set来进行去重操作。
回调函数(简易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>车牌识别系统</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"); }
7、main函数的实现:
根据muduo网络库的要求,创建loop对象,绑定loop对象到服务器类中,然后根据需求设置两个server的IP地址、端口、HTTP的onRequest的回调函数(生成符合需求的HTTP响应,可以理解为简易的cgi),最后start服务器并且启动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>车牌识别系统</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("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; }
结果展示
总结
本次分享的小项目使用了muduo网络库,C++的网络库本来就比较稀有,仅有muduo、boost.Asio、libevent、ACE这几个,而实际使用muduo网络库来编写的项目就非常稀少,因此这次这个项目可以有抛砖引玉的作用,为有兴趣研究网络编程的同学提供一个以muduo网络库为基础的项目实例。
参考
《Linux多线程服务端编程:使用muduo C++网络库》 ——陈硕
从零开始学写HTTP服务器(六)使用muduo网络库
这篇关于C++练手小项目(基于muduo网络库+mysql+jsoncpp的简易HTTPWebServer用于网页显示数据库后台数据的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-16Vue3资料:新手入门必读教程
- 2024-11-16Vue3资料:新手入门全面指南
- 2024-11-16Vue资料:新手入门完全指南
- 2024-11-16Vue项目实战:新手入门指南
- 2024-11-16React Hooks之useEffect案例详解
- 2024-11-16useRef案例详解:React中的useRef使用教程
- 2024-11-16React Hooks之useState案例详解
- 2024-11-16Vue入门指南:从零开始搭建第一个Vue项目
- 2024-11-16Vue3学习:新手入门教程与实践指南
- 2024-11-16Vue3学习:从入门到初级实战教程