mirror of
https://github.com/changkun/modern-cpp-tutorial.git
synced 2025-12-17 04:34:40 +03:00
add: exercise answer for chapter 6 and 7
This commit is contained in:
38
exercises/6/Makefile
Normal file
38
exercises/6/Makefile
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#
|
||||||
|
# Makefile
|
||||||
|
# web_server
|
||||||
|
#
|
||||||
|
# created by changkun at labex.io
|
||||||
|
#
|
||||||
|
|
||||||
|
CXX = g++
|
||||||
|
EXEC_HTTP = server.http
|
||||||
|
EXEC_HTTPS = server.https
|
||||||
|
|
||||||
|
SOURCE_HTTP = main.http.cpp
|
||||||
|
SOURCE_HTTPS = main.https.cpp
|
||||||
|
|
||||||
|
OBJECTS_HTTP = main.http.o
|
||||||
|
OBJECTS_HTTPS = main.https.o
|
||||||
|
|
||||||
|
LDFLAGS_COMMON = -std=c++11 -O3 -pthread -lboost_system
|
||||||
|
LDFLAGS_HTTP =
|
||||||
|
LDFLAGS_HTTPS = -lssl -lcrypto
|
||||||
|
|
||||||
|
LPATH_COMMON = -I/usr/include/boost
|
||||||
|
LPATH_HTTP =
|
||||||
|
LPATH_HTTPS = -I/usr/include/openssl
|
||||||
|
|
||||||
|
LLIB_COMMON = -L/usr/lib
|
||||||
|
|
||||||
|
all:
|
||||||
|
make http
|
||||||
|
make https
|
||||||
|
|
||||||
|
http:
|
||||||
|
$(CXX) $(SOURCE_HTTP) $(LDFLAGS_COMMON) $(LDFLAGS_HTTP) $(LPATH_COMMON) $(LPATH_HTTP) $(LLIB_COMMON) $(LLIB_HTTP) -o $(EXEC_HTTP)
|
||||||
|
https:
|
||||||
|
$(CXX) $(SOURCE_HTTPS) $(LDFLAGS_COMMON) $(LDFLAGS_HTTPS) $(LPATH_COMMON) $(LPATH_HTTPS) $(LLIB_COMMON) $(LLIB_HTTPS) -o $(EXEC_HTTPS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(EXEC_HTTP) $(EXEC_HTTPS) *.o
|
||||||
96
exercises/6/handler.hpp
Normal file
96
exercises/6/handler.hpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
//
|
||||||
|
// handler.hpp
|
||||||
|
// web_server
|
||||||
|
// created by changkun at changkun.de/modern-cpp
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "server.base.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace LabexWeb;
|
||||||
|
|
||||||
|
template<typename SERVER_TYPE>
|
||||||
|
void start_server(SERVER_TYPE &server) {
|
||||||
|
// resources request
|
||||||
|
|
||||||
|
// processing POST /string, return the string from POST
|
||||||
|
server.resource["^/string/?$"]["POST"] = [](ostream& response, Request& request) {
|
||||||
|
// fetch string from istream (*request.content)
|
||||||
|
stringstream ss;
|
||||||
|
*request.content >> ss.rdbuf(); // read request to stringstream
|
||||||
|
string content=ss.str();
|
||||||
|
|
||||||
|
// return response
|
||||||
|
response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
|
||||||
|
};
|
||||||
|
|
||||||
|
// process GET request from /info, return response
|
||||||
|
server.resource["^/info/?$"]["GET"] = [](ostream& response, Request& request) {
|
||||||
|
stringstream content_stream;
|
||||||
|
content_stream << "<h1>Request:</h1>";
|
||||||
|
content_stream << request.method << " " << request.path << " HTTP/" << request.http_version << "<br>";
|
||||||
|
for(auto& header: request.header) {
|
||||||
|
content_stream << header.first << ": " << header.second << "<br>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the length of content_stream (use content.tellp() to get)
|
||||||
|
content_stream.seekp(0, ios::end);
|
||||||
|
|
||||||
|
response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n" << content_stream.rdbuf();
|
||||||
|
};
|
||||||
|
|
||||||
|
// process GET request for /match/[digit+numbers], e.g. GET request is /match/abc123, will return abc123
|
||||||
|
server.resource["^/match/([0-9a-zA-Z]+)/?$"]["GET"] = [](ostream& response, Request& request) {
|
||||||
|
string number=request.path_match[1];
|
||||||
|
response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// peocess default GET request; anonymous function will be called if no other matches
|
||||||
|
// response files in folder web/
|
||||||
|
// default: index.html
|
||||||
|
server.default_resource["^/?(.*)$"]["GET"] = [](ostream& response, Request& request) {
|
||||||
|
string filename = "www/";
|
||||||
|
|
||||||
|
string path = request.path_match[1];
|
||||||
|
|
||||||
|
// forbidden use `..` access content outside folder web/
|
||||||
|
size_t last_pos = path.rfind(".");
|
||||||
|
size_t current_pos = 0;
|
||||||
|
size_t pos;
|
||||||
|
while((pos=path.find('.', current_pos)) != string::npos && pos != last_pos) {
|
||||||
|
current_pos = pos;
|
||||||
|
path.erase(pos, 1);
|
||||||
|
last_pos--;
|
||||||
|
}
|
||||||
|
|
||||||
|
filename += path;
|
||||||
|
ifstream ifs;
|
||||||
|
// folder inspection across platform
|
||||||
|
if(filename.find('.') == string::npos) {
|
||||||
|
if(filename[filename.length()-1]!='/')
|
||||||
|
filename+='/';
|
||||||
|
filename += "index.html";
|
||||||
|
}
|
||||||
|
ifs.open(filename, ifstream::in);
|
||||||
|
|
||||||
|
if(ifs) {
|
||||||
|
ifs.seekg(0, ios::end);
|
||||||
|
size_t length=ifs.tellg();
|
||||||
|
|
||||||
|
ifs.seekg(0, ios::beg);
|
||||||
|
|
||||||
|
// copy file to response-stream , shouldn't use for large files
|
||||||
|
response << "HTTP/1.1 200 OK\r\nContent-Length: " << length << "\r\n\r\n" << ifs.rdbuf();
|
||||||
|
|
||||||
|
ifs.close();
|
||||||
|
} else {
|
||||||
|
// return unable to open if file doesn't exists
|
||||||
|
string content="Could not open file "+filename;
|
||||||
|
response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// start HTTP(S) server
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
19
exercises/6/main.http.cpp
Normal file
19
exercises/6/main.http.cpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// main_http.cpp
|
||||||
|
// web_server
|
||||||
|
// created by changkun at changkun.de/modern-cpp
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include "server.http.hpp"
|
||||||
|
#include "handler.hpp"
|
||||||
|
|
||||||
|
using namespace LabexWeb;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// HTTP server runs in port 12345 HTTP, enable 4 threads
|
||||||
|
Server<HTTP> server(12345, 4);
|
||||||
|
std::cout << "Server starting at port: 12345" << std::endl;
|
||||||
|
start_server<Server<HTTP>>(server);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
18
exercises/6/main.https.cpp
Normal file
18
exercises/6/main.https.cpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// main_https.cpp
|
||||||
|
// web_server
|
||||||
|
// created by changkun at changkun.de/modern-cpp
|
||||||
|
//
|
||||||
|
#include <iostream>
|
||||||
|
#include "server.https.hpp"
|
||||||
|
#include "handler.hpp"
|
||||||
|
using namespace LabexWeb;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// HTTPS server runs in port 12345, enable 4 threads
|
||||||
|
// Use certificates for security
|
||||||
|
Server<HTTPS> server(12345, 4, "server.crt", "server.key");
|
||||||
|
std::cout << "Server starting at port: 12345" << std::endl;
|
||||||
|
start_server<Server<HTTPS>>(server);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
197
exercises/6/server.base.hpp
Normal file
197
exercises/6/server.base.hpp
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
//
|
||||||
|
// server_base.hpp
|
||||||
|
// web_server
|
||||||
|
// created by changkun at changkun.de/modern-cpp
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SERVER_BASE_HPP
|
||||||
|
#define SERVER_BASE_HPP
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace LabexWeb {
|
||||||
|
struct Request {
|
||||||
|
// request method, POST, GET; path; HTTP version
|
||||||
|
std::string method, path, http_version;
|
||||||
|
// use smart pointer for reference counting of content
|
||||||
|
std::shared_ptr<std::istream> content;
|
||||||
|
// hash container, key-value dict
|
||||||
|
std::unordered_map<std::string, std::string> header;
|
||||||
|
// use regular expression for path match
|
||||||
|
std::smatch path_match;
|
||||||
|
};
|
||||||
|
|
||||||
|
// use typedef simplify resource type
|
||||||
|
typedef std::map<std::string, std::unordered_map<std::string,
|
||||||
|
std::function<void(std::ostream&, Request&)>>> resource_type;
|
||||||
|
|
||||||
|
// socket_type is HTTP or HTTPS
|
||||||
|
template <typename socket_type>
|
||||||
|
class ServerBase {
|
||||||
|
public:
|
||||||
|
resource_type resource;
|
||||||
|
resource_type default_resource;
|
||||||
|
|
||||||
|
// construct server, initalize port, default: 1 thread
|
||||||
|
ServerBase(unsigned short port, size_t num_threads = 1) :
|
||||||
|
endpoint(boost::asio::ip::tcp::v4(), port),
|
||||||
|
acceptor(m_io_service, endpoint),
|
||||||
|
num_threads(num_threads) {}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
// default resource in the end of vector, as response method
|
||||||
|
for(auto it = resource.begin(); it != resource.end(); it++) {
|
||||||
|
all_resources.push_back(it);
|
||||||
|
}
|
||||||
|
for(auto it = default_resource.begin(); it != default_resource.end(); it++) {
|
||||||
|
all_resources.push_back(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// socket connection
|
||||||
|
accept();
|
||||||
|
|
||||||
|
// if num_threads>1, then m_io_service.run()
|
||||||
|
// it will start (num_threads-1) threads as thread pool
|
||||||
|
for(size_t c = 1;c < num_threads; c++) {
|
||||||
|
threads.emplace_back([this](){
|
||||||
|
m_io_service.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// main thread
|
||||||
|
m_io_service.run();
|
||||||
|
|
||||||
|
// wait for other threads finish
|
||||||
|
for(auto& t: threads)
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
// io_service is a dispatcher in asio library, all asynchronous io events are dispatched by it
|
||||||
|
// in another word, constructor of IO object need a io_service object as parameter
|
||||||
|
boost::asio::io_service m_io_service;
|
||||||
|
// IP address, port and protocol version as a endpoint, and generated on serverside
|
||||||
|
// tcp::acceptor object, wait for connection
|
||||||
|
boost::asio::ip::tcp::endpoint endpoint;
|
||||||
|
// thus, a acceptor object requires io_service and endpoint as parameters
|
||||||
|
boost::asio::ip::tcp::acceptor acceptor;
|
||||||
|
|
||||||
|
// server side threads
|
||||||
|
size_t num_threads;
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
|
// all resource will be append to the end of vector, and created in start()
|
||||||
|
std::vector<resource_type::iterator> all_resources;
|
||||||
|
|
||||||
|
// requires to implement this method for different type of server
|
||||||
|
virtual void accept() {}
|
||||||
|
|
||||||
|
void process_request_and_respond(std::shared_ptr<socket_type> socket) const {
|
||||||
|
// created cache for async_read_untile()
|
||||||
|
// shared_ptr will use for passing object to anonymous function
|
||||||
|
// the type will be deduce as std::shared_ptr<boost::asio::streambuf>
|
||||||
|
auto read_buffer = std::make_shared<boost::asio::streambuf>();
|
||||||
|
|
||||||
|
boost::asio::async_read_until(*socket, *read_buffer, "\r\n\r\n",
|
||||||
|
[this, socket, read_buffer](const boost::system::error_code& ec, size_t bytes_transferred) {
|
||||||
|
if(!ec) {
|
||||||
|
// Note: read_buffer->size() always equal to bytes_transferred, the document of Boost indicates:
|
||||||
|
// after async_read_until operation, streambuf contains some extra data out of delimiter
|
||||||
|
// thus, the best way is to read and parse the content from the left of read_buffer, then append the content of async_read
|
||||||
|
size_t total = read_buffer->size();
|
||||||
|
|
||||||
|
// convert istream to string-lines
|
||||||
|
std::istream stream(read_buffer.get());
|
||||||
|
|
||||||
|
// deduce the type of std::shared_ptr<Request>
|
||||||
|
auto request = std::make_shared<Request>();
|
||||||
|
*request = parse_request(stream);
|
||||||
|
|
||||||
|
size_t num_additional_bytes = total-bytes_transferred;
|
||||||
|
|
||||||
|
// if satisfy then also read
|
||||||
|
if(request->header.count("Content-Length")>0) {
|
||||||
|
boost::asio::async_read(*socket, *read_buffer,
|
||||||
|
boost::asio::transfer_exactly(stoull(request->header["Content-Length"]) - num_additional_bytes),
|
||||||
|
[this, socket, read_buffer, request](const boost::system::error_code& ec, size_t bytes_transferred) {
|
||||||
|
if(!ec) {
|
||||||
|
// pointer as istream object stored in read_buffer
|
||||||
|
request->content = std::shared_ptr<std::istream>(new std::istream(read_buffer.get()));
|
||||||
|
respond(socket, request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
respond(socket, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Request parse_request(std::istream& stream) const {
|
||||||
|
Request request;
|
||||||
|
|
||||||
|
std::regex e("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
|
||||||
|
|
||||||
|
std::smatch sub_match;
|
||||||
|
|
||||||
|
// read method, path and http version from the frist line
|
||||||
|
std::string line;
|
||||||
|
getline(stream, line);
|
||||||
|
line.pop_back();
|
||||||
|
if(std::regex_match(line, sub_match, e)) {
|
||||||
|
request.method = sub_match[1];
|
||||||
|
request.path = sub_match[2];
|
||||||
|
request.http_version = sub_match[3];
|
||||||
|
|
||||||
|
bool matched;
|
||||||
|
e="^([^:]*): ?(.*)$";
|
||||||
|
// parse head information
|
||||||
|
do {
|
||||||
|
getline(stream, line);
|
||||||
|
line.pop_back();
|
||||||
|
matched=std::regex_match(line, sub_match, e);
|
||||||
|
if(matched) {
|
||||||
|
request.header[sub_match[1]] = sub_match[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
} while(matched==true);
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
void respond(std::shared_ptr<socket_type> socket, std::shared_ptr<Request> request) const {
|
||||||
|
// response after search requested path and method
|
||||||
|
for(auto res_it: all_resources) {
|
||||||
|
std::regex e(res_it->first);
|
||||||
|
std::smatch sm_res;
|
||||||
|
if(std::regex_match(request->path, sm_res, e)) {
|
||||||
|
if(res_it->second.count(request->method)>0) {
|
||||||
|
request->path_match = move(sm_res);
|
||||||
|
|
||||||
|
// will be deduce to std::shared_ptr<boost::asio::streambuf>
|
||||||
|
auto write_buffer = std::make_shared<boost::asio::streambuf>();
|
||||||
|
std::ostream response(write_buffer.get());
|
||||||
|
res_it->second[request->method](response, *request);
|
||||||
|
|
||||||
|
// capture write_buffer in lambda, make sure it can be destroyed after async_write
|
||||||
|
boost::asio::async_write(*socket, *write_buffer,
|
||||||
|
[this, socket, request, write_buffer](const boost::system::error_code& ec, size_t bytes_transferred) {
|
||||||
|
// HTTP 1.1 connection
|
||||||
|
if(!ec && stof(request->http_version)>1.05)
|
||||||
|
process_request_and_respond(socket);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename socket_type>
|
||||||
|
class Server : public ServerBase<socket_type> {};
|
||||||
|
}
|
||||||
|
#endif /* SERVER_BASE_HPP */
|
||||||
38
exercises/6/server.http.hpp
Normal file
38
exercises/6/server.http.hpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// server_http.hpp
|
||||||
|
// web_server
|
||||||
|
// created by changkun at changkun.de/modern-cpp
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SERVER_HTTP_HPP
|
||||||
|
#define SERVER_HTTP_HPP
|
||||||
|
|
||||||
|
#include "server.base.hpp"
|
||||||
|
|
||||||
|
namespace LabexWeb {
|
||||||
|
typedef boost::asio::ip::tcp::socket HTTP;
|
||||||
|
template<>
|
||||||
|
class Server<HTTP> : public ServerBase<HTTP> {
|
||||||
|
public:
|
||||||
|
// use port, thread number to construct web server
|
||||||
|
// http server is much simple than https since it doesn't need to initial config file
|
||||||
|
Server(unsigned short port, size_t num_threads=1) :
|
||||||
|
ServerBase<HTTP>::ServerBase(port, num_threads) {};
|
||||||
|
private:
|
||||||
|
// implement accept() method
|
||||||
|
void accept() {
|
||||||
|
// create a new socket for current connection
|
||||||
|
// shared_ptr is used for passing temporal object to anonymous function
|
||||||
|
// socket will be deduce as type of std::shared_ptr<HTTP>
|
||||||
|
auto socket = std::make_shared<HTTP>(m_io_service);
|
||||||
|
|
||||||
|
acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec) {
|
||||||
|
// establish a connection
|
||||||
|
accept();
|
||||||
|
// if no error
|
||||||
|
if(!ec) process_request_and_respond(socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif /* SERVER_HTTP_HPP */
|
||||||
65
exercises/6/server.https.hpp
Normal file
65
exercises/6/server.https.hpp
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// server_https.hpp
|
||||||
|
// web_server
|
||||||
|
// created by changkun at changkun.de/modern-cpp
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SERVER_HTTPS_HPP
|
||||||
|
#define SERVER_HTTPS_HPP
|
||||||
|
|
||||||
|
#include "server.http.hpp"
|
||||||
|
#include <boost/asio/ssl.hpp>
|
||||||
|
|
||||||
|
namespace LabexWeb {
|
||||||
|
|
||||||
|
// define HTTPS type
|
||||||
|
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;
|
||||||
|
|
||||||
|
// define HTTPS service, template type is HTTPS
|
||||||
|
template<>
|
||||||
|
class Server<HTTPS> : public ServerBase<HTTPS> {
|
||||||
|
public:
|
||||||
|
// a HTTPS server requires two more parameters: certificate file and private key file
|
||||||
|
Server(unsigned short port, size_t num_threads,
|
||||||
|
const std::string& cert_file, const std::string& private_key_file) :
|
||||||
|
ServerBase<HTTPS>::ServerBase(port, num_threads),
|
||||||
|
context(boost::asio::ssl::context::sslv23) {
|
||||||
|
// use certificate file
|
||||||
|
context.use_certificate_chain_file(cert_file);
|
||||||
|
// use private key file, we need pass a new parameter to specify the format
|
||||||
|
context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// compare to HTTP server, we must define ssl context object
|
||||||
|
boost::asio::ssl::context context;
|
||||||
|
|
||||||
|
// the difference between HTTPS and HTTP server
|
||||||
|
// is the construct difference of socket object
|
||||||
|
// HTTPS will encrypt the IO stream socket
|
||||||
|
// thus, accept() method must initialize ssl context
|
||||||
|
void accept() {
|
||||||
|
// create a new socket for current connection
|
||||||
|
// shared_ptr is used for passing temporal object to anonymous function
|
||||||
|
// socket will be deduce as std::shared_ptr<HTTPS>
|
||||||
|
auto socket = std::make_shared<HTTPS>(m_io_service, context);
|
||||||
|
|
||||||
|
acceptor.async_accept(
|
||||||
|
(*socket).lowest_layer(),
|
||||||
|
[this, socket](const boost::system::error_code& ec) {
|
||||||
|
// accept a new connection
|
||||||
|
accept();
|
||||||
|
|
||||||
|
// if no error
|
||||||
|
if(!ec) {
|
||||||
|
(*socket).async_handshake(boost::asio::ssl::stream_base::server,
|
||||||
|
[this, socket](const boost::system::error_code& ec) {
|
||||||
|
if(!ec) process_request_and_respond(socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* SERVER_HTTPS_HPP */
|
||||||
8
exercises/6/www/index.html
Normal file
8
exercises/6/www/index.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>LabEx Web Server Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Hello world in index.html.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
exercises/6/www/test.html
Normal file
8
exercises/6/www/test.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>LabEx Web Server Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Hello world in test.html.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
141
exercises/7/ThreadPool.hpp
Normal file
141
exercises/7/ThreadPool.hpp
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#ifndef THREAD_POOL_H
|
||||||
|
#define THREAD_POOL_H
|
||||||
|
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <queue> // std::queue
|
||||||
|
#include <memory> // std::make_shared
|
||||||
|
|
||||||
|
#include <thread> // std::thread
|
||||||
|
#include <mutex> // std::mutex, std::unique_lock
|
||||||
|
#include <condition_variable> // std::condition_variable
|
||||||
|
#include <future> // std::future, std::packaged_task
|
||||||
|
|
||||||
|
#include <functional> // std::function, std::bind
|
||||||
|
#include <stdexcept> // std::runtime_error
|
||||||
|
#include <utility> // std::move, std::forward
|
||||||
|
|
||||||
|
class ThreadPool {
|
||||||
|
public:
|
||||||
|
|
||||||
|
// initialize the number of concurrency threads
|
||||||
|
ThreadPool(size_t);
|
||||||
|
|
||||||
|
// enqueue new thread task
|
||||||
|
template<class F, class... Args>
|
||||||
|
auto enqueue(F&& f, Args&&... args)
|
||||||
|
-> std::future<typename std::result_of<F(Args...)>::type>;
|
||||||
|
|
||||||
|
// destroy thread pool and all created threads
|
||||||
|
~ThreadPool();
|
||||||
|
private:
|
||||||
|
|
||||||
|
// thread list, stores all threads
|
||||||
|
std::vector< std::thread > workers;
|
||||||
|
// queue task, the type of queue elements are functions with void return type
|
||||||
|
std::queue< std::function<void()> > tasks;
|
||||||
|
|
||||||
|
// for synchonization
|
||||||
|
std::mutex queue_mutex;
|
||||||
|
// std::condition_variable is a new feature from c++11,
|
||||||
|
// it's a synchronization primitives. it can be used
|
||||||
|
// to block a thread or threads at the same time until
|
||||||
|
// all of them modified condition_variable.
|
||||||
|
std::condition_variable condition;
|
||||||
|
bool stop;
|
||||||
|
};
|
||||||
|
|
||||||
|
// constructor initialize a fixed size of worker
|
||||||
|
inline ThreadPool::ThreadPool(size_t threads): stop(false)
|
||||||
|
{
|
||||||
|
// initialize worker
|
||||||
|
for(size_t i = 0;i<threads;++i)
|
||||||
|
// std::vector::emplace_back :
|
||||||
|
// append to the end of vector container
|
||||||
|
// this element will be constructed at the end of container, without copy and move behavior
|
||||||
|
workers.emplace_back(
|
||||||
|
// the lambda express capture this, i.e. the instance of thread pool
|
||||||
|
[this]
|
||||||
|
{
|
||||||
|
// avoid fake awake
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
|
||||||
|
// define function task container, return type is void
|
||||||
|
std::function<void()> task;
|
||||||
|
|
||||||
|
// critical section
|
||||||
|
{
|
||||||
|
// get mutex
|
||||||
|
std::unique_lock<std::mutex> lock(this->queue_mutex);
|
||||||
|
|
||||||
|
// block current thread
|
||||||
|
this->condition.wait(lock,
|
||||||
|
[this]{ return this->stop || !this->tasks.empty(); });
|
||||||
|
|
||||||
|
// return if queue empty and task finished
|
||||||
|
if(this->stop && this->tasks.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// otherwise execute the first element of queue
|
||||||
|
task = std::move(this->tasks.front());
|
||||||
|
this->tasks.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// execution
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue a new thread
|
||||||
|
// use variadic templates and tail return type
|
||||||
|
template<class F, class... Args>
|
||||||
|
auto ThreadPool::enqueue(F&& f, Args&&... args)
|
||||||
|
-> std::future<typename std::result_of<F(Args...)>::type>
|
||||||
|
{
|
||||||
|
// deduce return type
|
||||||
|
using return_type = typename std::result_of<F(Args...)>::type;
|
||||||
|
|
||||||
|
// fetch task
|
||||||
|
auto task = std::make_shared< std::packaged_task<return_type()> >(
|
||||||
|
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
|
||||||
|
);
|
||||||
|
|
||||||
|
std::future<return_type> res = task->get_future();
|
||||||
|
|
||||||
|
// critical section
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||||
|
|
||||||
|
// avoid add new thread if theadpool is destroied
|
||||||
|
if(stop)
|
||||||
|
throw std::runtime_error("enqueue on stopped ThreadPool");
|
||||||
|
|
||||||
|
// add thread to queue
|
||||||
|
tasks.emplace([task]{ (*task)(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify a wait thread
|
||||||
|
condition.notify_one();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// destroy everything
|
||||||
|
inline ThreadPool::~ThreadPool()
|
||||||
|
{
|
||||||
|
// critical section
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wake up all threads
|
||||||
|
condition.notify_all();
|
||||||
|
|
||||||
|
// let all processes into synchronous execution, use c++11 new for-loop: for(value:values)
|
||||||
|
for(std::thread &worker: workers)
|
||||||
|
worker.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
40
exercises/7/main.cpp
Normal file
40
exercises/7/main.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include <iostream> // std::cout, std::endl
|
||||||
|
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <future> // std::future
|
||||||
|
#include <thread> // std::this_thread::sleep_for
|
||||||
|
#include <chrono> // std::chrono::seconds
|
||||||
|
|
||||||
|
#include "ThreadPool.hpp"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// create a thread pool with max. 4 concurrency threads
|
||||||
|
ThreadPool pool(4);
|
||||||
|
// create execution results list
|
||||||
|
std::vector< std::future<std::string> > results;
|
||||||
|
|
||||||
|
// start eight thread task
|
||||||
|
for(int i = 0; i < 8; ++i) {
|
||||||
|
// add all task to result list
|
||||||
|
results.emplace_back(
|
||||||
|
// ass print task to thread pool
|
||||||
|
pool.enqueue([i] {
|
||||||
|
std::cout << "hello " << i << std::endl;
|
||||||
|
// wait a sec when the previous line is out
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
// keep output and return the status of execution
|
||||||
|
std::cout << "world " << i << std::endl;
|
||||||
|
return std::string("---thread ") + std::to_string(i) + std::string(" finished.---");
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
for(auto && result: results)
|
||||||
|
std::cout << result.get() << ' ';
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user