Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Kitware
GitHub Repository: Kitware/CMake
Path: blob/master/Utilities/cmcppdap/src/socket.cpp
3153 views
1
// Copyright 2019 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "socket.h"
16
17
#include "rwmutex.h"
18
19
#if defined(_WIN32)
20
#include <winsock2.h>
21
#include <ws2tcpip.h>
22
#else
23
#include <netdb.h>
24
#include <netinet/in.h>
25
#include <netinet/tcp.h>
26
#include <sys/select.h>
27
#include <sys/socket.h>
28
#include <unistd.h>
29
#endif
30
31
#if defined(_WIN32)
32
#include <atomic>
33
namespace {
34
std::atomic<int> wsaInitCount = {0};
35
} // anonymous namespace
36
#else
37
#include <fcntl.h>
38
#include <unistd.h>
39
namespace {
40
using SOCKET = int;
41
} // anonymous namespace
42
#endif
43
44
namespace {
45
constexpr SOCKET InvalidSocket = static_cast<SOCKET>(-1);
46
void init() {
47
#if defined(_WIN32)
48
if (wsaInitCount++ == 0) {
49
WSADATA winsockData;
50
(void)WSAStartup(MAKEWORD(2, 2), &winsockData);
51
}
52
#endif
53
}
54
55
void term() {
56
#if defined(_WIN32)
57
if (--wsaInitCount == 0) {
58
WSACleanup();
59
}
60
#endif
61
}
62
63
bool setBlocking(SOCKET s, bool blocking) {
64
#if defined(_WIN32)
65
u_long mode = blocking ? 0 : 1;
66
return ioctlsocket(s, FIONBIO, &mode) == NO_ERROR;
67
#else
68
auto arg = fcntl(s, F_GETFL, nullptr);
69
if (arg < 0) {
70
return false;
71
}
72
arg = blocking ? (arg & ~O_NONBLOCK) : (arg | O_NONBLOCK);
73
return fcntl(s, F_SETFL, arg) >= 0;
74
#endif
75
}
76
77
bool errored(SOCKET s) {
78
if (s == InvalidSocket) {
79
return true;
80
}
81
char error = 0;
82
socklen_t len = sizeof(error);
83
getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len);
84
return error != 0;
85
}
86
87
} // anonymous namespace
88
89
class dap::Socket::Shared : public dap::ReaderWriter {
90
public:
91
static std::shared_ptr<Shared> create(const char* address, const char* port) {
92
init();
93
94
addrinfo hints = {};
95
hints.ai_family = AF_INET;
96
hints.ai_socktype = SOCK_STREAM;
97
hints.ai_protocol = IPPROTO_TCP;
98
hints.ai_flags = AI_PASSIVE;
99
100
addrinfo* info = nullptr;
101
getaddrinfo(address, port, &hints, &info);
102
103
if (info) {
104
auto socket =
105
::socket(info->ai_family, info->ai_socktype, info->ai_protocol);
106
auto out = std::make_shared<Shared>(info, socket);
107
out->setOptions();
108
return out;
109
}
110
111
term();
112
return nullptr;
113
}
114
115
Shared(SOCKET socket) : info(nullptr), s(socket) {}
116
Shared(addrinfo* info, SOCKET socket) : info(info), s(socket) {}
117
118
~Shared() {
119
if (info) {
120
freeaddrinfo(info);
121
}
122
close();
123
term();
124
}
125
126
template <typename FUNCTION>
127
void lock(FUNCTION&& f) {
128
RLock l(mutex);
129
f(s, info);
130
}
131
132
void setOptions() {
133
RLock l(mutex);
134
if (s == InvalidSocket) {
135
return;
136
}
137
138
int enable = 1;
139
140
#if !defined(_WIN32)
141
// Prevent sockets lingering after process termination, causing
142
// reconnection issues on the same port.
143
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable));
144
145
struct {
146
int l_onoff; /* linger active */
147
int l_linger; /* how many seconds to linger for */
148
} linger = {false, 0};
149
setsockopt(s, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));
150
#endif // !defined(_WIN32)
151
152
// Enable TCP_NODELAY.
153
// DAP usually consists of small packet requests, with small packet
154
// responses. When there are many frequent, blocking requests made,
155
// Nagle's algorithm can dramatically limit the request->response rates.
156
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&enable, sizeof(enable));
157
}
158
159
// dap::ReaderWriter compliance
160
bool isOpen() {
161
{
162
RLock l(mutex);
163
if ((s != InvalidSocket) && !errored(s)) {
164
return true;
165
}
166
}
167
WLock lock(mutex);
168
s = InvalidSocket;
169
return false;
170
}
171
172
void close() {
173
{
174
RLock l(mutex);
175
if (s != InvalidSocket) {
176
#if defined(_WIN32)
177
closesocket(s);
178
#elif __APPLE__
179
// ::shutdown() *should* be sufficient to unblock ::accept(), but
180
// apparently on macos it can return ENOTCONN and ::accept() continues
181
// to block indefinitely.
182
// Note: There is a race here. Calling ::close() frees the socket ID,
183
// which may be reused before `s` is assigned InvalidSocket.
184
::shutdown(s, SHUT_RDWR);
185
::close(s);
186
#else
187
// ::shutdown() to unblock ::accept(). We'll actually close the socket
188
// under lock below.
189
::shutdown(s, SHUT_RDWR);
190
#endif
191
}
192
}
193
194
WLock l(mutex);
195
if (s != InvalidSocket) {
196
#if !defined(_WIN32) && !defined(__APPLE__)
197
::close(s);
198
#endif
199
s = InvalidSocket;
200
}
201
}
202
203
size_t read(void* buffer, size_t bytes) {
204
RLock lock(mutex);
205
if (s == InvalidSocket) {
206
return 0;
207
}
208
auto len =
209
recv(s, reinterpret_cast<char*>(buffer), static_cast<int>(bytes), 0);
210
return (len < 0) ? 0 : len;
211
}
212
213
bool write(const void* buffer, size_t bytes) {
214
RLock lock(mutex);
215
if (s == InvalidSocket) {
216
return false;
217
}
218
if (bytes == 0) {
219
return true;
220
}
221
return ::send(s, reinterpret_cast<const char*>(buffer),
222
static_cast<int>(bytes), 0) > 0;
223
}
224
225
private:
226
addrinfo* const info;
227
SOCKET s = InvalidSocket;
228
RWMutex mutex;
229
};
230
231
namespace dap {
232
233
Socket::Socket(const char* address, const char* port)
234
: shared(Shared::create(address, port)) {
235
if (shared) {
236
shared->lock([&](SOCKET socket, const addrinfo* info) {
237
if (bind(socket, info->ai_addr, (int)info->ai_addrlen) != 0) {
238
shared.reset();
239
return;
240
}
241
242
if (listen(socket, 0) != 0) {
243
shared.reset();
244
return;
245
}
246
});
247
}
248
}
249
250
std::shared_ptr<ReaderWriter> Socket::accept() const {
251
std::shared_ptr<Shared> out;
252
if (shared) {
253
shared->lock([&](SOCKET socket, const addrinfo*) {
254
if (socket != InvalidSocket && !errored(socket)) {
255
init();
256
auto accepted = ::accept(socket, 0, 0);
257
if (accepted != InvalidSocket) {
258
out = std::make_shared<Shared>(accepted);
259
out->setOptions();
260
}
261
}
262
});
263
}
264
return out;
265
}
266
267
bool Socket::isOpen() const {
268
if (shared) {
269
return shared->isOpen();
270
}
271
return false;
272
}
273
274
void Socket::close() const {
275
if (shared) {
276
shared->close();
277
}
278
}
279
280
std::shared_ptr<ReaderWriter> Socket::connect(const char* address,
281
const char* port,
282
uint32_t timeoutMillis) {
283
auto shared = Shared::create(address, port);
284
if (!shared) {
285
return nullptr;
286
}
287
288
std::shared_ptr<ReaderWriter> out;
289
shared->lock([&](SOCKET socket, const addrinfo* info) {
290
if (socket == InvalidSocket) {
291
return;
292
}
293
294
if (timeoutMillis == 0) {
295
if (::connect(socket, info->ai_addr, (int)info->ai_addrlen) == 0) {
296
out = shared;
297
}
298
return;
299
}
300
301
if (!setBlocking(socket, false)) {
302
return;
303
}
304
305
auto res = ::connect(socket, info->ai_addr, (int)info->ai_addrlen);
306
if (res == 0) {
307
if (setBlocking(socket, true)) {
308
out = shared;
309
}
310
} else {
311
const auto microseconds = timeoutMillis * 1000;
312
313
fd_set fdset;
314
FD_ZERO(&fdset);
315
FD_SET(socket, &fdset);
316
317
timeval tv;
318
tv.tv_sec = microseconds / 1000000;
319
tv.tv_usec = microseconds - static_cast<uint32_t>(tv.tv_sec * 1000000);
320
res = select(static_cast<int>(socket + 1), nullptr, &fdset, nullptr, &tv);
321
if (res > 0 && !errored(socket) && setBlocking(socket, true)) {
322
out = shared;
323
}
324
}
325
});
326
327
if (!out) {
328
return nullptr;
329
}
330
331
return out->isOpen() ? out : nullptr;
332
}
333
334
} // namespace dap
335
336