/**
* @file TCPClient.cpp
* @brief implementation of the TCP client class
* @author Mohamed Amine Mzoughi
*/
#include "TCPClient.h"
CTCPClient::CTCPClient(const LogFnCallback oLogger,
const SettingsFlag eSettings /*= ALL_FLAGS*/) :
ASocket(oLogger, eSettings),
m_eStatus(DISCONNECTED),
m_pResultAddrInfo(nullptr),
m_ConnectSocket(INVALID_SOCKET)
//m_uRetryCount(0),
//m_uRetryPeriod(0)
{
}
// Method for setting receive timeout. Can be called after Connect
bool CTCPClient::SetRcvTimeout(unsigned int msec_timeout) {
#ifndef WINDOWS
struct timeval t = ASocket::TimevalFromMsec(msec_timeout);
return this->SetRcvTimeout(t);
#else
int iErr;
// it's expecting an int but it doesn't matter...
iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&msec_timeout, sizeof(struct timeval));
if (iErr < 0) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPServer][Error] CTCPClient::SetRcvTimeout : Socket error in SO_RCVTIMEO call to setsockopt.");
return false;
}
return true;
#endif
}
#ifndef WINDOWS
bool CTCPClient::SetRcvTimeout(struct timeval timeout) {
int iErr;
iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*) &timeout, sizeof(struct timeval));
if (iErr < 0) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPServer][Error] CTCPClient::SetRcvTimeout : Socket error in SO_RCVTIMEO call to setsockopt.");
return false;
}
return true;
}
#endif
// Method for setting send timeout. Can be called after Connect
bool CTCPClient::SetSndTimeout(unsigned int msec_timeout) {
#ifndef WINDOWS
struct timeval t = ASocket::TimevalFromMsec(msec_timeout);
return this->SetSndTimeout(t);
#else
int iErr;
// it's expecting an int but it doesn't matter...
iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&msec_timeout, sizeof(struct timeval));
if (iErr < 0) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPServer][Error] CTCPClient::SetSndTimeout : Socket error in SO_SNDTIMEO call to setsockopt.");
return false;
}
return true;
#endif
}
#ifndef WINDOWS
bool CTCPClient::SetSndTimeout(struct timeval timeout) {
int iErr;
iErr = setsockopt(m_ConnectSocket, SOL_SOCKET, SO_SNDTIMEO, (char*) &timeout, sizeof(struct timeval));
if (iErr < 0) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPServer][Error] CTCPClient::SetSndTimeout : Socket error in SO_SNDTIMEO call to setsockopt.");
return false;
}
return true;
}
#endif
// Connexion au serveur
bool CTCPClient::Connect(const std::string& strServer, const std::string& strPort)
{
if (m_eStatus == CONNECTED)
{
Disconnect();
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPClient][Warning] Opening a new connexion. The last one was automatically closed.");
}
#ifdef WINDOWS
ZeroMemory(&m_HintsAddrInfo, sizeof(m_HintsAddrInfo));
/* AF_INET is used to specify the IPv4 address family. */
m_HintsAddrInfo.ai_family = AF_INET;
/* SOCK_STREAM is used to specify a stream socket. */
m_HintsAddrInfo.ai_socktype = SOCK_STREAM;
/* IPPROTO_TCP is used to specify the TCP protocol. */
m_HintsAddrInfo.ai_protocol = IPPROTO_TCP;
/* Resolve the server address and port */
int iResult = getaddrinfo(strServer.c_str(), strPort.c_str(), &m_HintsAddrInfo, &m_pResultAddrInfo);
if (iResult != 0)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat("[TCPClient][Error] getaddrinfo failed : %d", iResult));
if (m_pResultAddrInfo != nullptr)
{
freeaddrinfo(m_pResultAddrInfo);
m_pResultAddrInfo = nullptr;
}
return false;
}
// socket creation
m_ConnectSocket = socket(m_pResultAddrInfo->ai_family, // AF_INET
m_pResultAddrInfo->ai_socktype, // SOCK_STREAM
m_pResultAddrInfo->ai_protocol);// IPPROTO_TCP
if (m_ConnectSocket == INVALID_SOCKET)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat("[TCPClient][Error] socket failed : %d", WSAGetLastError()));
freeaddrinfo(m_pResultAddrInfo);
m_pResultAddrInfo = nullptr;
return false;
}
// Fixes windows 0.2 second delay sending (buffering) data.
int on = 1;
int iErr;
iErr = setsockopt(m_ConnectSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on));
if (iErr == INVALID_SOCKET)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPClient][Error] Socket error in call to setsockopt");
closesocket(m_ConnectSocket);
freeaddrinfo(m_pResultAddrInfo); m_pResultAddrInfo = nullptr;
return false;
}
/*
SOCKET ConnectSocket = INVALID_SOCKET;
struct sockaddr_in clientService;
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
// The sockaddr_in structure specifies the address family,
// IP address, and port of the server to be connected to.
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
clientService.sin_port = htons(27015);
*/
// connexion to the server
//unsigned uRetry = 0;
//do
//{
iResult = connect(m_ConnectSocket,
m_pResultAddrInfo->ai_addr,
static_cast(m_pResultAddrInfo->ai_addrlen));
//iResult = connect(m_ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
//if (iResult != SOCKET_ERROR)
//break;
// retry mechanism
//if (uRetry < m_uRetryCount)
//if (m_eSettingsFlags & ENABLE_LOG)
/*m_oLog(StringFormat("[TCPClient][Error] connect retry %u after %u second(s)",
m_uRetryCount + 1, m_uRetryPeriod));*/
//if (m_uRetryPeriod > 0)
//{
//for (unsigned uSec = 0; uSec < m_uRetryPeriod; uSec++)
//Sleep(1000);
//}
//} while (iResult == SOCKET_ERROR && ++uRetry < m_uRetryCount);
freeaddrinfo(m_pResultAddrInfo);
m_pResultAddrInfo = nullptr;
if (iResult != SOCKET_ERROR)
{
m_eStatus = CONNECTED;
return true;
}
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat("[TCPClient][Error] Unable to connect to server : %d", WSAGetLastError()));
#else
memset(&m_HintsAddrInfo, 0, sizeof m_HintsAddrInfo);
m_HintsAddrInfo.ai_family = AF_INET; // AF_INET or AF_INET6 to force version or use AF_UNSPEC
m_HintsAddrInfo.ai_socktype = SOCK_STREAM;
//m_HintsAddrInfo.ai_flags = 0;
//m_HintsAddrInfo.ai_protocol = 0; /* Any protocol */
int iAddrInfoRet = getaddrinfo(strServer.c_str(), strPort.c_str(), &m_HintsAddrInfo, &m_pResultAddrInfo);
if (iAddrInfoRet != 0)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat("[TCPClient][Error] getaddrinfo failed : %s", gai_strerror(iAddrInfoRet)));
if (m_pResultAddrInfo != nullptr)
{
freeaddrinfo(m_pResultAddrInfo);
m_pResultAddrInfo = nullptr;
}
return false;
}
/* getaddrinfo() returns a list of address structures.
* Try each address until we successfully connect(2).
* If socket(2) (or connect(2)) fails, we (close the socket
* and) try the next address. */
struct addrinfo* pResPtr = m_pResultAddrInfo;
for (pResPtr = m_pResultAddrInfo; pResPtr != nullptr; pResPtr = pResPtr->ai_next)
{
// create socket
m_ConnectSocket = socket(pResPtr->ai_family, pResPtr->ai_socktype, pResPtr->ai_protocol);
if (m_ConnectSocket < 0) // or == -1
continue;
// connexion to the server
int iConRet = connect(m_ConnectSocket, pResPtr->ai_addr, pResPtr->ai_addrlen);
if (iConRet >= 0) // or != -1
{
/* Success */
m_eStatus = CONNECTED;
if (m_pResultAddrInfo != nullptr)
{
freeaddrinfo(m_pResultAddrInfo);
m_pResultAddrInfo = nullptr;
}
return true;
}
close(m_ConnectSocket);
}
if (m_pResultAddrInfo != nullptr)
{
freeaddrinfo(m_pResultAddrInfo); /* No longer needed */
m_pResultAddrInfo = nullptr;
}
/* No address succeeded */
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPClient][Error] no such host.");
#endif
return false;
}
bool CTCPClient::Send(const char* pData, const size_t uSize) const
{
if (!pData || !uSize)
return false;
if (m_eStatus != CONNECTED)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPClient][Error] send failed : not connected to a server.");
return false;
}
int total = 0;
do
{
const int flags = 0;
int nSent;
nSent = send(m_ConnectSocket, pData + total, uSize - total, flags);
if (nSent < 0)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPClient][Error] Socket error in call to send.");
return false;
}
total += nSent;
} while(total < uSize);
return true;
}
bool CTCPClient::Send(const std::string& strData) const
{
return Send(strData.c_str(), strData.length());
}
bool CTCPClient::Send(const std::vector& Data) const
{
return Send(Data.data(), Data.size());
}
/* ret > 0 : bytes received
* ret == 0 : connection closed
* ret < 0 : recv failed
*/
int CTCPClient::Receive(char* pData, const size_t uSize, bool bReadFully /*= true*/) const
{
if (!pData || !uSize)
return -2;
if (m_eStatus != CONNECTED)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPClient][Error] recv failed : not connected to a server.");
return -1;
}
#ifdef WINDOWS
int tries = 0;
#endif
int total = 0;
do
{
int nRecvd = recv(m_ConnectSocket, pData + total, uSize - total, 0);
if (nRecvd == 0)
{
// peer shut down
break;
}
#ifdef WINDOWS
if ((nRecvd < 0) && (WSAGetLastError() == WSAENOBUFS))
{
// On long messages, Windows recv sometimes fails with WSAENOBUFS, but
// will work if you try again.
if ((tries++ < 1000))
{
Sleep(1);
continue;
}
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("[TCPClient][Error] Socket error in call to recv.");
break;
}
#endif
total += nRecvd;
} while (bReadFully && (total < uSize));
return total;
}
bool CTCPClient::Disconnect()
{
if (m_eStatus != CONNECTED)
return true;
m_eStatus = DISCONNECTED;
#ifdef WINDOWS
// shutdown the connection since no more data will be sent
int iResult = shutdown(m_ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
{
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat("[TCPClient][Error] shutdown failed : %d", WSAGetLastError()));
return false;
}
closesocket(m_ConnectSocket);
if (m_pResultAddrInfo != nullptr)
{
freeaddrinfo(m_pResultAddrInfo);
m_pResultAddrInfo = nullptr;
}
#else
close(m_ConnectSocket);
#endif
m_ConnectSocket = INVALID_SOCKET;
return true;
}
CTCPClient::~CTCPClient()
{
if (m_eStatus == CONNECTED)
Disconnect();
}