Instrument Neutral Distributed Interface INDI 2.2.5
tcpsocket.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2022 by Pawel Soja <[email protected]>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17*/
18#include "tcpsocket.h"
19#include "tcpsocket_p.h"
20
21#include <errno.h>
22#include <chrono>
23#include <algorithm>
24#if !defined(_WIN32) && !defined(__CYGWIN__)
25#include <netinet/in.h>
26#endif
27
28// SocketAddress
29const char *SocketAddress::unixDomainPrefix = "localhost:";
30
31SocketAddress::SocketAddress(const std::string &hostName, unsigned short port)
32{
33 if (isUnix(hostName))
34 *this = SocketAddress::afUnix(hostName.substr(strlen(unixDomainPrefix)));
35 else
36 *this = SocketAddress::afInet(hostName, port);
37}
38
39SocketAddress SocketAddress::afInet(const std::string &hostName, unsigned short port)
40{
41 struct hostent *hp = gethostbyname(hostName.c_str());
42 if (hp == nullptr)
43 return SocketAddress();
44
45 if (hp->h_addr_list == nullptr)
46 return SocketAddress();
47
48 if (hp->h_addr_list[0] == nullptr)
49 return SocketAddress();
50
51 struct sockaddr_in *sa_in = new sockaddr_in;
52 (void)memset(sa_in, 0, sizeof(struct sockaddr_in));
53 sa_in->sin_family = AF_INET;
54 sa_in->sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
55 sa_in->sin_port = htons(port);
56
57 SocketAddress result;
58 result.mData.reset(reinterpret_cast<struct sockaddr*>(sa_in));
59 result.mSize = sizeof(struct sockaddr_in);
60 return result;
61}
62
63bool SocketAddress::isUnix(const std::string &hostName)
64{
65 return hostName.rfind(unixDomainPrefix, 0) == 0;
66}
67
68// TcpSocketPrivate
70 : parent(parent)
71{ }
72
74{
76 if (thread.joinable())
77 {
78 thread.join();
79 }
80}
81
82TcpSocket::TcpSocket(std::unique_ptr<TcpSocketPrivate> &&d)
83 : d_ptr(std::move(d))
84{ }
85
86ssize_t TcpSocketPrivate::write(const void *data, size_t size)
87{
88 ssize_t ret;
89 do
90 {
91 std::unique_lock<std::mutex> locker(socketStateMutex);
93 {
94 return 0;
95 }
96 ret = TcpSocketPrivate::sendSocket(data, size);
97 }
98 while (ret == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
99
100 if (ret < 0)
101 {
103 return 0;
104 }
105
106 return ret;
107}
108
109bool TcpSocketPrivate::connectSocket(const std::string &hostName, unsigned short port)
110{
111 // create socket handle
112 if (!createSocket(SocketAddress::isUnix(hostName) ? AF_UNIX : AF_INET))
113 {
115 return false;
116 }
117
118 // set non blocking mode
119 if (!setNonblockSocket())
120 {
122 return false;
123 }
124
125 // get socket address
126 auto sockAddr = SocketAddress(hostName, port);
127
128 if (!sockAddr.isValid())
129 {
131 return false;
132 }
133
134 // connect to host
135 if (::connect(socketFd, &sockAddr, int(sockAddr.size())) < 0 && errno != EINPROGRESS)
136 {
138 return false;
139 }
140
141 return true;
142}
143
145{
146 // wait for connect
147 select.clear();
150
151 if (select.isTimeout())
152 {
154 return false;
155 }
156
157 if (select.isWakedUp())
158 {
159 return false;
160 }
161
162 return TcpSocketPrivate::sendSocket("", 0) == 0; // final test, -1 if not connected
163}
164
166{
167 select.clear();
169#ifndef _WIN32
170 select.setTimeout(10 * 1000); // we can wake up
171#else
172 select.setTimeout(100);
173#endif
174
175 select.select();
176
177 // timeout, continue...
178 if (select.isTimeout())
179 {
180 return true;
181 }
182
183 // manual wakeup, maybe isAboutToClose, exit
184 if (select.isWakedUp())
185 {
186 return true;
187 }
188
189 // nothing to do
191 {
192 return true;
193 }
194
195 // call virtual method
196 parent->readyRead();
197
198 return true;
199}
200
201void TcpSocketPrivate::joinThread(std::thread &thread)
202{
203 std::unique_lock<std::mutex> locker(socketStateMutex);
204 isAboutToClose = true;
205 if (thread.joinable())
206 {
207 thread.join();
208 }
209 isAboutToClose = false;
210}
211
213{
214 typedef std::function<void()> F;
215 F f;
216 public:
217 Finally(const F &f) : f(f) { }
219 {
220 if (f) f();
221 }
222};
223
224void TcpSocketPrivate::connectToHost(const std::string &hostName, unsigned short port)
225{
227 {
229 return;
230 }
231
233
234 thread = std::thread([this, hostName, port] (std::thread && oldThread)
235 {
236 Finally finally([this]
237 {
238 closeSocket();
240 });
241
242 joinThread(oldThread); // join prevus thread if exists
243
244 // lookup and connect
245 if (!connectSocket(hostName, port))
246 {
247 // see error in connectSocket
248 return;
249 }
250 // wait for connected
253 {
255 return;
256 }
257
259 parent->connected();
260 while (isAboutToClose == false && processSocket());
262
263 }, std::move(thread));
264}
265
267{
268 std::unique_lock<std::mutex> locker(socketStateMutex);
269 if (socketState == TcpSocket::UnconnectedState || isAboutToClose.exchange(true) == true)
270 {
271 return;
272 }
273 select.wakeUp();
274}
275
277{
278 if (socketFd == SocketInvalid)
279 return;
280
281#ifdef _WIN32
282 ::closesocket(socketFd);
283#else
284 ::close(socketFd);
285#endif
286 socketFd = SocketInvalid;
287}
288
289void TcpSocketPrivate::setSocketError(TcpSocket::SocketError error, ErrorType errorType, const std::string &errorString)
290{
291 if (errorType == ErrorTypeSystem && errorString == "")
292 {
293#ifdef _WIN32
294 LPTSTR s = nullptr;
295 auto code = WSAGetLastError();
296 FormatMessage(
297 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
298 nullptr, code,
299 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
300 (LPTSTR)&s, 0, nullptr
301 );
302
303#ifdef UNICODE
304 std::wstring ws(s);
305 this->errorString = std::string(ws.begin(), ws.end());
306#else
307 this->errorString = s;
308#endif
309 LocalFree(s);
310
311 this->errorString += " (" + std::to_string(code) + ")";
312
313#else
314 this->errorString = strerror(errno);
315 this->errorString += " (" + std::to_string(errno) + ")";
316#endif
317 }
318 else
319 {
320 this->errorString = errorString;
321 }
322 socketError = error;
323 isAboutToClose = true;
324 parent->errorOccurred(error);
325}
326
328{
329 std::unique_lock<std::mutex> locker(socketStateMutex);
330 if (socketState.exchange(state) == state)
331 return;
332 socketStateChanged.notify_all();
333}
334
335// TcpSocket
337 : d_ptr(new TcpSocketPrivate(this))
338{ }
339
341{
344 {
345 d_ptr->joinThread(d_ptr->thread);
346 }
347}
348
350{
351 d_ptr->timeout = timeout;
352}
353
354void TcpSocket::connectToHost(const std::string &hostName, uint16_t port)
355{
356 d_ptr->connectToHost(hostName, port);
357}
358
360{
361 d_ptr->aboutToClose();
362}
363
364ssize_t TcpSocket::write(const char *data, size_t size)
365{
366 return d_ptr->write(data, size);
367}
368
369ssize_t TcpSocket::write(const std::string &data)
370{
371 return write(data.data(), data.size());
372}
373
374static std::string sSocketErrorToString(TcpSocket::SocketError error)
375{
376 switch (error)
377 {
379 return "ConnectionRefusedError";
381 return "RemoteHostClosedError";
383 return "HostNotFoundError";
385 return "SocketAccessError";
387 return "SocketResourceError";
389 return "SocketTimeoutError";
391 return "DatagramTooLargeError";
393 return "NetworkError";
395 return "AddressInUseError";
397 return "SocketAddressNotAvailableError";
399 return "UnsupportedSocketOperationError";
401 return "UnfinishedSocketOperationError";
403 return "ProxyAuthenticationRequiredError";
405 return "SslHandshakeFailedError";
407 return "ProxyConnectionRefusedError";
409 return "ProxyConnectionClosedError";
411 return "ProxyConnectionTimeoutError";
413 return "ProxyNotFoundError";
415 return "ProxyProtocolError";
417 return "OperationError";
419 return "SslInternalError";
421 return "SslInvalidUserDataError";
423 return "TemporaryError";
425 return "UnknownSocketError";
426 }
427 return "UnknownSocketError";
428}
429
431{
432 return d_ptr->socketError;
433}
434
435std::string TcpSocket::errorString() const
436{
437 return sSocketErrorToString(d_ptr->socketError) + ": " + d_ptr->errorString;
438}
439
440void TcpSocket::onConnected(const std::function<void()> &callback)
441{
442 d_ptr->onConnected = callback;
443}
444
445void TcpSocket::onDisconnected(const std::function<void()> &callback)
446{
447 d_ptr->onDisconnected = callback;
448}
449
450void TcpSocket::onData(const std::function<void(const char *, size_t)> &callback)
451{
452 d_ptr->onData = callback;
453}
454
455void TcpSocket::onErrorOccurred(const std::function<void(SocketError)> &callback)
456{
457 d_ptr->onErrorOccurred = callback;
458}
459
460bool TcpSocket::waitForConnected(int timeout) const
461{
462 if (d_ptr->thread.get_id() == std::this_thread::get_id())
463 {
465 return false;
466 }
467
468 std::unique_lock<std::mutex> locker(d_ptr->socketStateMutex);
469 d_ptr->socketStateChanged.wait_for(locker, std::chrono::milliseconds(timeout), [this]
470 {
471 return d_ptr->socketState == TcpSocket::ConnectedState || d_ptr->socketState == TcpSocket::UnconnectedState;
472 });
473 return d_ptr->socketState == TcpSocket::ConnectedState;
474}
475
476bool TcpSocket::waitForDisconnected(int timeout) const
477{
478 if (d_ptr->thread.get_id() == std::this_thread::get_id())
479 {
481 return false;
482 }
483
484 std::unique_lock<std::mutex> locker(d_ptr->socketStateMutex);
485 return d_ptr->socketStateChanged.wait_for(locker, std::chrono::milliseconds(timeout), [this]
486 {
487 return d_ptr->socketState == TcpSocket::UnconnectedState;
488 });
489
490}
491
493{
494 return reinterpret_cast<int *>(d_ptr->socketFd);
495}
496
498{
500}
501
506
508{
509 char data[65536];
510 ssize_t size = d_ptr->recvSocket(data, sizeof(data));
511
512 if (size <= 0)
513 {
515 return;
516 }
517
518 emitData(data, size);
519}
520
525
527{
528 if (d_ptr->onConnected)
529 d_ptr->onConnected();
530}
531
533{
534 if (d_ptr->onDisconnected)
535 d_ptr->onDisconnected();
536}
537
538void TcpSocket::emitData(const char *data, size_t size) const
539{
540 if (d_ptr->onData)
541 d_ptr->onData(data, size);
542}
543
545{
546 if (d_ptr->onErrorOccurred)
547 d_ptr->onErrorOccurred(error);
548}
549
551{
552 d_ptr->setSocketError(socketError);
553}
Finally(const F &f)
bool isWakedUp() const
Definition select.h:187
void setReadEvent(SocketFileDescriptor fd)
Definition select.h:152
void setReadWriteExceptionEvent(SocketFileDescriptor fd)
Definition select.h:177
void select()
Definition select.h:131
void setTimeout(int timeout)
Definition select.h:125
bool isTimeout() const
Definition select.h:197
void wakeUp()
Definition select.h:106
void clear()
Definition select.h:113
bool isReadEvent(SocketFileDescriptor fd) const
Definition select.h:205
std::unique_ptr< struct sockaddr > mData
Definition tcpsocket_p.h:73
SocketAddress()=default
static bool isUnix(const std::string &hostName)
Definition tcpsocket.cpp:63
static const char * unixDomainPrefix
Definition tcpsocket_p.h:37
static SocketAddress afUnix(const std::string &hostName)
static SocketAddress afInet(const std::string &hostName, unsigned short port)
Definition tcpsocket.cpp:39
bool createSocket(int domain)
SocketFileDescriptor socketFd
std::thread thread
std::string errorString
TcpSocketPrivate(TcpSocket *parent)
Definition tcpsocket.cpp:69
std::atomic< TcpSocket::SocketState > socketState
ssize_t sendSocket(const void *src, size_t size)
std::condition_variable socketStateChanged
bool waitForConnectedSockets()
virtual ~TcpSocketPrivate()
Definition tcpsocket.cpp:73
TcpSocket::SocketError socketError
TcpSocket * parent
bool connectSocket(const std::string &hostName, unsigned short port)
void connectToHost(const std::string &hostName, unsigned short port)
void setSocketState(TcpSocket::SocketState state)
ssize_t write(const void *data, size_t size)
Definition tcpsocket.cpp:86
void setSocketError(TcpSocket::SocketError error, ErrorType errorType=ErrorTypeSystem, const std::string &errorString="")
std::mutex socketStateMutex
void joinThread(std::thread &thread)
std::atomic< bool > isAboutToClose
std::unique_ptr< TcpSocketPrivate > d_ptr
Definition tcpsocket.h:120
void onData(const std::function< void(const char *, size_t)> &callback)
bool waitForDisconnected(int timeout=2000) const
virtual void disconnected()
std::string errorString() const
@ ConnectedState
Definition tcpsocket.h:65
@ ConnectingState
Definition tcpsocket.h:64
@ HostLookupState
Definition tcpsocket.h:63
@ UnconnectedState
Definition tcpsocket.h:62
void emitConnected() const
void setConnectionTimeout(int timeout)
void connectToHost(const std::string &hostName, uint16_t port)
virtual ~TcpSocket()
@ ProxyNotFoundError
Definition tcpsocket.h:50
@ SslHandshakeFailedError
Definition tcpsocket.h:46
@ ProxyConnectionRefusedError
Definition tcpsocket.h:47
@ TemporaryError
Definition tcpsocket.h:55
@ ProxyConnectionClosedError
Definition tcpsocket.h:48
@ SslInternalError
Definition tcpsocket.h:53
@ RemoteHostClosedError
Definition tcpsocket.h:34
@ UnfinishedSocketOperationError
Definition tcpsocket.h:44
@ OperationError
Definition tcpsocket.h:52
@ ConnectionRefusedError
Definition tcpsocket.h:33
@ AddressInUseError
Definition tcpsocket.h:41
@ SocketAddressNotAvailableError
Definition tcpsocket.h:42
@ SslInvalidUserDataError
Definition tcpsocket.h:54
@ SocketAccessError
Definition tcpsocket.h:36
@ ProxyProtocolError
Definition tcpsocket.h:51
@ NetworkError
Definition tcpsocket.h:40
@ UnsupportedSocketOperationError
Definition tcpsocket.h:43
@ ProxyAuthenticationRequiredError
Definition tcpsocket.h:45
@ SocketTimeoutError
Definition tcpsocket.h:38
@ ProxyConnectionTimeoutError
Definition tcpsocket.h:49
@ SocketResourceError
Definition tcpsocket.h:37
@ HostNotFoundError
Definition tcpsocket.h:35
@ UnknownSocketError
Definition tcpsocket.h:57
@ DatagramTooLargeError
Definition tcpsocket.h:39
void emitErrorOccurred(SocketError error) const
void emitDisconnected() const
virtual void connected()
SocketError error() const
void emitData(const char *data, size_t size) const
void setSocketError(SocketError socketError)
int * socketDescriptor() const
void onErrorOccurred(const std::function< void(SocketError)> &callback)
void disconnectFromHost()
ssize_t write(const char *data, size_t size)
void onDisconnected(const std::function< void()> &callback)
bool waitForConnected(int timeout=2000) const
virtual void errorOccurred(SocketError)
virtual void readyRead()
void onConnected(const std::function< void()> &callback)
int errno