基于muduo+mysql+jsoncpp的简易HTTPWebServer
一、项目介绍
本项目基于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){//解析URIconst 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);//回收请求行bufferbuf->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 whilereturn 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_alivebool 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-Lengthsnprintf(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());//解析jsonJson::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);//插入到mysqlif(!sqlConnection_.insert(id, license_plate, myTime, ip)){LOG_WARN << "insert to sql fail!!";return ;}//插入到mapjsonMap[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");}
}
#endifmap<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博客
相关文章:
基于muduo+mysql+jsoncpp的简易HTTPWebServer
一、项目介绍 本项目基于C语言、陈硕老师的muduo网络库、mysql数据库以及jsoncpp,服务器监听两个端口,一个端口用于处理http请求,另一个端口用于处理发送来的json数据。 此项目在实现时,识别出车牌后打包为json数据发送给后端服务…...
Java/Kotlin逆向基础与Smali语法精解
1. 法律警示与道德边界 1.1 司法判例深度剖析 案例一:2021年某游戏外挂团伙刑事案 犯罪手法:逆向《王者荣耀》通信协议,修改战斗数据包 技术细节:Hook libil2cpp.so的SendPacket函数 量刑依据:非法经营罪ÿ…...
C++:入门详解(关于C与C++基本差别)
目录 一.C的第一个程序 二.命名空间(namespace) 1.命名空间的定义与使用: (1)命名空间里可以定义变量,函数,结构体等多种类型 (2)命名空间调用(…...
CI/CD—GitLab钩子触发Jenkins自动构建项目
GitLab钩子简介: 项目钩子 项目钩子是针对单个项目的钩子,会在项目级别的特定事件发生时触发。这些事件包括代码推送、合并请求创建、问题创建等。项目钩子由项目管理员或具有相应权限的用户进行配置,仅对特定项目生效。 使用场景:…...
RPA 职业前景:个人职场发展的 “新机遇”
1. RPA职业定义与范畴 1.1 RPA核心概念 机器人流程自动化(RPA)是一种通过软件机器人模拟人类操作,自动执行重复性、规则性任务的技术。RPA的核心在于其能够高效、准确地处理大量数据和流程,减少人工干预,从而提高工作…...
【CSS3】金丹篇
目录 标准流概念元素类型及排列规则块级元素行内元素行内块元素 标准流的特点打破标准流 浮动基本使用清除浮动额外标签法单伪元素法双伪元素法(推荐)overflow 法 Flex 布局Flex 组成主轴对齐方式侧轴对齐方式修改主轴方向弹性盒子伸缩比弹性盒子换行行对…...
Git(一)
一、介绍 二、Git代码托管服务 三、Git常用命令 全局设置: 获取Git仓库: 工作区、暂存区、版本库概念: Git工作区文件的状态: 本地仓库操作: 远程仓库操作: 分支操作: 标签操作: 四…...
Python大数据可视化:基于spark的短视频推荐系统的设计与实现_django+spider
开发语言:Python框架:djangoPython版本:python3.7.7数据库:mysql 5.7数据库工具:Navicat11开发软件:PyCharm 系统展示 管理员登录 管理员功能界面 热门视频界面 用户界面 用户反馈界面 论坛交流界面 系统…...
面试题之react useMemo和uesCallback
在面试中,关于 React 中的 useMemo 和 useCallback 的区别 是一个常见的问题。 useMemo 和 useCallback 的区别 1. 功能定义 useMemo: 用于缓存计算结果,避免在每次组件渲染时重新计算复杂的值。它接受一个计算函数和一个依赖数组࿰…...
K8S学习之基础十九:k8s的四层代理Service
K8S四层代理Service 四层负载均衡Service 在k8s中,访问pod可以通过ip端口的方式,但是pod是由生命 周期的,pod在重启的时候ip地址往往会发生变化,访问pod就需要新的ip地址,这样就会很麻烦,每次pod地址改变就…...
C++:string容器(下篇)
1.string浅拷贝的问题 // 为了和标准库区分,此处使用String class String { public :/*String():_str(new char[1]){*_str \0;}*///String(const char* str "\0") // 错误示范//String(const char* str nullptr) // 错误示范String(const char* str …...
sudo systemctl restart docker 重启docker失败
一般会使用如下命令,进行docker重启。 sudo systemctl daemon-reload sudo systemctl restart docker 重启失败时,会提示:Job for docker.service failed because the control process exited with error code. See "systemctl status…...
Linux基本操作指令3
1、wget: 这是一个用于从网络上下载文件的命令行工具。它支持 HTTP、HTTPS 和 FTP 协议。 wget http://download.qt.io/archive/qt/5.12/5.12.9/qt-opensource-linux-x64-5.12.9.run 2、下载完成后,你可以通过以下命令使文件可执行并运行安装程序: ch…...
React:类组件(上)
kerwin老师我来了 类组件的创建 class组件,js里的类命名首字符大写,类里面包括构造函数,方法 组件类要继承React.Component才有效 必须包含render方法 import React from react class App extends React.Component{render() {return <…...
sqli-lab靶场学习(七)——Less23-25(关键字被过滤、二次注入)
前言 之前的每一关,我们都是在末尾加上注释符,屏蔽后面的语句,这样我们只要闭合了区间之后,差不多就是为所欲为的状态。但如果注释符不生效的情况下,又该如何呢? Less23(注释符被过滤ÿ…...
虚函数和虚表的原理是什么?
虚函数是一个使用virtual关键字声明的成员函数,在基类中声明虚函数,在子类中可以使用override重写该函数。虚函数根据指针或引用指向的实际对象调用,实现运行时的多态。 虚函数表(虚表)是一个用于存储虚函数地址的数组…...
RReadWriteLock读写锁应用场景
背景 操作涉及一批数据,如订单,可能存在多个场景下操作,先使用读锁,从redis缓存中获取操作中数据 比如 关闭账单, 发起调账, 线下结算, 合并支付 先判断当前操作的数据,是否在…...
【面试】MySQL
MySQL 1、数据库三范式2、什么是关系型数据库,什么是非关系型数据库3、什么是数据库存储引擎4、MySQL5.x和8.0有什么区别5、char 和 varchar 的区别6、in 和 exists 的区别7、MySQL 时间类型数据存储建议8、drop、delete 与 truncate 区别9、一条 Sql 的执行顺序10、…...
Trae AI 开发工具使用手册
这篇手册将介绍 Trae 的基本功能、安装步骤以及使用方法,帮助开发者快速上手这款工具。 Trae AI 开发工具使用手册 Trae 是字节跳动于 2025 年推出的一款 AI 原生集成开发环境(IDE),旨在通过智能代码生成、上下文理解和自动化任务…...
表格columns拼接两个后端返回的字段(以umi框架为例)
在用组件对前端项目进行开发时,我们会遇到以下情况:项目原型中有取值范围这个表字段,需要存放最小取值到最大取值。 而后端返回给我们的数据是返回了一个最小值和一个最大值, 在columns中我们需要对这两个字段进行拼接࿰…...
常见的算法题python
字符串倒序 def func1(str):return str[::-1] def func2(str):new_str ""for i in str:new_str inew_strreturn new_str if __name____main__:str"linda"print(func2(str))合并两个有序的列表 def func3(list1,list2):for i in list1:list2.append(i)li…...
linux学习(十)(磁盘和文件系统(索引节点,文件系统,添加磁盘,交换,LVM公司,挂载))
Linux 磁盘文件系统 Linux 使用各种文件系统来允许我们从计算机系统的硬件(例如磁盘)存储和检索数据。文件系统定义了如何在这些存储设备上组织、存储和检索数据。流行的 Linux 文件系统示例包括 EXT4、FAT32、NTFS 和 Btrfs。 每个文件系统都有自己的…...
k8s v1.28.15部署(kubeadm方式)
k8s部署(kubeadm方式) 部署环境及版本 系统版本:CentOS Linux release 7.9.2009 k8s版本:v1.28.15 docker版本:26.1.4 containerd版本:1.6.33 calico版本:v3.25.0准备 主机ip主机名角色配置1…...
Python开发Scikit-learn面试题及参考答案
目录 如何用 SimpleImputer 处理数据集中的缺失值? 使用 StandardScaler 对数据进行标准化的原理是什么?与 MinMaxScaler 有何区别? 如何用 OneHotEncoder 对类别型特征进行编码? 解释特征选择中 SelectKBest 与 VarianceThreshold 的应用场景。 如何通过 PolynomialFe…...
Java在小米SU7 Ultra汽车中的技术赋能
目录 一、智能驾驶“大脑”与实时数据 场景一:海量数据的分布式计算 场景二:实时决策的毫秒级响应 场景三:弹性扩展与容错机制 技术隐喻: 二、车载信息系统(IVI)的交互 场景一:Android Automo…...
蓝队第三次
1.了解什么是盲注 盲注(Blind SQL Injection)是SQL注入的一种形式,攻击者无法直接通过页面回显或错误信息获取数据,而是通过观察页面的布尔状态(真/假)或时间延迟来间接推断数据库信息。例如,通…...
Element Plus中的树组件的具体用法(持续更新!)
const defaultProps {//子树为节点对象的childrenchildren: children,//节点标签为节点对象的name属性label: name, } 属性 以下是树组件中的常用属性以及作用: data:展示的数据(数据源) show-checkbox:节点是否可…...
nodejs使用WebSocket实现聊天效果
在nodejs中使用WebSocket实现聊天效果(简易实现) 安装 npm i ws 实现 创建 server.js /*** 创建一个 WebSocket 服务器,监听指定端口,并处理客户端连接和消息。** param {Object} WebSocket - 引入的 WebSocket 模块,…...
通领科技冲刺北交所
高质量增长奔赴产业新征程 日前,通领科技已正式启动在北交所的 IPO 进程,期望借助资本市场的力量,加速技术升级,推动全球化战略布局。这一举措不仅展现了中国汽车零部件企业的强大实力,也预示着行业转型升级的新突破。…...
利用LLMs准确预测旋转机械(如轴承)的剩余使用寿命(RUL)
研究背景 研究问题:如何准确预测旋转机械(如轴承)的剩余使用寿命(RUL),这对于设备可靠性和减少工业系统中的意外故障至关重要。研究难点:该问题的研究难点包括:训练和测试阶段数据分布不一致、长期RUL预测的泛化能力有限。相关工作:现有工作主要包括基于模型的方法、数…...
comctl32!ListView_OnSetItem函数分析LISTSUBITEM结构中的image表示图标位置
第一部分: BOOL ListView_SetSubItem(LV* plv, const LV_ITEM* plvi) { LISTSUBITEM lsi; BOOL fChanged FALSE; int i; int idpa; HDPA hdpa; if (plvi->mask & ~(LVIF_DI_SETITEM | LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) { …...
Django工程获取请求参数的几种方式
在 Django 中获取请求参数的完整方法如下: 一、GET 请求参数获取 def view_func(request):# 获取单个参数(推荐方式)name request.GET.get(name, default) # 带默认值age request.GET.get(age, 0)# 获取多个同名参数(如复选框…...
使用Qt调用HslCommunication(C++调用C#库)
使用C/CLI 来调用C#的dll 任务分解: 1、实现C#封装一个调用hsl的dll; 2、实现C控制台调用C#的dll库; 3、把调用C#的dll用C再封装为一个dll; 4、最后再用Qt调用c的dll; 填坑: 1、开发时VS需要安装CLI项目库…...
C++中的构造函数
目录 一、什么是构造函数: 二、构造函数的特性和使用: 1、构造函数的特性: 2、构造函数的重载: 三、默认生成的构造函数: 一、什么是构造函数: 在C中,当创建一个对象之后,就会自…...
MySQL知识点(第一部分)
MySQL 基础: 1、SQL语句的分类: DDL:用于控制数据库的操作DML:用于控制表结构的字段,增、删、修DQL:用于查询语句DCL:用于管理数据库,用户,数据库的访问 权限。 2、M…...
Lua怎么学?Lua编程实战:从基础语法到高级特性
朋友们,大家好,我是袁庭新。我的《Lua编程实战:从基础语法到高级特性》教程上线了!这是一套从零掌握Lua编程,涵盖基础到高级,以实战驱动,快速上手Lua开发的精品教程! 我为什么要研发…...
Qt常用控件之 纵向列表QListWidget
纵向列表QListWidget QListWidget 是一个纵向列表控件。 QListWidget属性 属性说明currentRow当前被选中的是第几行。count一共有多少行。sortingEnabled是否允许排序。isWrapping是否允许换行。itemAlignment元素的对齐方式。selectRectVisible被选中的元素矩形是否可见。s…...
Manus无需邀请码即可使用的平替方案-OpenManus实测
文章目录 Manus 简介核心定位技术架构核心特点应用场景性能表现用户体验发展计划OpenManus技术架构与设计理念核心功能特性应用场景案例与闭源Manus的差异对比安装使用与实战演示执行过程记录简单案例-快速写一个helloworld的java程序复杂案例-分析特斯拉汽车近三年财务数据并生…...
git如何解除远程仓库 改变远程仓库地址
这三个命令通常一起使用来更改项目的远程仓库地址: 查看当前远程仓库配置:git remote -v移除旧的远程仓库配置:git remote remove origin添加新的远程仓库配置:git remote add origin 新的远程仓库地址 要解除当前项目的远程仓库…...
VSCode 配置优化指南:打造高效的 uni-app、Vue2/3、JS/TS 开发环境
VSCode 配置优化指南,适用于 uni-app、Vue2、Vue3、JavaScript、TypeScript 开发,包括插件推荐、设置优化、代码片段、调试配置等,确保你的开发体验更加流畅高效。 1. 安装 VSCode 如果你还未安装 VSCode,可前往 VSCode 官网 下载最新版并安装。 2. 安装推荐插件 (1) Vue…...
云计算:虚拟化、容器化与云存储技术详解
在上一篇中,我们深入探讨了网络安全的核心技术,包括加密、认证和防火墙,并通过实际案例和细节帮助读者全面理解这些技术的应用和重要性。今天,我们将转向一个近年来迅速发展的领域——云计算。云计算通过提供按需访问的计算资源,彻底改变了IT基础设施的构建和管理方式。本…...
oracle 数据导出方案
工作中有遇到需要将oracle 数据库表全部导出,还需要去除表数据中的换行符。 方案 shell 设计 封装函数 1 function con_oracle() 用于连接oracle 2 function send_file() 用于发送文件 3 主程序 使用循环将所有表导出并发送到数据服务器 主程序 程序代码 #!…...
电商项目-秒杀系统(四)秒杀异步下单防止重复秒杀
一、 防止恶意刷单解决 在生产场景下,可能会有一些人会恶意访问当前网站,来进行恶意的刷单。这样会造成当前系统出现一些业务上的业务混乱,出现脏数据,或者造成后端访问压力大等问题。 一般要解决这个问题的话,前端可…...
Express 中 get 参数获取
1. 使用 req.query 获取 URL 查询字符串参数 在 GET 请求中,参数通常以查询字符串的形式附加在 URL 后面,格式为 ?参数名1值1&参数名2值2 。Express 里可通过 req.query 对象获取这些参数。 const express require("express"); const …...
《Python实战进阶》No17: 数据库连接与 ORM(SQLAlchemy 实战)
No17: 数据库连接与 ORM(SQLAlchemy 实战) 摘要 本文深入探讨SQLAlchemy在复杂场景下的高级应用,涵盖四大核心主题: 会话生命周期管理:通过事件钩子实现事务监控与审计追踪混合继承映射:结合单表/连接表继…...
运行OpenManus项目(使用Conda)
部署本项目需要具备一定的基础:Linux基础、需要安装好Anaconda/Miniforge(Python可以不装好,直接新建虚拟环境的时候装好即可),如果不装Anaconda或者Miniforge,只装过Python,需要确保Python是3.…...
Linux一键美化命令行,一键安装zsh终端插件
zsh应该是很多人第一个用的Linux终端美化软件 但是其安装略微复杂,让人有些困扰 所以我花了两天写了一键安装脚本,实测运行后直接安装好 适用于Ubuntu、Debian、Red Hat、macOS等系统 直接安装好zsh 以及常用插件 autojump 跳转插件 zsh-syntax-highlig…...
OpenManus介绍及本地部署体验
1.OpenManus介绍 OpenManus,由 MetaGPT 团队精心打造的开源项目,于2025年3月发布。它致力于模仿并改进 Manus 这一封闭式商业 AI Agent 的核心功能,为用户提供无需邀请码、可本地化部署的智能体解决方案。换句话说,OpenManus 就像…...
2025-03-09 学习记录--C/C++-PTA 习题10-7 十进制转换二进制
合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。💪🏻 一、题目描述 ⭐️ 裁判测试程序样例: #include <stdio.h>void dectobin( int n );int main() {int n;scanf(…...
计算机网络----主要内容简介
这里写目录标题 章节概览每章的大体结构结构功能与服务的关系 一些概念概念一概念二传统的网络层的工作方式(路由IP)现代的网络层的工作方式(SDN) 章节概览 其中,网络层分为了两章 下面的红色部分是部分选修 每章的大…...