C++/Winsock TCP send/recv issue between client/server

csocketstcp

I have a problem related with client/server communication via socket when I do consecutive send and after that a recv on client. Example:

Case A:

Client                Server
send(...);----------->While(recv(...)>0){
send(...);-----------> print(message);
send(...);----------->}
recv(...);----------->Send(...); 

The server receives the 3 messages and send the last answer, but the recv on client failed with SOCKET_ERROR with WSAGetLastError() value of 10060.
The only way to make this case to work is when I add a shutdown(…,SD_SEND) after the last send on client.

Why the case A have this behaviour? and why it works only when I add the shutdown() command?

But if I do:

Case B:

Client                Server
send(...);----------->While(recv(...)>0){
recv(...);-----------> send(...);
send(...);-----------> ...
recv(...);-----------> ...
send(...);-----------> ...
recv(...);----------->}

It works fine, server/client receives and send each message.

Here is the code for case A:
Client:

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")

#define DEFAULT_BUFLEN 1024
#define DEFAULT_PORT "27015"

int main() {
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
ADDRINFOA *ptr = NULL, *result = NULL, hints;
char *ans, *sendbuf = "message\0";
char recvbuf[1024];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
ans=new char[1024];

// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
printf("--> Initializing Winsock...\n*** Version: %s\n", wsaData.szDescription);
if (iResult != 0) {
    printf("*** Could not initialize Socket.\n*** Error code: %d", iResult);
    return 1;
}

ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;

// Resolve the server address and port
iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result);
printf("--> Setting server address...\n");
printf("--> local ip: 127.0.0.1 at port: %s...\n",DEFAULT_PORT);
if ( iResult != 0 ) {
    printf("*** Error in setting server address.\n*** Error code: %d", iResult);
    WSACleanup();
    return 1;
}

// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {
    // Create a SOCKET for connecting to server
    printf("--> Creating client socket object...\n");
    ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
    if (ConnectSocket == INVALID_SOCKET) {
        printf("*** Error creating socket.\n*** Error code: %d\n",WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    if(setsockopt(ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)new int(1000), sizeof(int))){
        WSACleanup();
        //strcpy(recvbuf, "EX_95");
        return -5;    //    Error setting recv timeout.
    }
    // Connect to server.
    iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        closesocket(ConnectSocket);
        ConnectSocket = INVALID_SOCKET;
        continue;
    }
    printf("*** Client ready *** \n\n");
    break;
}

freeaddrinfo(result);

if (ConnectSocket == INVALID_SOCKET) {
    printf("*** Unable to connect to server!\n");
    WSACleanup();
    return 1;
}

// Send an initial buffer
iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
if (iResult == SOCKET_ERROR) {
    printf("*** Send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("<-- Bytes Sent: %ld\n", iResult);

    iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
if (iResult == SOCKET_ERROR) {
    printf("*** Send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("<-- Bytes Sent: %ld\n", iResult);


iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
if (iResult == SOCKET_ERROR) {
    printf("*** Send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("<-- Bytes Sent: %ld\n", iResult);

// shutdown the connection since no more data will be sent
/*iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed with error: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}*/

iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if ( iResult > 0 )
    printf("Bytes received: %d\n", iResult);
else if ( iResult == 0 )
    printf("Connection closed\n");
else
    printf("recv failed with error: %d\n", WSAGetLastError());


// cleanup
closesocket(ConnectSocket);
WSACleanup();
system("pause");
return 0;
}

Server:

// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#include <iostream>
#include <Winsock2.h>
#include <Ws2tcpip.h>
#include <string>

#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")

const char* DEFAULT_PORT = "27015";
const int DEFAULT_BUFLEN = 1024;

using namespace std;

int main(){
//Connection Variables.
WSADATA wsaData;
ADDRINFOA *result = NULL, hints;
SOCKADDR *clientInfo = NULL;
int iResult;
//Receive Variables.
char recvBuff[DEFAULT_BUFLEN];
int recvBuffLen = DEFAULT_BUFLEN;
int iSendResult;
//Server/Client sockets.
SOCKET ListenSocket = INVALID_SOCKET; //SOCKET for the server to listen for client connections.
SOCKET ClientSocket = INVALID_SOCKET;

//Initialize Winsock
iResult = WSAStartup( MAKEWORD( 2, 2 ), &wsaData );
printf("--> Initializing Winsock...\n*** Version: %s\n", wsaData.szDescription);
if ( iResult != 0 ){
    printf("*** Could not initialize Socket.\n*** Error code: %d", iResult);
    return 1;
}
//Initialize hints allocated memory.
ZeroMemory( &hints, sizeof( hints ) );

hints.ai_family   = AF_INET;     //AF_INET is used to specify the IPv4 address family.
hints.ai_socktype = SOCK_STREAM; //SOCK_STREAM is used to specify a stream socket.
hints.ai_protocol = IPPROTO_TCP; //IPPROTO_TCP is used to specify the TCP protocol.
hints.ai_flags    = AI_PASSIVE;  //AI_PASSIVE The socket address will be used in a call to the bind function.   

printf("--> Getting address info from server...\n");
iResult = getaddrinfo( NULL, DEFAULT_PORT, &hints, &result );
if( iResult != 0 ){
    printf("*** Error in getting address info from server.\n*** Error code: %d", iResult);
    WSACleanup();
    return 2;
}

printf("--> Creating server socket object...\n");
//printf("Create socket to ip: %s at port: %s\n", inet_ntoa(((SOCKADDR_IN*)(result->ai_addr))->sin_addr),DEFAULT_PORT);
ListenSocket = socket( result->ai_family, result->ai_socktype, result->ai_protocol );
if( ListenSocket == INVALID_SOCKET ){
     printf("*** Error creating socket.\n*** Error code: %d\n",WSAGetLastError());
    freeaddrinfo( result );
    WSACleanup();
    return 3;
}

//BIND()->Associates a local address to a socket.
printf("--> Bind listen object to local ip: 127.0.0.1 at port: %s...\n",DEFAULT_PORT);
iResult = bind( ListenSocket, result->ai_addr, result->ai_addrlen );
if( iResult == SOCKET_ERROR ){
    printf("*** Binding failed.\n*** Error code: %d\n", WSAGetLastError());
    closesocket( ListenSocket );
    freeaddrinfo( result );
    WSACleanup();
    return 4;
}
freeaddrinfo(result);

printf("*** Server ONLINE: Listening...\n\n");
iResult = listen( ListenSocket, SOMAXCONN );
if( iResult == SOCKET_ERROR ){
    printf("*** Failed start listening.\n*** Error code: %d\n",WSAGetLastError());
    closesocket( ListenSocket );
    freeaddrinfo( result );
    WSACleanup();
    return 5;
}

for(;1;){   
   ClientSocket = accept( ListenSocket,clientInfo, NULL );
   if( ClientSocket == INVALID_SOCKET ){
        printf("*** Failed accepting connection from client.\n*** Error code: %d\n",WSAGetLastError());
        continue;
   }
   else{
        printf("--> Connection accepted from client.\n");
        iResult=1;
        while(iResult > 0){
            iResult = recv( ClientSocket, recvBuff, recvBuffLen, 0 );
            if( iResult > 0 ){
                printf("--> Message received: %s\n--> Total: %d\n", recvBuff, iResult);
            }
        }
        iSendResult = send( ClientSocket, "Answer\0", DEFAULT_BUFLEN, 0 );
        if( iSendResult == SOCKET_ERROR ){
            printf("*** Sending data failed.\n*** Error code: %d\n",WSAGetLastError());
            continue;
        }
        else{
            printf("--> Sent: %d bytes\n", iSendResult);
        }
        printf("*** Closing connection... \n\n");
        iResult = shutdown( ClientSocket, SD_BOTH );
        if( iResult == SOCKET_ERROR ){
            printf("*** Shutdown client failed.\nError code: %d\n",WSAGetLastError());
            closesocket( ClientSocket );
            WSACleanup();
            return 9;
        }
  }
 }
 closesocket( ClientSocket );
 WSACleanup();
 system("pause");
 return 0;
}

Thanks in advance!!!!
Nicolas Miranda S.

Best Answer

The server is blocked inside the recv call when the client waits for the response and thus cannot send anything. Your receiver timeout is 1 second, so after 1 second the client generates a timeout error (WSAETIMEDOUT == 10060).

When you do the shutdown, you only specify SD_SEND, so the connection is not closed, but it causes the server to exit recv, and it is therefore able to send the response.

Note: recv will block until something is received for a stream socket (SOCK_STREAM). You should look at the select() function to see how you can "peek" whether there is data available before calling recv.

Here is an example of using select:

    fd_set fds;
    timeval tv;
    tv.tv_sec = 5000;
    fds.fd_count = 1;
    fds.fd_array[0] = ClientSocket;

    int select_result = select(1, &fds, NULL, NULL, &tv);

If select_result == 0, there was a timeout. Otherwise one of the sockets in fd_set is ready with data. Here there's just one and it was specified as a readfds in the select call.

You need to re-arrange the app so that you have some event (receive the third message, for example, or some timeout without anything received, or something in the message itself) that causes the server to send the response. You can use a second thread for responses but that's beyond what I can show in a short answer.

Related Topic