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