version 0.2.0
This commit is contained in:
parent
d19c4efad3
commit
3dddee43f7
14
.github/workflows/docker-publish.yml
vendored
14
.github/workflows/docker-publish.yml
vendored
|
@ -1,16 +1,14 @@
|
|||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
release:
|
||||
types: [created]
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -24,6 +22,14 @@ jobs:
|
|||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
|
||||
|
||||
- name: Get Release Tag
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Write Version
|
||||
run: |
|
||||
echo Building with version number $RELEASE_VERSION
|
||||
echo $RELEASE_VERSION > src/build/version.txt
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
anthracite
|
||||
src/error_pages/
|
||||
src/build/version.cpp
|
||||
|
|
66
CHANGELOG.md
66
CHANGELOG.md
|
@ -1,3 +1,69 @@
|
|||
# 0.2.0 Fifth Pre-Release
|
||||
- Added true HTTP/1.1 support with persistent connections
|
||||
- Added HTTP/1.0 vs HTTP/1.1 test to benchmarking suite
|
||||
- Slight improvements to benchmarking
|
||||
- Added version number information to binaries at build-time
|
||||
- Added error page generation build step
|
||||
- GitHub CI pipeline will now tag the version when publishing the container to the registry
|
||||
- General system stability improvements were made to enhance the user's experience
|
||||
|
||||
## HTTP/1.1 Speed improvements
|
||||
The following benchmark shows the speed improvements created by implementing
|
||||
persistent connections with HTTP/1.1. This test measures the time it takes to
|
||||
request a 222 byte file 10,000 times by one user
|
||||
|
||||
```
|
||||
=====[ Anthracite Benchmarking Tool ]=====
|
||||
Requests : 10000
|
||||
Test : HTTP 1.0 vs HTTP 1.1
|
||||
|
||||
|
||||
HTTP/1.0 Total Time: 3.1160 seconds
|
||||
HTTP/1.1 Total Time: 0.3621 seconds
|
||||
```
|
||||
|
||||
## Benchmark Results
|
||||
Each benchmark makes 1000 requests requesting a 50MB file using
|
||||
100 users to the webserver running in a Docker container.
|
||||
|
||||
This is a change from previous benchmarks which used a large html
|
||||
file.
|
||||
|
||||
```
|
||||
=====[ Anthracite Benchmarking Tool ]=====
|
||||
Requests : 1000
|
||||
Users/Threads: 100
|
||||
Test : Load Test
|
||||
|
||||
|
||||
====[ anthracite ]=====
|
||||
Average Response Time: 19.5831 seconds
|
||||
p995 Response Time : 38.9563 seconds
|
||||
p99 Response Time : 37.1518 seconds
|
||||
p90 Response Time : 27.5117 seconds
|
||||
p75 Response Time : 21.4345 seconds
|
||||
p50 Response Time : 17.7999 seconds
|
||||
Total Response Time : 19583.1491 seconds
|
||||
====[ nginx ]=====
|
||||
Average Response Time: 19.5464 seconds
|
||||
p995 Response Time : 49.9527 seconds
|
||||
p99 Response Time : 47.5037 seconds
|
||||
p90 Response Time : 29.7642 seconds
|
||||
p75 Response Time : 21.4559 seconds
|
||||
p50 Response Time : 17.1338 seconds
|
||||
Total Response Time : 19546.4399 seconds
|
||||
====[ apache ]=====
|
||||
Average Response Time: 20.8133 seconds
|
||||
p995 Response Time : 42.5797 seconds
|
||||
p99 Response Time : 39.8580 seconds
|
||||
p90 Response Time : 30.1892 seconds
|
||||
p75 Response Time : 22.3492 seconds
|
||||
p50 Response Time : 19.0437 seconds
|
||||
Total Response Time : 20813.3035 seconds
|
||||
==========
|
||||
Total Test Time : 612.3112 seconds
|
||||
```
|
||||
|
||||
# 0.1.2 Fourth Pre-Release
|
||||
|
||||
- Fixed bug with mapping / to index.html
|
||||
|
|
59
README.md
59
README.md
|
@ -1,59 +1,28 @@
|
|||
# Anthracite
|
||||
A simple web server written in C++
|
||||
A simple web server written in C++. Supports HTTP 1.0 & 1.1.
|
||||
|
||||
## Module-Based Backends
|
||||
Anthracite includes (read: will include) system for allowing different "backend modules" to handle requests.
|
||||
This allows for anthracite to be extended for additional use-cases. For example, the following
|
||||
backends could be implemented:
|
||||
## Developing
|
||||
|
||||
- File: Return files from a directory
|
||||
- Reverse Proxy: Pass the request to another server
|
||||
- Web Framework: Pass the request into an application built on your favorite web framework
|
||||
To build/develop Anthracite, you must have C++20, Make, and Python3 installed.
|
||||
|
||||
## Building & Debugging
|
||||
|
||||
Once you have the repository cloned, you can run the following command to build Anthracite:
|
||||
|
||||
```shell
|
||||
make build
|
||||
```
|
||||
|
||||
It will create a binary file named `./anthracite`
|
||||
|
||||
To save time, you can use the following command to build and run anthracite on port `8080`:
|
||||
|
||||
```shell
|
||||
make run
|
||||
```
|
||||
|
||||
To save time again, you can use the following command to build and debug anthracite in gdb:
|
||||
|
||||
```shell
|
||||
make debug
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Run the following commands to serve all files located in `./www/`:
|
||||
|
||||
```shell
|
||||
./anthracite [PORT_NUMBER]
|
||||
```
|
||||
You can run Anthracite with: `make run`
|
||||
|
||||
## Todo
|
||||
- [x] HTTP/1.0
|
||||
- [x] Serve HTML Pages
|
||||
- [x] Properly parse HTTP requests
|
||||
- [x] Add module-based backend system for handling requests
|
||||
- [x] Multithreading
|
||||
- [ ] Cleanup (this one will never truly be done)
|
||||
- [ ] Proper error handling
|
||||
- [ ] Build out module-based backend system for handling requests
|
||||
- [ ] Fix glaring security issues
|
||||
- [x] HTTP/1.1
|
||||
- [ ] Improve benchmarking infrastructure
|
||||
- [ ] Faster parsing
|
||||
- [ ] Speed optimizations such as keeping the most visited html pages in memory
|
||||
- [ ] Cleanup codebase
|
||||
- [ ] Enable cache support
|
||||
- [ ] Support newer HTTP versions
|
||||
- [ ] Fix glaring security issues
|
||||
- [ ] Proper error handling
|
||||
- [ ] User configuration
|
||||
- [ ] Build out module-based backend system for handling requests
|
||||
- [ ] HTTP/2
|
||||
- [ ] Enhance logging
|
||||
- [ ] Cleanup (this one will never truly be done)
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
cd ..
|
||||
docker build . -t anthracite:latest
|
||||
cd benchmark
|
||||
docker build . -t benchmark-anthracite -f anthracite.Dockerfile
|
||||
docker compose up -d
|
1020251
benchmark/www/large.html
1020251
benchmark/www/large.html
File diff suppressed because it is too large
Load diff
36
benchmarks/http_1_v_11/benchmark.py
Normal file
36
benchmarks/http_1_v_11/benchmark.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import socket
|
||||
import time
|
||||
|
||||
num_requests = 10000
|
||||
|
||||
http_1_times = []
|
||||
http_11_times = []
|
||||
|
||||
print('=====[ Anthracite Benchmarking Tool ]=====')
|
||||
print(f'Requests : {num_requests}')
|
||||
print(f'Test : HTTP 1.0 vs HTTP 1.1\n\n')
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.connect(("localhost" , 8091))
|
||||
for i in range(num_requests):
|
||||
start_time = time.time()
|
||||
s.sendall(b"GET /test.html HTTP/1.1\r\nAccept: text/html\r\nConnection: keep-alive\r\n\r\n")
|
||||
data = s.recv(220)
|
||||
end_time = time.time()
|
||||
http_11_times.append((end_time - start_time))
|
||||
s.close()
|
||||
|
||||
for i in range(num_requests):
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
start_time = time.time()
|
||||
s.connect(("localhost" , 8091))
|
||||
s.sendall(b"GET /test.html HTTP/1.0\r\nAccept: text/html\r\n\r\n")
|
||||
data = s.recv(220)
|
||||
end_time = time.time()
|
||||
http_1_times.append((end_time - start_time))
|
||||
s.close()
|
||||
|
||||
run_time_1 = sum(http_1_times)
|
||||
run_time_11 = sum(http_11_times)
|
||||
print(f'HTTP/1.0 Total Time: {run_time_1:.4f} seconds')
|
||||
print(f'HTTP/1.1 Total Time: {run_time_11:.4f} seconds')
|
7
benchmarks/http_1_v_11/docker-compose.yaml
Normal file
7
benchmarks/http_1_v_11/docker-compose.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
services:
|
||||
anthracite:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: anthracite.Dockerfile
|
||||
ports:
|
||||
- "8091:80"
|
4
benchmarks/http_1_v_11/rebuild-container.sh
Executable file
4
benchmarks/http_1_v_11/rebuild-container.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
cd ../..
|
||||
docker build . -t anthracite:latest
|
||||
cd benchmarks/http_1_v_11
|
||||
docker compose build
|
1
benchmarks/http_1_v_11/run.sh
Executable file
1
benchmarks/http_1_v_11/run.sh
Executable file
|
@ -0,0 +1 @@
|
|||
docker-compose stop && ./rebuild-container.sh && docker compose up -d && clear && python3 benchmark.py && docker-compose stop
|
2
benchmarks/http_1_v_11/www/test.html
Normal file
2
benchmarks/http_1_v_11/www/test.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<h1>Anthracite Benchmarking</h1>
|
||||
<p>Test document to test the speed of HTTP/1.0 and HTTP/1.1</p>
|
2
benchmarks/load_test/anthracite.Dockerfile
Normal file
2
benchmarks/load_test/anthracite.Dockerfile
Normal file
|
@ -0,0 +1,2 @@
|
|||
FROM anthracite:latest
|
||||
COPY ./www/ /www/
|
|
@ -5,7 +5,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|||
from http.client import HTTPConnection
|
||||
|
||||
HTTPConnection._http_vsn_str = 'HTTP/1.0'
|
||||
urls = { 'anthracite': 'http://localhost:8081/large.html','nginx': 'http://localhost:8082/large.html', 'apache': 'http://localhost:8083/large.html' }
|
||||
urls = { 'anthracite': 'http://localhost:8081/50MB.zip','nginx': 'http://localhost:8082/50MB.zip', 'apache': 'http://localhost:8083/50MB.zip' }
|
||||
num_requests = 1000
|
||||
num_users = 100 # number of threads
|
||||
response_times = {}
|
||||
|
@ -37,7 +37,8 @@ def make_request(request_number, server_name):
|
|||
|
||||
print('=====[ Anthracite Benchmarking Tool ]=====')
|
||||
print(f'Requests : {num_requests}')
|
||||
print(f'Users/Threads: {num_users}\n\n')
|
||||
print(f'Users/Threads: {num_users}')
|
||||
print(f'Test : Load Test\n\n')
|
||||
start_all_time = time.time()
|
||||
|
||||
futures = []
|
5
benchmarks/load_test/rebuild-container.sh
Executable file
5
benchmarks/load_test/rebuild-container.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
cd ../..
|
||||
docker build . -t anthracite:latest
|
||||
cd benchmarks/load_test
|
||||
docker compose build
|
||||
docker compose up -d
|
1
benchmarks/load_test/run.sh
Executable file
1
benchmarks/load_test/run.sh
Executable file
|
@ -0,0 +1 @@
|
|||
docker-compose stop && ./rebuild-container.sh && docker compose up -d && clear && python3 benchmark.py && docker-compose stop
|
BIN
benchmarks/load_test/www/50MB.zip
Normal file
BIN
benchmarks/load_test/www/50MB.zip
Normal file
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
.PHONY: format lint build build-release build-docker run debug
|
||||
|
||||
build:
|
||||
python3 ./error_gen.py
|
||||
cd ./build && ./version.sh && python3 ./error_gen.py
|
||||
g++ main.cpp --std=c++20 -g -o ./anthracite
|
||||
|
||||
build-release:
|
||||
|
@ -19,6 +19,9 @@ run-test: build
|
|||
debug: build
|
||||
gdb --args ./anthracite 8080
|
||||
|
||||
debug-test: build
|
||||
gdb --args ./anthracite 8080 ./test_www
|
||||
|
||||
format:
|
||||
clang-format *.cpp -i
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "backend.cpp"
|
||||
|
||||
class file_backend : public backend {
|
||||
|
@ -13,9 +14,7 @@ private:
|
|||
|
||||
int status = http_status_codes::OK;
|
||||
if (file_info == file_cache.end()) {
|
||||
status = http_status_codes::NOT_FOUND;
|
||||
filename = "./error_pages/404.html";
|
||||
file_info = file_cache.find(filename);
|
||||
return handle_error(http_status_codes::NOT_FOUND);
|
||||
}
|
||||
|
||||
return std::make_unique<http_response>(file_info->second, filename, status);
|
||||
|
@ -47,9 +46,22 @@ public:
|
|||
populate_cache();
|
||||
}
|
||||
|
||||
~file_backend() = default;
|
||||
|
||||
std::unique_ptr<http_response> handle_request(http_request& req) override {
|
||||
return handle_request_cache(req);
|
||||
}
|
||||
|
||||
std::unique_ptr<http_response> handle_error(const http_status_codes& error) {
|
||||
std::string filename = "./error_pages/" + std::to_string(error) + ".html";
|
||||
auto file_info = file_cache.find(filename);
|
||||
|
||||
http_status_codes status = error;
|
||||
if (file_info == file_cache.end()) {
|
||||
status = http_status_codes::NOT_FOUND;
|
||||
filename = "./error_pages/404.html";
|
||||
file_info = file_cache.find(filename);
|
||||
}
|
||||
|
||||
return std::make_unique<http_response>(file_info->second, filename, status);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
|
||||
import os
|
||||
|
||||
version = "0.1.5"
|
||||
version = "Unknown"
|
||||
|
||||
def generate_error_page(error_code, error_title, error_description):
|
||||
with open("version.txt", "r") as file:
|
||||
version = file.read().strip()
|
||||
|
||||
def generate_error_page(error_code, error_title):
|
||||
html = f"""<html>
|
||||
<head><title>{error_title}</title></head>
|
||||
<body>
|
||||
<center>
|
||||
<h1>{error_code} - {error_title}</h1>
|
||||
<hr>
|
||||
<p>{error_description}</p>
|
||||
<p>Anthracite/{version}</p>
|
||||
<p><small><a href="https://github.com/nickorlow/anthracite">This is Open Source Software</small></a></p>
|
||||
</center>
|
||||
|
@ -64,12 +66,11 @@ error_codes = {
|
|||
}
|
||||
|
||||
|
||||
error_dir = './error_pages'
|
||||
error_dir = '../error_pages'
|
||||
os.makedirs(error_dir, exist_ok=True)
|
||||
|
||||
for code, title in error_codes.items():
|
||||
error_description = error_codes[code]
|
||||
error_page = generate_error_page(code, title, error_description)
|
||||
error_page = generate_error_page(code, title)
|
||||
file_path = os.path.join(error_dir, f"{code}.html")
|
||||
with open(file_path, "w") as file:
|
||||
file.write(error_page)
|
3
src/build/version.sh
Executable file
3
src/build/version.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
echo "#include <string>" > version.cpp
|
||||
echo "const std::string ANTHRACITE_VERSION_STRING = \"$(cat version.txt)\";" >> version.cpp
|
||||
echo "const std::string ANTHRACITE_FULL_VERSION_STRING = \"Anthracite/$(cat version.txt)\";" >> version.cpp
|
1
src/build/version.txt
Normal file
1
src/build/version.txt
Normal file
|
@ -0,0 +1 @@
|
|||
0.2.0
|
|
@ -219,6 +219,8 @@ enum http_version { HTTP_0_9,
|
|||
HTTP_3_0 };
|
||||
|
||||
static std::unordered_map<std::string, http_version> const http_version_map = {
|
||||
// This is because HTTP 0.9 didn't specify version in the header
|
||||
{ "", HTTP_0_9 },
|
||||
{ "HTTP/0.9", HTTP_0_9 },
|
||||
{ "HTTP/1.0", HTTP_1_0 },
|
||||
{ "HTTP/1.1", HTTP_1_1 },
|
||||
|
|
|
@ -20,8 +20,8 @@ public:
|
|||
name_value(name_value&&) = default;
|
||||
name_value& operator=(name_value&&) = default;
|
||||
|
||||
std::string name() { return _name; }
|
||||
std::string value() { return _value; }
|
||||
std::string& name() { return _name; }
|
||||
std::string& value() { return _value; }
|
||||
|
||||
virtual std::string to_string() { return ""; }
|
||||
};
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
#include "constants.cpp"
|
||||
#include "header_query.cpp"
|
||||
#include "../socket.cpp"
|
||||
#include "../build/version.cpp"
|
||||
|
|
|
@ -19,10 +19,9 @@ private:
|
|||
std::unordered_map<std::string, query_param> _query_params; // kinda goofy, whatever
|
||||
|
||||
public:
|
||||
http_request(anthracite_socket& s)
|
||||
: _path(""), _client_ipaddr(s.get_client_ip())
|
||||
http_request(std::string& raw_data, std::string client_ip)
|
||||
: _path(""), _client_ipaddr(client_ip)
|
||||
{
|
||||
std::string raw_data = s.recv_message(HTTP_HEADER_BYTES);
|
||||
|
||||
parser_state state = METHOD;
|
||||
|
||||
|
@ -136,6 +135,25 @@ public:
|
|||
|
||||
std::string client_ip() { return _client_ipaddr; }
|
||||
|
||||
http_version get_http_version() {
|
||||
return _http_version;
|
||||
}
|
||||
|
||||
bool is_supported_version() {
|
||||
return _http_version == HTTP_1_1 || _http_version == HTTP_1_0;
|
||||
}
|
||||
|
||||
bool close_connection() {
|
||||
const auto& header = _headers.find("Connection");
|
||||
const bool found = header != _headers.end();
|
||||
|
||||
if(found && header->second.value() == "keep-alive") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string to_string()
|
||||
{
|
||||
std::string response = "";
|
||||
|
|
|
@ -32,17 +32,18 @@ public:
|
|||
std::string header_to_string()
|
||||
{
|
||||
std::string response = "";
|
||||
response += "HTTP/1.0 " + std::to_string(_status_code) + " " + http_status_map.find(_status_code)->second + "\r\n";
|
||||
response += "HTTP/1.1 " + std::to_string(_status_code) + " " + http_status_map.find(_status_code)->second + "\r\n";
|
||||
std::string content_type = "text/html";
|
||||
std::string file_extension = _filename.substr(_filename.rfind('.') + 1);
|
||||
auto mime_type = mime_types.find(file_extension);
|
||||
if (mime_type != mime_types.end()) {
|
||||
content_type = mime_type->second;
|
||||
}
|
||||
|
||||
add_header(http_header("Content-Type", content_type), false);
|
||||
add_header(http_header("Content-Length", std::to_string(_content.length())), false);
|
||||
add_header(http_header("Server", "Anthracite/0.0.1"), false);
|
||||
add_header(http_header("Origin-Server", "Anthracite/0.0.1"), false);
|
||||
add_header(http_header("Server", ANTHRACITE_FULL_VERSION_STRING), false);
|
||||
add_header(http_header("Origin-Server", ANTHRACITE_FULL_VERSION_STRING), false);
|
||||
|
||||
for (auto header : _headers) {
|
||||
response += header.second.to_string();
|
||||
|
|
27
src/main.cpp
27
src/main.cpp
|
@ -17,15 +17,24 @@ void log_request_and_response(http_request& req, std::unique_ptr<http_response>&
|
|||
constexpr int default_port = 80;
|
||||
constexpr int max_worker_threads = 128;
|
||||
|
||||
void handle_client(anthracite_socket s, backend& b, std::mutex& thread_wait_mutex, std::condition_variable& thread_wait_condvar, int& active_threads)
|
||||
void handle_client(anthracite_socket s, backend& b, file_backend& fb, std::mutex& thread_wait_mutex, std::condition_variable& thread_wait_condvar, int& active_threads)
|
||||
{
|
||||
http_request req(s);
|
||||
std::unique_ptr<http_response> resp = b.handle_request(req);
|
||||
log_request_and_response(req, resp);
|
||||
std::string header = resp->header_to_string();
|
||||
s.send_message(header);
|
||||
s.send_message(resp->content());
|
||||
resp.reset();
|
||||
while(true) {
|
||||
std::string raw_request = s.recv_message(HTTP_HEADER_BYTES);
|
||||
if(raw_request == "") {
|
||||
break;
|
||||
}
|
||||
http_request req(raw_request, s.get_client_ip());
|
||||
std::unique_ptr<http_response> resp = req.is_supported_version() ? b.handle_request(req) : fb.handle_error(http_status_codes::HTTP_VERSION_NOT_SUPPORTED);
|
||||
log_request_and_response(req, resp);
|
||||
std::string header = resp->header_to_string();
|
||||
s.send_message(header);
|
||||
s.send_message(resp->content());
|
||||
resp.reset();
|
||||
if(req.close_connection()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
s.close_conn();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(thread_wait_mutex);
|
||||
|
@ -58,7 +67,7 @@ int main(int argc, char** argv)
|
|||
std::unique_lock<std::mutex> lock(thread_wait_mutex);
|
||||
thread_wait_condvar.wait(lock, [active_threads] { return active_threads < max_worker_threads; });
|
||||
active_threads++;
|
||||
std::thread(handle_client, s, std::ref(fb), std::ref(thread_wait_mutex), std::ref(thread_wait_condvar), std::ref(active_threads)).detach();
|
||||
std::thread(handle_client, s, std::ref(fb), std::ref(fb), std::ref(thread_wait_mutex), std::ref(thread_wait_condvar), std::ref(active_threads)).detach();
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
#include <sys/time.h>
|
||||
|
||||
constexpr int MAX_QUEUE_LENGTH = 100;
|
||||
|
||||
|
@ -70,8 +71,17 @@ public:
|
|||
return "";
|
||||
}
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
|
||||
char response[buffer_size + 1];
|
||||
recv(client_socket, response, sizeof(response), 0);
|
||||
int result = recv(client_socket, response, sizeof(response), 0);
|
||||
|
||||
if (result < 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
response[buffer_size] = '\0';
|
||||
return std::string(response);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue