/* * Author, Copyright: Oleg Borodin <onborodin@gmail.com> */ #include <iostream> #include <vector> #include <queue> #include <chrono> #include <mutex> #include <condition_variable> #include <thread> #include <memory> #include <stdexcept> #include <iomanip> #include <csignal> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> namespace srv { class stream { private: int sock; public: stream(int sock) : sock(sock) { } std::shared_ptr<std::string> read(const int size) { auto str = std::make_shared<std::string>(size, 0); ssize_t rv = ::read(sock, (void*)str->c_str(), size); if (rv < size) { str->resize(rv + 1); } return str; } int write(std::string str) { return ::write(sock, str.c_str(), str.length()); } void close() { ::shutdown(sock, SHUT_RDWR); ::close(sock); } }; class listener { private: int sock; public: listener(int port, int backlog) { if((sock = ::socket(PF_INET, SOCK_STREAM, 0)) < 0) { throw std::runtime_error("cannot create socket"); } int optval; optval = 1; if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { throw std::runtime_error("cannot set socket option"); } struct timeval tv; tv.tv_sec = 10; tv.tv_usec = 0; if (::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)) < 0) { throw std::runtime_error("cannot set socket option"); } struct sockaddr addr; struct sockaddr_in* paddr = (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(sockaddr_in); if (::bind(sock, (struct sockaddr*)paddr, paddr->sin_len) < 0) { throw std::runtime_error("cannot bind socket"); } if (::listen(sock, backlog) < 0) { throw std::runtime_error("cannot listen socket"); } } std::shared_ptr<srv::stream> accept() { int newsock; if ((newsock = ::accept(sock, NULL, 0)) != -1) { return std::make_shared<srv::stream>(newsock); } return nullptr; } }; class queue { private: std::mutex mutex; std::queue<std::shared_ptr<srv::stream>> squeue; public: void push(std::shared_ptr<srv::stream> item) { std::unique_lock<std::mutex> lock(mutex); squeue.push(item); } std::shared_ptr<srv::stream> front() { std::unique_lock<std::mutex> lock(mutex); if (squeue.size() > 0) { auto result = squeue.front(); squeue.pop(); lock.unlock(); return result; } return nullptr; } int size() { std::unique_lock<std::mutex> lock(mutex); return squeue.size(); } bool empty() { std::unique_lock<std::mutex> lock(mutex); return squeue.empty(); } }; class worker { private: std::shared_ptr<srv::queue> queue; std::condition_variable cond; std::mutex mutex; public: worker(std::shared_ptr<srv::queue> q) : queue(q) { std::thread thread(&srv::worker::handler, this); thread.detach(); } void handler() { while (true) { std::unique_lock<std::mutex> lock(mutex); cond.wait(lock, [this]{ return !queue->empty(); }); auto stream = queue->front(); if (stream == nullptr) continue; auto req = stream->read(1024); if (req->length() > 0) { std::string content = "<html>\r\n" "<head><title>Hello</title></head>\r\n" "<body>\r\n" "<center><h1>Hello, World!</h1></center>\r\n" "<hr><center>Srv11/0.1</center>\r\n" "</body>\r\n" "</html>\r\n"; std::string header = "HTTP/1.1 200 OK\r\n" "Accept-Ranges: bytes\r\n" "Content-Type: text/html\r\n" "Content-Length: 142\r\n" "Conection: close\r\n" "Server: Srv11/0.1\r\n" "Date: Wed, 16 May 2019 20:05:07 UTC\r\n" "\r\n"; stream->write(header); stream->write(content); } stream->close(); lock.unlock(); } }; void notify() { std::unique_lock<std::mutex> lock(mutex); cond.notify_one(); } }; class pool { private: std::shared_ptr<srv::queue> queue; std::vector<std::shared_ptr<srv::worker>> workers; public: pool(int size) { queue = std::make_shared<srv::queue>(); for (int i = 0; i < size; i++) { auto w = std::make_shared<srv::worker>(queue); workers.push_back(w); } } void enqueu(std::shared_ptr<srv::stream> item) { queue->push(item); for (auto& w: workers) { w->notify(); } } }; class server { private: srv::listener& listener; srv::pool& pool; public: server(srv::listener& a, srv::pool& p) : listener(a), pool(p) {} void run() { while (true) { std::shared_ptr<srv::stream> s; if((s = listener.accept()) != nullptr) { pool.enqueu(std::move(s)); } } } }; } // namespace srv int main(int argc, char **argv) { try { std::signal(SIGPIPE, SIG_IGN); srv::listener listener(1024, 1024*4); srv::pool pool(30); srv::server server(listener, pool); server.run(); } catch (std::exception& e) { std::cerr << "#exception: " << e.what() << ", exit." << std::endl; return 1; } return 0; }
$ ab -s 20 -c 2000 -n100000 'http://127.0.0.1:1024/'
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 127.0.0.1 (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: Srv11/0.1
Server Hostname: 127.0.0.1
Server Port: 1024
Document Path: /
Document Length: 142 bytes
Concurrency Level: 2000
Time taken for tests: 11.675 seconds
Complete requests: 100000
Failed requests: 0
Total transferred: 30300000 bytes
HTML transferred: 14200000 bytes
Requests per second: 8565.30 [#/sec] (mean)
Time per request: 233.500 [ms] (mean)
Time per request: 0.117 [ms] (mean, across all concurrent requests)
Transfer rate: 2534.46 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 4 24.1 0 200
Processing: 69 227 35.5 212 447
Waiting: 0 227 36.4 212 447
Total: 69 231 42.0 212 530
Percentage of the requests served within a certain time (ms)
50% 212
66% 214
75% 233
80% 253
90% 287
95% 291
98% 379
99% 441
100% 530 (longest request)
on the same system
$ ab -s 20 -c 2000 -n100000 'http://127.0.0.1:80/'
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 127.0.0.1 (be patient)
apr_socket_recv: Connection reset by peer (54)
Total of 1 requests completed
$ ab -s 20 -c 1000 -n100000 'http://127.0.0.1:80/'
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 127.0.0.1 (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: nginx/1.14.2
Server Hostname: 127.0.0.1
Server Port: 80
Document Path: /
Document Length: 185 bytes
Concurrency Level: 1000
Time taken for tests: 3.765 seconds
Complete requests: 100000
Failed requests: 0
Non-2xx responses: 100000
Total transferred: 37700000 bytes
HTML transferred: 18500000 bytes
Requests per second: 26557.00 [#/sec] (mean)
Time per request: 37.655 [ms] (mean)
Time per request: 0.038 [ms] (mean, across all concurrent requests)
Transfer rate: 9777.33 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 14 150.9 5 3013
Processing: 5 15 24.0 11 658
Waiting: 0 13 22.2 10 467
Total: 7 29 152.9 15 3430
Percentage of the requests served within a certain time (ms)
50% 15
66% 19
75% 20
80% 21
90% 25
95% 56
98% 75
99% 237
100% 3430 (longest request)