8
\$\begingroup\$

I am writing a C++ client application which will send some data to server and wait for its response. Now the protocol is to wait for a specific timeout and then retry for specific times. If all goes wrong, the client will report a communication failure.

I have implemented the whole matter in a non blocking socket operation. I have some doubts on whether my send/receive methods are correct or not.

Below is my code for TCP communication which is written in VC++ 2005 on Windows platform.

Socket parameters building method

bool CTCPCommunication::OpenConnection(bool bRetryConnect)
{
 //create the socket handle and config its paramaters
 m_hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 if (m_hSocket == INVALID_SOCKET)
 {
 WRITELOG("Call to API 'socket' failed, error: %d", WSAGetLastError());
 return false;
 }
 //setting socket address
 CT2CA serverIP(m_csServerIP);
 char* pchServerIP = serverIP;
 m_stAddress.sin_family = AF_INET;
 m_stAddress.sin_addr.S_un.S_addr = inet_addr(pchServerIP);
 m_stAddress.sin_port = htons(m_iServerPort);
 //setting socket timeout
 m_stTimeout.tv_sec = SOCK_TIMEOUT_SECONDS;
 m_stTimeout.tv_usec = 0;
 //set socket to non blocking mode
 unsigned long iMode = 1;
 int iResult = ioctlsocket(m_hSocket, FIONBIO, &iMode);
 if (iResult != NO_ERROR)
 {
 WRITELOG("Call to API 'ioctlsocket' failed, error: %d", WSAGetLastError());
 return false;
 }
 bool bSuccess = false;
 //Called for the first time when starting server connection
 if (bRetryConnect == false)
 {
 bSuccess = InitialConnect();
 }
 //For all the other time when client detects a failure in communication and makes a retry
 else
 {
 bSuccess = Connect();
 }
 return bSuccess;
}

Connection building method

bool CTCPCommunication::Connect()
{
 ReportStatus(App_Stat_Connect);
 CT2CA serverIP(m_csServerIP);
 char* pchserverIP = serverIP;
 WRITELOG("Connecting to server %s:%d", pchserverIP, m_iServerPort);
 //try to connect server 
 connect(m_hSocket, (struct sockaddr *)&m_stAddress, sizeof(m_stAddress));
 //check and wait for the socket to be ready with timeout
 fd_set fdWrite;
 FD_ZERO(&fdWrite);
 FD_SET(m_hSocket, &fdWrite);
 int iRet = select(0, NULL, &fdWrite, NULL, &m_stTimeout);
 //decide success or failure
 if((iRet > 0) && (FD_ISSET(m_hSocket, &fdWrite)))
 {
 return true;
 }
 return false;
}

Reinitiate connection for a retry

bool CTCPCommunication::RetryConnection()
{
 bool bSuccess = CloseConnection();
 if (bSuccess == false)
 {
 ReportStatus(App_Err_Retry);
 WRITELOG("Unabled to attempt retry as existing connection could not be closed, error: %d", WSAGetLastError());
 return bSuccess;
 }
 bSuccess = OpenConnection(true);
 return bSuccess;
}

Upload data to server

bool CTCPCommunication::UploadDataPacket(char* pchSendData, int iSendDataLen, MessageID eSendMessageID, CString csSendPacketGUID)
{
 bool bSuccess = false;
 m_iRetryCount = 0;
 while (m_iRetryCount <= MAX_RETRY)
 {
 // Pushing data packet to socket
 bSuccess = SendSocketData(pchSendData, iSendDataLen);
 if (bSuccess == true)
 {
 // Receive data from socket
 char chRecvBuff[MAX_RECV_LEN+1] = {0};
 bSuccess = ReceiveSocketData(chRecvBuff, MAX_RECV_LEN+1);
 // Verify response packet for proper GUID
 if (bSuccess == true)
 {
 CString csRecvBuff = CString(chRecvBuff);
 bSuccess = ValidateACK(eSendMessageID, csRecvBuff, csSendPacketGUID);
 if (bSuccess == true)
 {
 break;
 }
 }
 }
 if (bSuccess == false)
 {
 RetryConnection();
 m_iRetryCount++;
 if(m_iRetryCount <= MAX_RETRY)
 {
 ReportStatus(App_Stat_Retry, m_iRetryCount);
 WRITELOG("Attempting retry %d", m_iRetryCount);
 }
 }
 }
 return bSuccess;
}

Send socket data

bool CTCPCommunication::SendSocketData(char* pchData, int iBuffLen)
{
 bool bSuccess = true;
 while (iBuffLen > 0)
 {
 //check whether the socket is ready to write data
 fd_set fdWrite;
 FD_ZERO(&fdWrite);
 FD_SET(m_hSocket, &fdWrite);
 int iRet = select(0, NULL, &fdWrite, NULL, &m_stTimeout);
 if ((iRet > 0) && (FD_ISSET(m_hSocket, &fdWrite)))
 {
 int iSentLen = send(m_hSocket, pchData, iBuffLen, 0);
 //sending failed due to socket error
 if (iSentLen == SOCKET_ERROR)
 {
 WRITELOG("Call to socket API 'send' failed, error: %d", WSAGetLastError());
 bSuccess = false;
 break;
 }
 pchData += iSentLen;
 iBuffLen -= iSentLen;
 }
 else
 {
 WRITELOG("Call to socket API 'select' failed inside send method, error: %d", WSAGetLastError());
 bSuccess = false;
 break;
 }
 }
 return bSuccess;
}

Receive socket data

bool CTCPCommunication::ReceiveSocketData(char* pchBuff, int iBuffLen)
{
 bool bSuccess = true;
 while ((iBuffLen-1) > 0)
 {
 //check whether the socket is ready to read data
 fd_set fdRead;
 FD_ZERO(&fdRead);
 FD_SET(m_hSocket, &fdRead);
 int iRet = select(0, &fdRead, NULL, NULL, &m_stTimeout);
 if ((iRet > 0) && (FD_ISSET(m_hSocket, &fdRead)))
 {
 int iRcvdLen = recv(m_hSocket, pchBuff, iBuffLen-1, 0);
 //receive failed due to socket error
 if (iRcvdLen <= 0)
 {
 WRITELOG("Call to socket API 'recv' failed, error: %d", WSAGetLastError());
 bSuccess = false;
 break;
 }
 pchBuff += iRcvdLen;
 iBuffLen -= iRcvdLen;
 }
 else
 {
 WRITELOG("Call to socket API 'select' failed inside recv method, error: %d", WSAGetLastError());
 bSuccess = false;
 break;
 }
 }
 return bSuccess;
}
asked Apr 29, 2015 at 20:06
\$\endgroup\$
0

2 Answers 2

6
\$\begingroup\$

First, you should not compare values to boolean literals in an if statement. This is better off written as bSuccess instead of bSuccess == true and !bSuccess instead of bSuccess == false.

Second, you are modifying a local variable right here, which will go out of scope as soon as you leave the method:

else
{
 WRITELOG("Call to socket API 'select' failed inside recv method, error: %d", WSAGetLastError());
 bSuccess = false;
 break;
}

Right after this, you break out of your loop and return bSuccess;. This could be written to demonstrate you are immediately returning a fail signal like this:

else
{
 WRITELOG("Call to socket API 'select' failed inside recv method, error: %d", WSAGetLastError());
 return false;
}

Third, you have this snippet:

//decide success or failure
if((iRet > 0) && (FD_ISSET(m_hSocket, &fdWrite)))
{
 return true;
}
return false;

This can be written without ifs like this:

return (iRet > 0) && FD_ISSET(m_hSocket, &fdWrite);

The parenthesis around the first statement are not necessary, but might help readability.

answered Apr 29, 2015 at 20:09
\$\endgroup\$
6
  • \$\begingroup\$ Well, regarding your first and second point. Thats a microsoft convention used in most of the cases, so I do follow that. Third and Fourth points are just to make the code more readable in my opinions. \$\endgroup\$ Commented Apr 29, 2015 at 20:49
  • 4
    \$\begingroup\$ @hypheni Never compare against boolean literals. Ever. I'd be more okay seeing a GOTO statement than a comparison verse boolean literal. \$\endgroup\$ Commented Apr 29, 2015 at 20:54
  • \$\begingroup\$ @nhgrif: Whats the problem? if (var == true) is more readable than if (var) in IDE like Visual studio where you will find true, false in specific color. \$\endgroup\$ Commented Apr 29, 2015 at 20:59
  • \$\begingroup\$ @hypheni It isn't always about readability, it is also about correctness. \$\endgroup\$ Commented Apr 29, 2015 at 21:00
  • \$\begingroup\$ @Hosch250: Well, I agree, comparing with true is redudant. Anyways I will try to get rid of this habbit. Can you / anyone comment more on the socket send/recv part? \$\endgroup\$ Commented Apr 29, 2015 at 21:06
4
\$\begingroup\$

Testing that socket is selected for writability is not enough. Losely speaking it only means that send wouldn't block, and after a failed connect it indeed would return immediately (with errno set to ENOTCONN). A canonical way to test for success is to retrieve the completion error with

 int error;
 int len = sizeof(error);
 getsockopt(n_hSocket, SOL_SOCKET, SO_ERROR, &error, &len);

If error is 0 then connection is successful.

answered Apr 29, 2015 at 21:19
\$\endgroup\$
2
  • \$\begingroup\$ So thats for only connect method? \$\endgroup\$ Commented Apr 29, 2015 at 21:24
  • \$\begingroup\$ @hypheni So far, yes. More to come. \$\endgroup\$ Commented Apr 29, 2015 at 21:25

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.