starpc 0.46.0 → 0.46.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,448 @@
1
+ //go:build deps_only
2
+
3
+ // C++ TCP integration client for cross-language testing.
4
+ // Connects to a TCP server, runs the echo test suite.
5
+
6
+ #include <arpa/inet.h>
7
+ #include <atomic>
8
+ #include <csignal>
9
+ #include <cstdint>
10
+ #include <cstring>
11
+ #include <iostream>
12
+ #include <mutex>
13
+ #include <netinet/in.h>
14
+ #include <sys/socket.h>
15
+ #include <thread>
16
+ #include <unistd.h>
17
+
18
+ #include "echo/echo_srpc.pb.hpp"
19
+ #include "srpc/rpcproto.pb.h"
20
+ #include "srpc/starpc.hpp"
21
+
22
+ namespace {
23
+
24
+ const char *kTestBody = "hello world via starpc cross-language e2e test";
25
+
26
+ // ReadFull reads exactly n bytes from fd.
27
+ bool ReadFull(int fd, void *buf, size_t n) {
28
+ size_t total = 0;
29
+ while (total < n) {
30
+ ssize_t r = read(fd, static_cast<char *>(buf) + total, n - total);
31
+ if (r <= 0)
32
+ return false;
33
+ total += r;
34
+ }
35
+ return true;
36
+ }
37
+
38
+ // WriteFull writes exactly n bytes to fd.
39
+ bool WriteFull(int fd, const void *buf, size_t n) {
40
+ size_t total = 0;
41
+ while (total < n) {
42
+ ssize_t w = write(fd, static_cast<const char *>(buf) + total, n - total);
43
+ if (w <= 0)
44
+ return false;
45
+ total += w;
46
+ }
47
+ return true;
48
+ }
49
+
50
+ // TcpPacketWriter writes length-prefixed packets to a TCP socket.
51
+ class TcpPacketWriter : public starpc::PacketWriter {
52
+ public:
53
+ explicit TcpPacketWriter(int fd) : fd_(fd) {}
54
+
55
+ starpc::Error WritePacket(const srpc::Packet &pkt) override {
56
+ std::lock_guard<std::mutex> lock(mtx_);
57
+ std::string data;
58
+ if (!pkt.SerializeToString(&data))
59
+ return starpc::Error::InvalidMessage;
60
+
61
+ uint32_t len = static_cast<uint32_t>(data.size());
62
+ if (!WriteFull(fd_, &len, 4))
63
+ return starpc::Error::EOF_;
64
+ if (!WriteFull(fd_, data.data(), data.size()))
65
+ return starpc::Error::EOF_;
66
+ return starpc::Error::OK;
67
+ }
68
+
69
+ starpc::Error Close() override {
70
+ shutdown(fd_, SHUT_WR);
71
+ return starpc::Error::OK;
72
+ }
73
+
74
+ private:
75
+ int fd_;
76
+ std::mutex mtx_;
77
+ };
78
+
79
+ // Connect to TCP server, returns socket fd.
80
+ int TcpConnect(const std::string &host, int port) {
81
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
82
+ if (fd < 0)
83
+ return -1;
84
+
85
+ struct sockaddr_in addr {};
86
+ addr.sin_family = AF_INET;
87
+ addr.sin_port = htons(port);
88
+ inet_pton(AF_INET, host.c_str(), &addr.sin_addr);
89
+
90
+ if (connect(fd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) <
91
+ 0) {
92
+ close(fd);
93
+ return -1;
94
+ }
95
+ return fd;
96
+ }
97
+
98
+ // ReadPacketLoop reads length-prefixed packets from fd and feeds them to rpc.
99
+ void ReadPacketLoop(int fd, starpc::ClientRPC *rpc, std::atomic<bool> *done) {
100
+ while (!done->load()) {
101
+ uint32_t len = 0;
102
+ if (!ReadFull(fd, &len, 4)) {
103
+ rpc->HandleStreamClose(starpc::Error::EOF_);
104
+ break;
105
+ }
106
+ std::string data(len, '\0');
107
+ if (!ReadFull(fd, data.data(), len)) {
108
+ rpc->HandleStreamClose(starpc::Error::EOF_);
109
+ break;
110
+ }
111
+ starpc::Error err = rpc->HandlePacketData(data);
112
+ if (err != starpc::Error::OK)
113
+ break;
114
+ }
115
+ }
116
+
117
+ // CleanupConn shuts down the socket, joins the reader thread, then closes fd.
118
+ // Must be called to avoid fd reuse races between close() and the reader thread.
119
+ void CleanupConn(int fd, std::atomic<bool> &done, std::thread &reader) {
120
+ done.store(true);
121
+ shutdown(fd, SHUT_RDWR);
122
+ reader.join();
123
+ close(fd);
124
+ }
125
+
126
+ // ParseAddr parses "host:port" into host and port.
127
+ bool ParseAddr(const std::string &addr, std::string *host, int *port) {
128
+ auto pos = addr.rfind(':');
129
+ if (pos == std::string::npos)
130
+ return false;
131
+ *host = addr.substr(0, pos);
132
+ *port = std::stoi(addr.substr(pos + 1));
133
+ return true;
134
+ }
135
+
136
+ bool TestUnary(const std::string &host, int port) {
137
+ std::cout << "Testing Unary RPC... " << std::flush;
138
+
139
+ int fd = TcpConnect(host, port);
140
+ if (fd < 0) {
141
+ std::cerr << "FAILED: connect" << std::endl;
142
+ return false;
143
+ }
144
+
145
+ auto rpc = starpc::NewClientRPC("echo.Echoer", "Echo");
146
+ auto writer = std::make_unique<TcpPacketWriter>(fd);
147
+
148
+ std::atomic<bool> done{false};
149
+ std::thread reader(ReadPacketLoop, fd, rpc.get(), &done);
150
+
151
+ echo::EchoMsg req;
152
+ req.set_body(kTestBody);
153
+ std::string reqData;
154
+ req.SerializeToString(&reqData);
155
+
156
+ starpc::Error err = rpc->Start(writer.get(), true, reqData);
157
+ if (err != starpc::Error::OK) {
158
+ std::cerr << "FAILED: start: " << starpc::ErrorString(err) << std::endl;
159
+ CleanupConn(fd, done, reader);
160
+ return false;
161
+ }
162
+
163
+ std::string respData;
164
+ err = rpc->ReadOne(&respData);
165
+ if (err != starpc::Error::OK) {
166
+ std::cerr << "FAILED: read: " << starpc::ErrorString(err) << std::endl;
167
+ rpc->Close();
168
+ CleanupConn(fd, done, reader);
169
+ return false;
170
+ }
171
+
172
+ echo::EchoMsg resp;
173
+ if (!resp.ParseFromString(respData) || resp.body() != kTestBody) {
174
+ std::cerr << "FAILED: body mismatch" << std::endl;
175
+ rpc->Close();
176
+ CleanupConn(fd, done, reader);
177
+ return false;
178
+ }
179
+
180
+ rpc->Close();
181
+ CleanupConn(fd, done, reader);
182
+
183
+ std::cout << "PASSED" << std::endl;
184
+ return true;
185
+ }
186
+
187
+ bool TestServerStream(const std::string &host, int port) {
188
+ std::cout << "Testing ServerStream RPC... " << std::flush;
189
+
190
+ int fd = TcpConnect(host, port);
191
+ if (fd < 0) {
192
+ std::cerr << "FAILED: connect" << std::endl;
193
+ return false;
194
+ }
195
+
196
+ auto rpc = starpc::NewClientRPC("echo.Echoer", "EchoServerStream");
197
+ auto writer = std::make_unique<TcpPacketWriter>(fd);
198
+
199
+ std::atomic<bool> done{false};
200
+ std::thread reader(ReadPacketLoop, fd, rpc.get(), &done);
201
+
202
+ echo::EchoMsg req;
203
+ req.set_body(kTestBody);
204
+ std::string reqData;
205
+ req.SerializeToString(&reqData);
206
+
207
+ starpc::Error err = rpc->Start(writer.get(), true, reqData);
208
+ if (err != starpc::Error::OK) {
209
+ std::cerr << "FAILED: start" << std::endl;
210
+ CleanupConn(fd, done, reader);
211
+ return false;
212
+ }
213
+
214
+ int received = 0;
215
+ for (int i = 0; i < 5; i++) {
216
+ std::string respData;
217
+ err = rpc->ReadOne(&respData);
218
+ if (err != starpc::Error::OK) {
219
+ std::cerr << "FAILED: read " << i << ": " << starpc::ErrorString(err)
220
+ << std::endl;
221
+ rpc->Close();
222
+ CleanupConn(fd, done, reader);
223
+ return false;
224
+ }
225
+ echo::EchoMsg resp;
226
+ if (!resp.ParseFromString(respData) || resp.body() != kTestBody) {
227
+ std::cerr << "FAILED: body mismatch at " << i << std::endl;
228
+ rpc->Close();
229
+ CleanupConn(fd, done, reader);
230
+ return false;
231
+ }
232
+ received++;
233
+ }
234
+
235
+ rpc->Close();
236
+ CleanupConn(fd, done, reader);
237
+
238
+ if (received != 5) {
239
+ std::cerr << "FAILED: expected 5, got " << received << std::endl;
240
+ return false;
241
+ }
242
+
243
+ std::cout << "PASSED" << std::endl;
244
+ return true;
245
+ }
246
+
247
+ bool TestClientStream(const std::string &host, int port) {
248
+ std::cout << "Testing ClientStream RPC... " << std::flush;
249
+
250
+ int fd = TcpConnect(host, port);
251
+ if (fd < 0) {
252
+ std::cerr << "FAILED: connect" << std::endl;
253
+ return false;
254
+ }
255
+
256
+ auto rpc = starpc::NewClientRPC("echo.Echoer", "EchoClientStream");
257
+ auto writer = std::make_unique<TcpPacketWriter>(fd);
258
+
259
+ std::atomic<bool> done{false};
260
+ std::thread reader(ReadPacketLoop, fd, rpc.get(), &done);
261
+
262
+ starpc::Error err = rpc->Start(writer.get(), false, "");
263
+ if (err != starpc::Error::OK) {
264
+ std::cerr << "FAILED: start" << std::endl;
265
+ CleanupConn(fd, done, reader);
266
+ return false;
267
+ }
268
+
269
+ echo::EchoMsg req;
270
+ req.set_body(kTestBody);
271
+ std::string reqData;
272
+ req.SerializeToString(&reqData);
273
+
274
+ err = rpc->WriteCallData(reqData, false, false, starpc::Error::OK);
275
+ if (err != starpc::Error::OK) {
276
+ std::cerr << "FAILED: write" << std::endl;
277
+ CleanupConn(fd, done, reader);
278
+ return false;
279
+ }
280
+
281
+ err = rpc->WriteCallData("", false, true, starpc::Error::OK);
282
+ if (err != starpc::Error::OK) {
283
+ std::cerr << "FAILED: close send" << std::endl;
284
+ CleanupConn(fd, done, reader);
285
+ return false;
286
+ }
287
+
288
+ std::string respData;
289
+ err = rpc->ReadOne(&respData);
290
+ if (err != starpc::Error::OK) {
291
+ std::cerr << "FAILED: read: " << starpc::ErrorString(err) << std::endl;
292
+ rpc->Close();
293
+ CleanupConn(fd, done, reader);
294
+ return false;
295
+ }
296
+
297
+ echo::EchoMsg resp;
298
+ if (!resp.ParseFromString(respData) || resp.body() != kTestBody) {
299
+ std::cerr << "FAILED: body mismatch" << std::endl;
300
+ rpc->Close();
301
+ CleanupConn(fd, done, reader);
302
+ return false;
303
+ }
304
+
305
+ rpc->Close();
306
+ CleanupConn(fd, done, reader);
307
+
308
+ std::cout << "PASSED" << std::endl;
309
+ return true;
310
+ }
311
+
312
+ bool TestBidiStream(const std::string &host, int port) {
313
+ std::cout << "Testing BidiStream RPC... " << std::flush;
314
+
315
+ int fd = TcpConnect(host, port);
316
+ if (fd < 0) {
317
+ std::cerr << "FAILED: connect" << std::endl;
318
+ return false;
319
+ }
320
+
321
+ auto rpc = starpc::NewClientRPC("echo.Echoer", "EchoBidiStream");
322
+ auto writer = std::make_unique<TcpPacketWriter>(fd);
323
+
324
+ std::atomic<bool> done{false};
325
+ std::thread reader(ReadPacketLoop, fd, rpc.get(), &done);
326
+
327
+ starpc::Error err = rpc->Start(writer.get(), false, "");
328
+ if (err != starpc::Error::OK) {
329
+ std::cerr << "FAILED: start" << std::endl;
330
+ CleanupConn(fd, done, reader);
331
+ return false;
332
+ }
333
+
334
+ // Receive initial "hello from server" message.
335
+ std::string initData;
336
+ err = rpc->ReadOne(&initData);
337
+ if (err != starpc::Error::OK) {
338
+ std::cerr << "FAILED: read init: " << starpc::ErrorString(err) << std::endl;
339
+ rpc->Close();
340
+ CleanupConn(fd, done, reader);
341
+ return false;
342
+ }
343
+ echo::EchoMsg initMsg;
344
+ if (!initMsg.ParseFromString(initData) ||
345
+ initMsg.body() != "hello from server") {
346
+ std::cerr << "FAILED: init body mismatch: '" << initMsg.body() << "'"
347
+ << std::endl;
348
+ rpc->Close();
349
+ CleanupConn(fd, done, reader);
350
+ return false;
351
+ }
352
+
353
+ // Send a message and expect echo.
354
+ echo::EchoMsg req;
355
+ req.set_body(kTestBody);
356
+ std::string reqData;
357
+ req.SerializeToString(&reqData);
358
+
359
+ err = rpc->WriteCallData(reqData, false, false, starpc::Error::OK);
360
+ if (err != starpc::Error::OK) {
361
+ std::cerr << "FAILED: write" << std::endl;
362
+ rpc->Close();
363
+ CleanupConn(fd, done, reader);
364
+ return false;
365
+ }
366
+
367
+ std::string respData;
368
+ err = rpc->ReadOne(&respData);
369
+ if (err != starpc::Error::OK) {
370
+ std::cerr << "FAILED: read echo: " << starpc::ErrorString(err) << std::endl;
371
+ rpc->Close();
372
+ CleanupConn(fd, done, reader);
373
+ return false;
374
+ }
375
+
376
+ echo::EchoMsg resp;
377
+ if (!resp.ParseFromString(respData) || resp.body() != kTestBody) {
378
+ std::cerr << "FAILED: echo body mismatch" << std::endl;
379
+ rpc->Close();
380
+ CleanupConn(fd, done, reader);
381
+ return false;
382
+ }
383
+
384
+ // Close send.
385
+ err = rpc->WriteCallData("", false, true, starpc::Error::OK);
386
+ if (err != starpc::Error::OK) {
387
+ std::cerr << "FAILED: close send" << std::endl;
388
+ }
389
+
390
+ rpc->Close();
391
+ CleanupConn(fd, done, reader);
392
+
393
+ std::cout << "PASSED" << std::endl;
394
+ return true;
395
+ }
396
+
397
+ } // namespace
398
+
399
+ int main(int argc, char *argv[]) {
400
+ // Ignore SIGPIPE so writing to a closed socket returns EPIPE instead of
401
+ // killing the process. The server may close its side before we send cleanup
402
+ // packets.
403
+ signal(SIGPIPE, SIG_IGN);
404
+
405
+ if (argc < 2) {
406
+ std::cerr << "usage: cpp-client <host:port>" << std::endl;
407
+ return 1;
408
+ }
409
+
410
+ std::string host;
411
+ int port;
412
+ if (!ParseAddr(argv[1], &host, &port)) {
413
+ std::cerr << "invalid address: " << argv[1] << std::endl;
414
+ return 1;
415
+ }
416
+
417
+ std::cout << "=== starpc C++ Cross-Language Client ===" << std::endl;
418
+
419
+ int passed = 0, failed = 0;
420
+
421
+ if (TestUnary(host, port))
422
+ passed++;
423
+ else
424
+ failed++;
425
+ if (TestServerStream(host, port))
426
+ passed++;
427
+ else
428
+ failed++;
429
+ if (TestClientStream(host, port))
430
+ passed++;
431
+ else
432
+ failed++;
433
+ if (TestBidiStream(host, port))
434
+ passed++;
435
+ else
436
+ failed++;
437
+
438
+ std::cout << std::endl
439
+ << "Results: " << passed << " passed, " << failed << " failed"
440
+ << std::endl;
441
+
442
+ if (failed > 0) {
443
+ std::cout << "FAILED" << std::endl;
444
+ return 1;
445
+ }
446
+ std::cout << "All tests passed." << std::endl;
447
+ return 0;
448
+ }
@@ -0,0 +1,221 @@
1
+ //go:build deps_only
2
+
3
+ // C++ TCP integration server for cross-language testing.
4
+ // Listens on TCP, handles one RPC per connection using length-prefixed packets.
5
+
6
+ #include <arpa/inet.h>
7
+ #include <csignal>
8
+ #include <cstdint>
9
+ #include <cstring>
10
+ #include <iostream>
11
+ #include <mutex>
12
+ #include <netinet/in.h>
13
+ #include <sys/socket.h>
14
+ #include <thread>
15
+ #include <unistd.h>
16
+
17
+ #include "echo/echo_srpc.pb.hpp"
18
+ #include "rpcstream/rpcstream.hpp"
19
+ #include "srpc/rpcproto.pb.h"
20
+ #include "srpc/starpc.hpp"
21
+
22
+ namespace {
23
+
24
+ // ReadFull reads exactly n bytes from fd.
25
+ bool ReadFull(int fd, void *buf, size_t n) {
26
+ size_t total = 0;
27
+ while (total < n) {
28
+ ssize_t r = read(fd, static_cast<char *>(buf) + total, n - total);
29
+ if (r <= 0)
30
+ return false;
31
+ total += r;
32
+ }
33
+ return true;
34
+ }
35
+
36
+ // WriteFull writes exactly n bytes to fd.
37
+ bool WriteFull(int fd, const void *buf, size_t n) {
38
+ size_t total = 0;
39
+ while (total < n) {
40
+ ssize_t w = write(fd, static_cast<const char *>(buf) + total, n - total);
41
+ if (w <= 0)
42
+ return false;
43
+ total += w;
44
+ }
45
+ return true;
46
+ }
47
+
48
+ // TcpPacketWriter writes length-prefixed packets to a TCP socket.
49
+ class TcpPacketWriter : public starpc::PacketWriter {
50
+ public:
51
+ explicit TcpPacketWriter(int fd) : fd_(fd) {}
52
+
53
+ starpc::Error WritePacket(const srpc::Packet &pkt) override {
54
+ std::lock_guard<std::mutex> lock(mtx_);
55
+ std::string data;
56
+ if (!pkt.SerializeToString(&data))
57
+ return starpc::Error::InvalidMessage;
58
+
59
+ uint32_t len = static_cast<uint32_t>(data.size());
60
+ // Write LE uint32 length prefix.
61
+ if (!WriteFull(fd_, &len, 4))
62
+ return starpc::Error::EOF_;
63
+ if (!WriteFull(fd_, data.data(), data.size()))
64
+ return starpc::Error::EOF_;
65
+ return starpc::Error::OK;
66
+ }
67
+
68
+ starpc::Error Close() override {
69
+ shutdown(fd_, SHUT_WR);
70
+ return starpc::Error::OK;
71
+ }
72
+
73
+ private:
74
+ int fd_;
75
+ std::mutex mtx_;
76
+ };
77
+
78
+ // EchoServerImpl implements the echo service.
79
+ class EchoServerImpl : public echo::SRPCEchoerServer {
80
+ public:
81
+ starpc::Error Echo(const echo::EchoMsg &req, echo::EchoMsg *resp) override {
82
+ resp->set_body(req.body());
83
+ return starpc::Error::OK;
84
+ }
85
+
86
+ starpc::Error
87
+ EchoServerStream(const echo::EchoMsg &req,
88
+ echo::SRPCEchoer_EchoServerStreamStream *strm) override {
89
+ for (int i = 0; i < 5; i++) {
90
+ echo::EchoMsg msg;
91
+ msg.set_body(req.body());
92
+ starpc::Error err = strm->Send(msg);
93
+ if (err != starpc::Error::OK)
94
+ return err;
95
+ }
96
+ return starpc::Error::OK;
97
+ }
98
+
99
+ starpc::Error EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream *strm,
100
+ echo::EchoMsg *resp) override {
101
+ echo::EchoMsg msg;
102
+ starpc::Error err = strm->Recv(&msg);
103
+ if (err != starpc::Error::OK)
104
+ return err;
105
+ resp->set_body(msg.body());
106
+ return starpc::Error::OK;
107
+ }
108
+
109
+ starpc::Error
110
+ EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream *strm) override {
111
+ // Send initial message (matches Go server behavior).
112
+ echo::EchoMsg init;
113
+ init.set_body("hello from server");
114
+ starpc::Error err = strm->Send(init);
115
+ if (err != starpc::Error::OK)
116
+ return err;
117
+
118
+ while (true) {
119
+ echo::EchoMsg msg;
120
+ err = strm->Recv(&msg);
121
+ if (err == starpc::Error::EOF_)
122
+ break;
123
+ if (err != starpc::Error::OK)
124
+ return err;
125
+ err = strm->Send(msg);
126
+ if (err != starpc::Error::OK)
127
+ return err;
128
+ }
129
+ return starpc::Error::OK;
130
+ }
131
+
132
+ starpc::Error RpcStream(echo::SRPCEchoer_RpcStreamStream *) override {
133
+ return starpc::Error::Unimplemented;
134
+ }
135
+
136
+ starpc::Error DoNothing(const google::protobuf::Empty &,
137
+ google::protobuf::Empty *) override {
138
+ return starpc::Error::OK;
139
+ }
140
+ };
141
+
142
+ // HandleConnection handles one TCP connection (one RPC).
143
+ void HandleConnection(int fd, starpc::Mux *mux) {
144
+ auto writer = std::make_unique<TcpPacketWriter>(fd);
145
+ auto serverRpc = starpc::NewServerRPC(mux, writer.get());
146
+
147
+ while (true) {
148
+ // Read 4-byte LE uint32 length prefix.
149
+ uint32_t len = 0;
150
+ if (!ReadFull(fd, &len, 4))
151
+ break;
152
+
153
+ // Read the packet data.
154
+ std::string data(len, '\0');
155
+ if (!ReadFull(fd, data.data(), len))
156
+ break;
157
+
158
+ starpc::Error err = serverRpc->HandlePacketData(data);
159
+ if (err != starpc::Error::OK && err != starpc::Error::Completed)
160
+ break;
161
+ }
162
+
163
+ close(fd);
164
+ }
165
+
166
+ } // namespace
167
+
168
+ int main() {
169
+ signal(SIGPIPE, SIG_IGN);
170
+
171
+ auto mux = starpc::NewMux();
172
+ EchoServerImpl server;
173
+ auto [handler, err] = echo::SRPCRegisterEchoer(mux.get(), &server);
174
+ (void)handler;
175
+ if (err != starpc::Error::OK) {
176
+ std::cerr << "register error: " << starpc::ErrorString(err) << std::endl;
177
+ return 1;
178
+ }
179
+
180
+ int sockfd = socket(AF_INET, SOCK_STREAM, 0);
181
+ if (sockfd < 0) {
182
+ std::cerr << "socket error" << std::endl;
183
+ return 1;
184
+ }
185
+
186
+ int opt = 1;
187
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
188
+
189
+ struct sockaddr_in addr {};
190
+ addr.sin_family = AF_INET;
191
+ addr.sin_port = 0; // OS-assigned port.
192
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
193
+
194
+ if (bind(sockfd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) <
195
+ 0) {
196
+ std::cerr << "bind error" << std::endl;
197
+ close(sockfd);
198
+ return 1;
199
+ }
200
+
201
+ if (listen(sockfd, 10) < 0) {
202
+ std::cerr << "listen error" << std::endl;
203
+ close(sockfd);
204
+ return 1;
205
+ }
206
+
207
+ // Get the assigned port.
208
+ socklen_t addrLen = sizeof(addr);
209
+ getsockname(sockfd, reinterpret_cast<struct sockaddr *>(&addr), &addrLen);
210
+ std::cout << "LISTENING 127.0.0.1:" << ntohs(addr.sin_port) << std::endl;
211
+
212
+ while (true) {
213
+ int clientFd = accept(sockfd, nullptr, nullptr);
214
+ if (clientFd < 0)
215
+ break;
216
+ std::thread(HandleConnection, clientFd, mux.get()).detach();
217
+ }
218
+
219
+ close(sockfd);
220
+ return 0;
221
+ }