1 /**
2 * @file TCPClient.cpp
3 * @brief implementation of the TCP client class
4 * @author Mohamed Amine Mzoughi <mohamed-amine.mzoughi@laposte.net>
5 */
6
7 #include "TCPClient.h"
8
CTCPClient(const LogFnCallback oLogger,const SettingsFlag eSettings)9 CTCPClient::CTCPClient(const LogFnCallback oLogger,
10 const SettingsFlag eSettings /*= ALL_FLAGS*/) :
11 ASocket(oLogger, eSettings),
12 m_eStatus(DISCONNECTED),
13 m_ConnectSocket(INVALID_SOCKET),
14 m_pResultAddrInfo(nullptr)
15 //m_uRetryCount(0),
16 //m_uRetryPeriod(0)
17 {
18
19 }
20
21 // Method for setting receive timeout. Can be called after Connect
SetRcvTimeout(unsigned int msec_timeout)22 bool CTCPClient::SetRcvTimeout(unsigned int msec_timeout) {
23 #ifndef WINDOWS
24 struct timeval t = ASocket::TimevalFromMsec(msec_timeout);
25
26 return this->SetRcvTimeout(t);
27 #else
28 int iErr;
29
30 // it's expecting an int but it doesn't matter...
31 iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&msec_timeout, sizeof(struct timeval));
32 if (iErr < 0) {
33 if (m_eSettingsFlags & ENABLE_LOG)
34 m_oLog("[TCPServer][Error] CTCPClient::SetRcvTimeout : Socket error in SO_RCVTIMEO call to setsockopt.");
35
36 return false;
37 }
38
39 return true;
40 #endif
41 }
42
43 #ifndef WINDOWS
SetRcvTimeout(struct timeval timeout)44 bool CTCPClient::SetRcvTimeout(struct timeval timeout) {
45 int iErr;
46
47 iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*) &timeout, sizeof(struct timeval));
48 if (iErr < 0) {
49 if (m_eSettingsFlags & ENABLE_LOG)
50 m_oLog("[TCPServer][Error] CTCPClient::SetRcvTimeout : Socket error in SO_RCVTIMEO call to setsockopt.");
51
52 return false;
53 }
54
55 return true;
56 }
57 #endif
58
59 // Method for setting send timeout. Can be called after Connect
SetSndTimeout(unsigned int msec_timeout)60 bool CTCPClient::SetSndTimeout(unsigned int msec_timeout) {
61 #ifndef WINDOWS
62 struct timeval t = ASocket::TimevalFromMsec(msec_timeout);
63
64 return this->SetSndTimeout(t);
65 #else
66 int iErr;
67
68 // it's expecting an int but it doesn't matter...
69 iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&msec_timeout, sizeof(struct timeval));
70 if (iErr < 0) {
71 if (m_eSettingsFlags & ENABLE_LOG)
72 m_oLog("[TCPServer][Error] CTCPClient::SetSndTimeout : Socket error in SO_SNDTIMEO call to setsockopt.");
73
74 return false;
75 }
76
77 return true;
78 #endif
79 }
80
81 #ifndef WINDOWS
SetSndTimeout(struct timeval timeout)82 bool CTCPClient::SetSndTimeout(struct timeval timeout) {
83 int iErr;
84
85 iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_SNDTIMEO, (char*) &timeout, sizeof(struct timeval));
86 if (iErr < 0) {
87 if (m_eSettingsFlags & ENABLE_LOG)
88 m_oLog("[TCPServer][Error] CTCPClient::SetSndTimeout : Socket error in SO_SNDTIMEO call to setsockopt.");
89
90 return false;
91 }
92
93 return true;
94 }
95 #endif
96
97 // Connexion au serveur
Connect(const std::string & strServer,const std::string & strPort)98 bool CTCPClient::Connect(const std::string& strServer, const std::string& strPort)
99 {
100 if (m_eStatus == CONNECTED)
101 {
102 Disconnect();
103 if (m_eSettingsFlags & ENABLE_LOG)
104 m_oLog("[TCPClient][Warning] Opening a new connexion. The last one was automatically closed.");
105 }
106
107 #ifdef WINDOWS
108 ZeroMemory(&m_HintsAddrInfo, sizeof(m_HintsAddrInfo));
109 /* AF_INET is used to specify the IPv4 address family. */
110 m_HintsAddrInfo.ai_family = AF_INET;
111 /* SOCK_STREAM is used to specify a stream socket. */
112 m_HintsAddrInfo.ai_socktype = SOCK_STREAM;
113 /* IPPROTO_TCP is used to specify the TCP protocol. */
114 m_HintsAddrInfo.ai_protocol = IPPROTO_TCP;
115
116 /* Resolve the server address and port */
117 int iResult = getaddrinfo(strServer.c_str(), strPort.c_str(), &m_HintsAddrInfo, &m_pResultAddrInfo);
118 if (iResult != 0)
119 {
120 if (m_eSettingsFlags & ENABLE_LOG)
121 m_oLog(StringFormat("[TCPClient][Error] getaddrinfo failed : %d", iResult));
122
123 if (m_pResultAddrInfo != nullptr)
124 {
125 freeaddrinfo(m_pResultAddrInfo);
126 m_pResultAddrInfo = nullptr;
127 }
128
129 return false;
130 }
131
132 // socket creation
133 m_ConnectSocket = socket(m_pResultAddrInfo->ai_family, // AF_INET
134 m_pResultAddrInfo->ai_socktype, // SOCK_STREAM
135 m_pResultAddrInfo->ai_protocol);// IPPROTO_TCP
136
137 if (m_ConnectSocket == INVALID_SOCKET)
138 {
139 if (m_eSettingsFlags & ENABLE_LOG)
140 m_oLog(StringFormat("[TCPClient][Error] socket failed : %d", WSAGetLastError()));
141
142 freeaddrinfo(m_pResultAddrInfo);
143 m_pResultAddrInfo = nullptr;
144 return false;
145 }
146
147 // Fixes windows 0.2 second delay sending (buffering) data.
148 int on = 1;
149 int iErr;
150
151 iErr = setsockopt(m_ConnectSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on));
152 if (iErr == INVALID_SOCKET)
153 {
154 if (m_eSettingsFlags & ENABLE_LOG)
155 m_oLog("[TCPClient][Error] Socket error in call to setsockopt");
156
157 closesocket(m_ConnectSocket);
158 freeaddrinfo(m_pResultAddrInfo); m_pResultAddrInfo = nullptr;
159
160 return false;
161 }
162
163 /*
164 SOCKET ConnectSocket = INVALID_SOCKET;
165 struct sockaddr_in clientService;
166
167 ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
168 if (ConnectSocket == INVALID_SOCKET) {
169 printf("Error at socket(): %ld\n", WSAGetLastError());
170 WSACleanup();
171 return 1;
172 }
173
174 // The sockaddr_in structure specifies the address family,
175 // IP address, and port of the server to be connected to.
176 clientService.sin_family = AF_INET;
177 clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
178 clientService.sin_port = htons(27015);
179 */
180
181 // connexion to the server
182 //unsigned uRetry = 0;
183 //do
184 //{
185 iResult = connect(m_ConnectSocket,
186 m_pResultAddrInfo->ai_addr,
187 static_cast<int>(m_pResultAddrInfo->ai_addrlen));
188 //iResult = connect(m_ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
189
190 //if (iResult != SOCKET_ERROR)
191 //break;
192
193 // retry mechanism
194 //if (uRetry < m_uRetryCount)
195 //if (m_eSettingsFlags & ENABLE_LOG)
196 /*m_oLog(StringFormat("[TCPClient][Error] connect retry %u after %u second(s)",
197 m_uRetryCount + 1, m_uRetryPeriod));*/
198
199 //if (m_uRetryPeriod > 0)
200 //{
201 //for (unsigned uSec = 0; uSec < m_uRetryPeriod; uSec++)
202 //Sleep(1000);
203 //}
204 //} while (iResult == SOCKET_ERROR && ++uRetry < m_uRetryCount);
205
206 freeaddrinfo(m_pResultAddrInfo);
207 m_pResultAddrInfo = nullptr;
208
209 if (iResult != SOCKET_ERROR)
210 {
211 m_eStatus = CONNECTED;
212 return true;
213 }
214 if (m_eSettingsFlags & ENABLE_LOG)
215 m_oLog(StringFormat("[TCPClient][Error] Unable to connect to server : %d", WSAGetLastError()));
216
217 #else
218 memset(&m_HintsAddrInfo, 0, sizeof m_HintsAddrInfo);
219 m_HintsAddrInfo.ai_family = AF_INET; // AF_INET or AF_INET6 to force version or use AF_UNSPEC
220 m_HintsAddrInfo.ai_socktype = SOCK_STREAM;
221 //m_HintsAddrInfo.ai_flags = 0;
222 //m_HintsAddrInfo.ai_protocol = 0; /* Any protocol */
223
224 int iAddrInfoRet = getaddrinfo(strServer.c_str(), strPort.c_str(), &m_HintsAddrInfo, &m_pResultAddrInfo);
225 if (iAddrInfoRet != 0)
226 {
227 if (m_eSettingsFlags & ENABLE_LOG)
228 m_oLog(StringFormat("[TCPClient][Error] getaddrinfo failed : %s", gai_strerror(iAddrInfoRet)));
229
230 if (m_pResultAddrInfo != nullptr)
231 {
232 freeaddrinfo(m_pResultAddrInfo);
233 m_pResultAddrInfo = nullptr;
234 }
235
236 return false;
237 }
238
239 /* getaddrinfo() returns a list of address structures.
240 * Try each address until we successfully connect(2).
241 * If socket(2) (or connect(2)) fails, we (close the socket
242 * and) try the next address. */
243 struct addrinfo* pResPtr = m_pResultAddrInfo;
244 for (pResPtr = m_pResultAddrInfo; pResPtr != nullptr; pResPtr = pResPtr->ai_next)
245 {
246 // create socket
247 m_ConnectSocket = socket(pResPtr->ai_family, pResPtr->ai_socktype, pResPtr->ai_protocol);
248 if (m_ConnectSocket < 0) // or == -1
249 continue;
250
251 // connexion to the server
252 int iConRet = connect(m_ConnectSocket, pResPtr->ai_addr, pResPtr->ai_addrlen);
253 if (iConRet >= 0) // or != -1
254 {
255 /* Success */
256 m_eStatus = CONNECTED;
257
258 if (m_pResultAddrInfo != nullptr)
259 {
260 freeaddrinfo(m_pResultAddrInfo);
261 m_pResultAddrInfo = nullptr;
262 }
263
264 return true;
265 }
266
267 close(m_ConnectSocket);
268 }
269
270 if (m_pResultAddrInfo != nullptr)
271 {
272 freeaddrinfo(m_pResultAddrInfo); /* No longer needed */
273 m_pResultAddrInfo = nullptr;
274 }
275
276 /* No address succeeded */
277 if (m_eSettingsFlags & ENABLE_LOG)
278 m_oLog("[TCPClient][Error] no such host.");
279
280 #endif
281
282 return false;
283 }
284
Send(const char * pData,const size_t uSize) const285 bool CTCPClient::Send(const char* pData, const size_t uSize) const
286 {
287 if (!pData || !uSize)
288 return false;
289
290 if (m_eStatus != CONNECTED)
291 {
292 if (m_eSettingsFlags & ENABLE_LOG)
293 m_oLog("[TCPClient][Error] send failed : not connected to a server.");
294
295 return false;
296 }
297
298 size_t total = 0;
299 do
300 {
301 const int flags = 0;
302 int nSent;
303
304 nSent = send(m_ConnectSocket, pData + total, uSize - total, flags);
305
306 if (nSent < 0)
307 {
308 if (m_eSettingsFlags & ENABLE_LOG)
309 m_oLog("[TCPClient][Error] Socket error in call to send.");
310
311 return false;
312 }
313 total += nSent;
314 } while(total < uSize);
315
316 return true;
317 }
318
Send(const std::string & strData) const319 bool CTCPClient::Send(const std::string& strData) const
320 {
321 return Send(strData.c_str(), strData.length());
322 }
323
Send(const std::vector<char> & Data) const324 bool CTCPClient::Send(const std::vector<char>& Data) const
325 {
326 return Send(Data.data(), Data.size());
327 }
328
329 /* ret > 0 : bytes received
330 * ret == 0 : connection closed
331 * ret < 0 : recv failed
332 */
Receive(char * pData,const size_t uSize,bool bReadFully) const333 int CTCPClient::Receive(char* pData, const size_t uSize, bool bReadFully /*= true*/) const
334 {
335 if (!pData || !uSize)
336 return -2;
337
338 if (m_eStatus != CONNECTED)
339 {
340 if (m_eSettingsFlags & ENABLE_LOG)
341 m_oLog("[TCPClient][Error] recv failed : not connected to a server.");
342
343 return -1;
344 }
345
346 #ifdef WINDOWS
347 int tries = 0;
348 #endif
349
350 size_t total = 0;
351 do
352 {
353 int nRecvd = recv(m_ConnectSocket, pData + total, uSize - total, 0);
354
355 if (nRecvd == 0)
356 {
357 // peer shut down
358 break;
359 }
360
361 #ifdef WINDOWS
362 if ((nRecvd < 0) && (WSAGetLastError() == WSAENOBUFS))
363 {
364 // On long messages, Windows recv sometimes fails with WSAENOBUFS, but
365 // will work if you try again.
366 if ((tries++ < 1000))
367 {
368 Sleep(1);
369 continue;
370 }
371
372 if (m_eSettingsFlags & ENABLE_LOG)
373 m_oLog("[TCPClient][Error] Socket error in call to recv.");
374
375 break;
376 }
377 #endif
378
379 total += nRecvd;
380
381 } while (bReadFully && (total < uSize));
382
383 return total;
384 }
385
Disconnect()386 bool CTCPClient::Disconnect()
387 {
388 if (m_eStatus != CONNECTED)
389 return true;
390
391 m_eStatus = DISCONNECTED;
392
393 #ifdef WINDOWS
394 // shutdown the connection since no more data will be sent
395 int iResult = shutdown(m_ConnectSocket, SD_SEND);
396 if (iResult == SOCKET_ERROR)
397 {
398 if (m_eSettingsFlags & ENABLE_LOG)
399 m_oLog(StringFormat("[TCPClient][Error] shutdown failed : %d", WSAGetLastError()));
400
401 return false;
402 }
403 closesocket(m_ConnectSocket);
404
405 if (m_pResultAddrInfo != nullptr)
406 {
407 freeaddrinfo(m_pResultAddrInfo);
408 m_pResultAddrInfo = nullptr;
409 }
410 #else
411 close(m_ConnectSocket);
412 #endif
413
414 m_ConnectSocket = INVALID_SOCKET;
415
416 return true;
417 }
418
~CTCPClient()419 CTCPClient::~CTCPClient()
420 {
421 if (m_eStatus == CONNECTED)
422 Disconnect();
423 }
424