User Tools

Site Tools


Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cpp:pure-cpp-server [2019-05-28 15:26]
cpp:pure-cpp-server [2020-02-15 00:57] (current)
Line 1: Line 1:
 +=====C++ multi thread web server from scratch=====
 +
 +Minimal server.
 +
 +I used only:
 +  - Unix basic syscalls: socket(), bin(), listen(), accept(), read(), write(), close()
 +  - C++11 objects: std::​thread,​ std::mutex, std::​condition_variable,​ std::queue, std::​functions,​ std::move
 +
 +
 +===main.cpp===
 +
 +  * [[ssl-v]]
 +
 +<code c++ main.cpp>​
 +/*
 + *
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + *
 + */
 +
 +#include <​iostream>​
 +#include <​stdexcept>​
 +
 +#include "​server.hpp"​
 +
 +int main(int argc, char **argv) {
 +
 +    try {
 +        srv5::​server server(1026,​ 2048, 10);
 +        server.team();​
 +        server.run();​
 +
 +    } catch (std::​exception&​ e) {
 +        std::cerr << "#​exeption:​ " << e.what() << ", exit." << std::endl;
 +        return 1;
 +    }
 +
 +    return 0;
 +};
 +
 +</​code>​
 +
 +===server.hpp===
 +
 +<code c++ server.hpp>​
 +/*
 + *
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + *
 + */
 +
 +
 +#ifndef SERVER_HPP
 +#define SERVER_HPP
 +
 +#include <​sys/​socket.h>​
 +#include <​unistd.h>​
 +
 +#include <​thread>​
 +#include <​mutex>​
 +#include <​memory>​
 +
 +namespace srv5 {
 +
 +class server {
 +    private:
 +        struct sockaddr_storage addr;
 +        int socket;
 +        int kqueue;
 +        int backlog;
 +
 +        std::​queue<​int>​ queue;
 +        std::​condition_variable cv;
 +        std::mutex mutex;
 +        std::​vector<​std::​shared_ptr<​std::​thread>>​ pool;
 +        int threads;
 +
 +        std::string timestamp();​
 +        void push(int socket);
 +
 +    public:
 +        server(int port, int backlog, int threads);
 +        ~server();
 +        void team();
 +        void run();
 +};
 +
 +} // namespace srv5
 +
 +#endif
 +
 +</​code>​
 +
 +===server.cpp===
 +
 +<code c++ server.cpp>​
 +/*
 + *
 + * Copyright 2004-2019 Oleg Borodin ​ <​borodin@unix7.org>​
 + *
 + */
 +
 +#include <​sys/​types.h>​
 +#include <​sys/​socket.h>​
 +#include <​arpa/​inet.h>​
 +#include <​netinet/​in.h>​
 +#include <​unistd.h>​
 +#include <​signal.h>​
 +
 +#include <​iostream>​
 +#include <​fstream>​
 +#include <​sstream>​
 +#include <​functional>​
 +#include <​condition_variable>​
 +#include <​queue>​
 +#include <​thread>​
 +#include <​mutex>​
 +#include <​chrono>​
 +#include <​memory>​
 +#include <​stdexcept>​
 +#include <​iomanip>​
 +
 +#include "​server.hpp"​
 +
 +namespace srv5 {
 +
 +server::​server(int port, int backlog, int threads)
 +    : backlog(backlog),​ threads(threads) {
 +
 +    if((socket = ::​socket(PF_INET,​ SOCK_STREAM,​ 0)) < 0) {
 +        throw std::​runtime_error("#​ cannot socket()"​);​
 +    }
 +
 +    int optval = 1;
 +    if (::​setsockopt(socket,​ SOL_SOCKET, SO_REUSEADDR,​ &​optval,​ sizeof(optval)) < 0) {
 +        throw std::​runtime_error("#​ cannot setsockopt()"​);​
 +    }
 +
 +    auto paddr = reinterpret_cast<​struct sockaddr_in*>​(&​addr);​
 +    paddr->​sin_family = AF_INET;
 +    paddr->​sin_addr.s_addr = INADDR_ANY;
 +    paddr->​sin_port = htons(port);​
 +    paddr->​sin_len = sizeof(struct sockaddr_in);​
 +
 +    if (::​bind(socket,​ reinterpret_cast<​struct sockaddr *>​(paddr),​ paddr->​sin_len) < 0) {
 +        throw std::​runtime_error("#​ cannot bind()"​);​
 +    }
 +
 +}
 +void server::​run() {
 +    if (::​listen(socket,​ backlog) < 0) {
 +        throw std::​runtime_error("#​ cannot listen()"​);​
 +    }
 +    int newsocket;
 +    while ((newsocket = ::​accept(socket,​ NULL, 0)) > 0) {
 +        push(std::​move(newsocket));​
 +    }
 +}
 +
 +void server::​push(int socket) {
 +    std::​unique_lock<​std::​mutex>​ lock(mutex);​
 +    queue.push(socket);​
 +    cv.notify_all();​
 +}
 +
 +void server::​team() {
 +    auto worker = [&]{
 +        while(true) {
 +            std::​unique_lock<​std::​mutex>​ lock(mutex);​
 +            cv.wait(lock,​ [&]{ return !queue.empty();​ });
 +            while(!queue.empty()) {
 +                auto socket = queue.front();​
 +                queue.pop();​
 +                cv.notify_one();​
 +
 +                char buffer[512];​
 +                ::​read(socket,​ buffer, 512);
 +
 +                std::​stringstream body;
 +                for (int i = 0;i < 1000; i++) {
 +                    body << "​hello";​
 +                }
 +                std::​stringstream head;
 +                head << "​HTTP/​1.1 200 OK\r\n";​
 +                head << "Date: " << timestamp() << "​\r\n";​
 +                head << "​Server:​ Srv3/​0.1\r\n";​
 +                head << "​Content-Length:​ " << body.str().length() << "​\r\n";​
 +                head << "​Content-Type:​ text/​plain\r\n";​
 +                head << "​Connection:​ close\r\n";​
 +                head << "​\r\n";​
 +
 +                std::string res;
 +                res.append(head.str());​
 +                res.append(body.str());​
 +
 +                ::​write(socket,​ res.c_str(),​ res.length());​
 +                ::​shutdown(socket,​ SHUT_RDWR);
 +                ::​close(socket);​
 +            }
 +        }
 +    };
 +
 +    for (std::​size_t i = 0; i < threads; ++i) {
 +        auto t = new std::​thread(std::​move(worker));​
 +        std::​shared_ptr<​std::​thread>​ thread(std::​move(t));​
 +        pool.push_back(thread);​
 +    }
 +    for (std::​size_t i = 0; i < pool.size();​ ++i) {
 +        pool[i]->​detach();​
 +    }
 +}
 +
 +std::string server::​timestamp() {
 +    std::​stringstream ss;
 +    std::time_t now = std::​time(0);​
 +    ss << std::​put_time(std::​gmtime(&​now),​ "%a, %d %b %Y %T %Z");
 +    return ss.str();
 +}
 +
 +server::​~server() {
 +    ::​close(kqueue);​
 +    ::​close(socket);​
 +}
 +
 +} // namespace srv5
 +
 +</​code>​
 +===Out===
 +
 +With 2000 concurrent connection:
 +
 +<​file>​
 +$ ab -c 2000 -n100000 '​http://​localhost:​1026/'​
 +This is ApacheBench,​ Version 2.3 <​$Revision:​ 1826891 $>
 +Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://​www.zeustech.net/​
 +Licensed to The Apache Software Foundation, http://​www.apache.org/​
 +
 +Benchmarking localhost (be patient)
 +Completed 10000 requests
 +Completed 20000 requests
 +Completed 30000 requests
 +Completed 40000 requests
 +Completed 50000 requests
 +Completed 60000 requests
 +Completed 70000 requests
 +Completed 80000 requests
 +Completed 90000 requests
 +Completed 100000 requests
 +Finished 100000 requests
 +
 +
 +Server Software: ​       Srv3/0.1
 +Server Hostname: ​       localhost
 +Server Port:            1026
 +
 +Document Path:          /
 +Document Length: ​       10000 bytes
 +
 +Concurrency Level: ​     2000
 +Time taken for tests: ​  ​14.059 seconds
 +Complete requests: ​     100000
 +Failed requests: ​       0
 +Total transferred: ​     1014200000 bytes
 +HTML transferred: ​      ​1000000000 bytes
 +Requests per second: ​   7112.90 [#/sec] (mean)
 +Time per request: ​      ​281.179 [ms] (mean)
 +Time per request: ​      0.141 [ms] (mean, across all concurrent requests)
 +Transfer rate:          70448.22 [Kbytes/​sec] received
 +
 +Connection Times (ms)
 +              min  mean[+/-sd] median ​  max
 +Connect: ​       0    1   ​8.0 ​     0      72
 +Processing: ​   46  277  57.9    265     697
 +Waiting: ​       1  277  57.9    265     697
 +Total: ​       116  278  59.6    265     721
 +
 +Percentage of the requests served within a certain time (ms)
 +  50%    265
 +  66%    267
 +  75%    267
 +  80%    268
 +  90%    288
 +  95%    345
 +  98%    533
 +  99%    636
 + ​100% ​   721 (longest request)
 +</​file>​
 +
 +----
 +[<>]
 +