Multi-threaded web server based on blocking queue producer consumer model thread pool
Code address: WebServer_GitHub_Addr
README
Summary
This experiment implements a multi-threaded Web server based on blocking queue thread pool through C++ language. The server supports sending messages through the http protocol to grab specific resources on the server across hosts. At the same time, the background of the web server uses C ++ language to call
pthread.h
through the native system thread, and realizes a thread pool based on blocking queue, the thread pool Support manually setting the number of threads in the thread pool. At the same time, the thread pool maintains the task queue. Every time the web server gets a request, the background server will load the specific task corresponding to the specific request into the thread pool and wait for the thread in the thread pool to call. Since in this project, each remote fetch is a short link, so theoretically, the web server can receive countless requests.According to the requirements of the experiment, this web server can accept and parse HTTP requests, then read the files requested by HTTP from the file system of the server, and send correct response messages to the client according to whether the files exist.
In the server terminal, the server can print log information according to different levels, and print message information when receiving a request.
Execution effect
Define the ./wwwroot
directory under the code as the root directory of the server resources, and define ./wwwroot/index.html
as the server home page.
- When the client requests that the resource exists, the server will return the corresponding resource.
- When the client request path is the
/
root directory, the server returns the homepage by default. - When the client application path does not exist, the server returns
./wwwroot/error/404.html
as the returned resource.
Server power on
Browser remote connection
The server is a remote server of Tencent Cloud, and the browser is a local browser. This is a cross-host test across a LAN.
The client requests resources that do not exist
When the client sends a request, the message obtained by the background server
Multiple clients initiate requests
The server theoretically supports an infinite number of short links to request
Environment preparation
System: Linux version 3.10.0-1160.11.1.el7.x86_64
Compiler version: gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
Resource file/source file preparation directory structure
- The
MyThreadPool
directory maintains the handwritten thread pool source code - The
./wwwroot/
directory maintains server resources, where./wwwroot/
is the root directory accessed by the client - Other files are HttpServer source code
RUN
Service default port number: 8080
tips: If the binary program HttpServer does not have executable permission during operation chmod 755 HttpServer
Generate executable:make clean;make
Start the server:./HttpServer 8080
Input in the browser:ip:port
to visit the server home page
Or enter:ip:port/index.html
You can also visit the server home page
Enter a path that does not exist: 43:xxx:xxx:xxx:8080/a.txt
Detailed Code
Web server implementation architecture
The first layer of encapsulation: encapsulating the basic operations of the socket Sock.hpp
/* The specific interface implementation can be found in the source code */ /* Sock. hpp */ class Sock {<!-- --> private: const static int gbacklog = 20; public: Sock() {<!-- -->} ~Sock() {<!-- -->} /* The internal calls of the following interfaces are the underlying calls of the socket */ int Socket(); // create and get socket -- return value: listening socket void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0"); // bind void Listen(int sock); // set listening status int Accept(int listensock, std::string *ip, uint16_t *port); // accept connection -- return value: service socket bool Connect(int sock, const std::string & amp; server_ip, const uint16_t & amp; server_port); };
Second layer encapsulation: encapsulation of Http server HttpServer.hpp
- By maintaining the
Task
class, the actions required by the thread can be encapsulated. Through theoperator()()
overload of theTask
class, the thread can directly execute the internally bound method. - By maintaining the
HttpServer
class, after initializing and executingHttpServer::Start()
, the thread pool is started, and after the Accept is successful, the corresponding task is loaded into the thread pool. Wait for a call from a thread in the thread pool.
#ifndef __Yufc_HttpServer #define __Yufc_HttpServer #include "Sock.hpp" #include <functional> #include "MyThreadPool/threadPool.hpp" using func_t = std::function<void(int)>; /* task class */ struct Task {<!-- --> public: func_t func__; int sock__; public: Task() {<!-- -->} Task(func_t func, int sock) : func__(func), sock__(sock) {<!-- -->} void operator()() {<!-- --> func__(sock__); } }; /* http server class */ class HttpServer {<!-- --> private: int __listen_sock; uint16_t __port; Sock __sock; func_t __func; yufc_thread_pool::ThreadPool<Task> *__thread_pool = yufc_thread_pool::ThreadPool<Task>::getThreadPool(); public: HttpServer(const uint16_t & port, func_t func) : __port(port), __func(func); ~HttpServer(); void Start(); }; #endif
Http server startup HttpServer.cc
After executing HttpServer.cc
, main()
creates a server object pointer, and calls Start()
to start the server.
void HandlerHttpRequest(int sockfd);
The function maintains the tasks to be performed by each thread, including the following.
- read Http request
- Parse Http message
- create an http response
- send response to client
/* Generally http must have its own web root directory */ #define ROOT "./wwwroot" /* If the client only requests one /, it will generally return to the default home page */ #define HOME_PAGE "index.html" void HandlerHttpRequest(int sockfd); // See the source code for specific implementation int main(int argc, char **argv) {<!-- --> if (argc != 2) {<!-- --> Usage(argv[0]); exit(0); } std::unique_ptr<HttpServer> httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest)); httpserver->Start(); return 0; }
ulity.hpp
Provide Util
class, which provides static void cutString(const std::string & amp;s, const std::string & amp;sep, std::vector
interface. When parsing the http message, you can cut the incoming string s
according to the interval of sep
, and put the result in out
inside.
Usage.hpp
Provide Usage
function as the manual of HttpServer.
Log.hpp
Provide void logMessage(int level, const char *format, ...)
function, responsible for printing server log information.
Log levels are:
DEBUG debugging
NORMAL Normal operation
WARNING warning – process continues to run
ERROR non-fatal error – process continues
FATAL fatal error – process terminated
Thread pool implementation architecture
thread.hpp
- Simple encapsulation of native threads
typedef void*(*func_t_)(void*); // function pointer class ThreadData {<!-- --> public: void* __args; std::string __name; }; class Thread {<!-- --> private: std::string __name; // thread name pthread_t __tid; // thread tid func_t_ __func; // function to be called by the thread ThreadData __tdata; // thread data public: Thread(int num, func_t_ callback, void* args); // num-custom thread number callback-task to be executed by the thread args-callback parameter ~Thread(); void start(); void join(); std::string name(); // return thread name };
lockGuard.hpp
- Encapsulate mutex with RAII lock encapsulation style
//Encapsulate a lock class Mutex {<!-- --> private: pthread_mutex_t *__pmtx; public: Mutex(pthread_mutex_t *mtx) :__pmtx(mtx){<!-- -->} ~Mutex() {<!-- -->} public: void lock() // lock {<!-- --> pthread_mutex_lock(__pmtx); } void unlock() // unlock {<!-- --> pthread_mutex_unlock(__pmtx); } }; class lockGuard {<!-- --> public: lockGuard(pthread_mutex_t *mtx) :__mtx(mtx) {<!-- --> __mtx. lock(); } ~lockGuard() {<!-- --> __mtx. unlock(); } private: Mutex __mtx; };
threadPool.hpp
Overall structure of thread pool
#define _DEBUG_MODE_ false const int g_thread_num = 3; // default 3 threads /* The specific implementation can be seen in the source code */ namespace yufc_thread_pool {<!-- --> template <class T> class ThreadPool {<!-- --> private: std::vector<Thread *> __threads; // threads int __num; // number of threads std::queue<T> __task_queue; // task queue pthread_mutex_t __lock; // mutex pthread_cond_t __cond; // condition variable static ThreadPool<T> *thread_ptr; // singleton mode static pthread_mutex_t __mutexForPool; // protect getThreadPool on mutex private: //The structure is private -- let the thread pool become a singleton mode ThreadPool(int thread_num = g_thread_num); ThreadPool(const ThreadPool<T> & amp; other) = delete; const ThreadPool<T> & amp; operator=(const ThreadPool<T> & amp; other) = delete; public: ~ThreadPool(); public: static ThreadPool<T>* getThreadPool(int num = g_thread_num); // Lazy mode--get thread pool void run(); // start thread pool void pushTask(const T & amp;task); // Add tasks to the thread pool static void *routine(void *args); // what the thread has to do public: // A batch is required, and the interface for external members to access internal properties is provided to the static routine, otherwise the routine cannot be locked // The following interfaces are not locked, because we believe that when these functions are called, they are called in a safe context //Because these functions have been locked before calling, after calling, lockGuard will automatically unlock pthread_mutex_t *getMutex(); void waitCond(); // wait for the condition variable to be ready bool isEmpty(); // Determine whether the task queue is empty T getTask(); // get a task }; template <typename T> ThreadPool<T> *ThreadPool<T>::thread_ptr = nullptr; template <typename T> pthread_mutex_t ThreadPool<T>::__mutexForPool = PTHREAD_MUTEX_INITIALIZER; //static/global can be initialized like this, this lock is used to protect getThreadPool }
Some specific details of the implementation
Http message analysis
Because all we receive are http requests, we can obtain client information by parsing the http request message.
The following is a complete http message
GET /index.html HTTP/1.1 Host: 43.xxx.xxx.xxx:8080 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Accept: text/html,application/xhtml + xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3; q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9
Because we need to extract the resources that the customer needs to access, we need to extract the resource path entered by the customer.
The second field in the first line of the http message indicates the path where the client needs to extract resources.
At the same time, the interval between each line of the http message is a special string \r\
.
Therefore, in the HandlerHttpRequest
function in HttpServer.cc
, we first separate each line in the http message, and extract the second string of the first line Come out, you can get the path of the resource.
Thread pool in singleton mode
In this experiment, we hope that a process can only generate one thread pool, so we set the thread pool to singleton mode. This is the singleton pattern in a lazy way.
**Lazy:** Construct the object the first time it is needed.
**Hungry man:**Construct the object before main()
is executed.
-
Make the constructor private
-
Get the thread pool by
static ThreadPool
* getThreadPool(int num = g_thread_num) In this interface, if the execution flow is the first execution of the function, the function will construct a thread pool object and return its pointer. If the execution flow is not the first time the function is executed, the function will return
this
, which is itself.
In addition, in order to ensure thread safety when the thread pool is running, mutex locks are added to multiple operations in the thread pool to protect the operations.
Experimental results analysis and thinking
Improve section:
- Implement multiple execution streams and support multiple clients to connect.
- Encapsulate the thread pool so that the server can respond to http requests in parallel
Deficiencies:
- The message returned to the client is a static web page, and the function of supporting mutual interaction between multiple clients has not been implemented yet
- The response message is relatively rough and can be further beautified.
- The server can be further optimized, and the server process can be set as a daemon process, allowing it to execute and provide services for a long time.
- No simulated packet loss