Click here to Skip to main content
15,887,746 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more: , +
INTRODUCTION:

I have studied the MSDN examples for blocking TCP server[^] and blocking TCP client[^].

I wanted to try something simple, in view of modifying those examples to create simple chat application.

I have tried to implement the following, for a start:
  • send message from server
  • receive and display that message on client
  • send response from client
  • receive and display the response from client

RELEVANT INFORMATION

I apologize in advance for the lengthy code, but i strongly believe it is relevant for me to submit SSCCE[^] for both client and the server, in order for community to stand a chance for solving the problem.

I have tried to keep the code as minimal as possible, but did not want to omit basic error checking.

You can copy/paste both in single .cpp file, and they should compile and run without problem:

Server code:
C++
#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>

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

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int __cdecl main(void) 
{
    WSADATA wsaData;
    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int iResult;
    char recvbuf[DEFAULT_BUFLEN] = "";
    int recvbuflen = DEFAULT_BUFLEN;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // No longer need server socket, 
    // because I want to accept only 1 client
    closesocket(ListenSocket);

    // ===================== let us try to send a message...
    std::string message = "Test message from server !!!";
    int total = message.size();
    const int messageLength = message.size();

    while (iResult = send( ClientSocket,
        // send only the missing part of the string, if send failed to deliver entire packet:
        // we move the start of the string forward by messageLength - total
        // while we send remaining number of bytes, which is held in total
        message.substr(messageLength - total, total).c_str(), total, 0),
        iResult > 0)
    {
        total -= iResult;
    }

    if (iResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

/*  // adding this, seems to solve the problem ???
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }
*/
    // receive response from client...
    while (iResult = recv(ClientSocket, recvbuf, recvbuflen, 0), iResult > 0)
    {
        printf("%s", recvbuf);
        memset(recvbuf, '\0', sizeof(recvbuf));
    }

    if(iResult < 0)
    {
        printf("recv failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

    // cleanup
    closesocket(ClientSocket);
    WSACleanup();

    getchar();  // so I can stop the console from immediately closing...
    return 0;
}


Client code:
C++
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>

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


#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int __cdecl main(int argc, char **argv) 
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo *result = NULL,
                    *ptr = NULL,
                    hints;

    char recvbuf[DEFAULT_BUFLEN] = "";
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) 
    {
        printf("WSAStartup failed with error: %d\n", 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);
    if ( iResult != 0 ) 
    {
        printf("getaddrinfo failed with error: %d\n", 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
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) 
        {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) 
        {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

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

    // receive message from server...
    while (iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0), iResult > 0)
    {
        printf("%s", recvbuf);
        memset(recvbuf, '\0', sizeof(recvbuf));
    }

    if(iResult < 0)
    {
        printf("recv failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // ===================== let us try to send a message...
    std::string message = "Client response...";
    int total = message.size();
    const int messageLength = message.size();

    while (iResult = send( ConnectSocket,
        // send only the missing part of the string, if send failed to deliver entire packet:
        // we move the start of the string forward by messageLength - total
        // while we send remaining number of bytes, which is held in total
        message.substr(messageLength - total, total).c_str(), total, 0),
        iResult > 0)
    {
        total -= iResult;
    }

    if (iResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // 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;
    }

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    getchar();  // so I can stop the console from immediately closing...
    return 0;
}


PROBLEM:

I have implemented the solution, but did not get the expected result.

Server sends the message, client successfully receives and displays it, but then client gets stuck infinitely, instead of sending it's response to the server, which blocks the server infinitely as well.

What I have tried:

First try:

Using the Debugger, I have placed breakpoint after client's receive block only to determine client never gets there after it receives first message.

I believe while loop should call recv again, which should return 0, thus forcing the loop to end.

Debugger doesn't even continue to show the content of client's receive buffer after I hit Continue, instead it exhibits behavior I can not describe at this moment since I am not a native English speaker.

Second try:

I have also tried to put receiving loop from server into thread, using CreateThread, but that did not help either.

I have also tried to put receiving loop from the client into thread, but that failed too.

I have tried to put both client and server receiving loops into thread, but that failed too.

Third try:

Finally, I have added the call to shutdown( ClientSocket, SD_SEND) in the server code, you shall find it at the lower part of the code, it is commented out.

This seems to fix the problem, but i am not sure if this is the right solution since i am just starting with Winsock.
Posted
Updated 22-Aug-16 7:37am

1 solution

You are using blocking sockets. When receiving and there are no more data, the recv function will block (not return) until new data are received or the connection is closed (as triggered with your third try).

The simplest method to avoid this is using some kind of indication that all current data has been send (e.g. using a special character like the End-of-Transmission character - Wikipedia, the free encyclopedia[^] when sending strings). Once your received data contain this indicator, leave the receive loop and process the data.

I noticed also that you did not append the terminating NULL character after receiving. You should do so to ensure that you have valid strings when printing them (or set all buffer characters to zero before receiving). You are clearing the buffer actually after receiving but not before the first time.

[EDIT]
To handle both problems, just send the strings including the terminating NULL character and stop receiving when the last actually received character is zero.
[/EDIT]
 
Share this answer
 
v2
Comments
MyOldAccount 23-Aug-16 8:25am    
First off, thank you for answering.

I noticed also that you did not append the terminating NULL character after receiving.

I am receiving bytes and am planning to dump them to a binary file. There is no need for terminating NULL, in my humble opinion.

You are clearing the buffer actually after receiving but not before the first time.

Sharp observation, I have fixed that!

the recv function will block (not return)

That explains it, I was thinking it will return.

The simplest method to avoid this is using some kind of indication that all current data has been send

I am working on implementing your advice.

I have officially accepted and 5ed your answer. Thanks!
Jochen Arndt 23-Aug-16 9:23am    
Thank you for your feedback and accepting the answer.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900