Repository navigation
Expand file tree
/
Copy pathbackend_server.cpp
More file actions
111 lines (93 loc) · 3.1 KB
/
Copy pathbackend_server.cpp
File metadata and controls
111 lines (93 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// backend_server.cpp
//
// A trivial TCP "backend" used to test the load balancer.
// Listens on a given port, accepts connections, reads whatever the client
// sends, and echoes back a response tagged with this server's port number
// (so that when we hit the load balancer, we can see WHICH backend served
// the request - essential for verifying round-robin / least-connections
// logic actually works).
//
// Usage: ./backend_server <port> [artificial_delay_ms]
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <sstream>
#include <string>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
namespace {
constexpr int kBacklog = 128;
constexpr size_t kRecvBufferSize = 4096;
void handle_client(int client_fd, int port, int delay_ms) {
char buffer[kRecvBufferSize];
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) {
close(client_fd);
return;
}
buffer[n] = '\0';
if (delay_ms > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
}
std::ostringstream response;
response << "HELLO_FROM_BACKEND_" << port
<< " (received " << n << " bytes)\n";
std::string resp_str = response.str();
ssize_t sent = 0;
while (sent < static_cast<ssize_t>(resp_str.size())) {
ssize_t s = send(client_fd, resp_str.data() + sent,
resp_str.size() - sent, 0);
if (s <= 0) break;
sent += s;
}
close(client_fd);
}
} // namespace
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <port> [artificial_delay_ms]\n";
return 1;
}
int port = std::stoi(argv[1]);
int delay_ms = (argc >= 3) ? std::stoi(argv[2]) : 0;
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("socket");
return 1;
}
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(listen_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
perror("bind");
return 1;
}
if (listen(listen_fd, kBacklog) < 0) {
perror("listen");
return 1;
}
std::cout << "[backend] listening on port " << port;
if (delay_ms > 0) std::cout << " (simulated delay " << delay_ms << "ms)";
std::cout << std::endl;
while (true) {
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listen_fd, reinterpret_cast<sockaddr*>(&client_addr),
&client_len);
if (client_fd < 0) {
perror("accept");
continue;
}
// Each backend connection handled on its own thread - this is just
// a test fixture, not the thing we're benchmarking, so simplicity
// wins over efficiency here.
std::thread(handle_client, client_fd, port, delay_ms).detach();
}
close(listen_fd);
return 0;
}