User Tools

Site Tools


C++ multi thread web server from scratch

Minimal server.

I used only:

  1. Unix basic syscalls: socket(), bin(), listen(), accept(), read(), write(), close()
  2. C++11 objects: std::thread, std::mutex, std::condition_variable, std::queue, std::functions, std::move

main.cpp

main.cpp
/*
 *
 * Copyright 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;
};

server.hpp

server.hpp
/*
 *
 * Copyright 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

server.cpp

server.cpp
/*
 *
 * Copyright 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

Out

With 2000 concurrent connection:

$ 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)

First PagePrevious PageBack to overviewNext PageLast Page