TCP Client Example

This document provides a basic example of a TCP client implementation in Windows using C++. This example demonstrates how to establish a connection to a TCP server, send data, and receive data.

Overview

A TCP (Transmission Control Protocol) client initiates a connection to a TCP server to exchange data. This involves:

Prerequisites

Code Example (C++)

tcp_client.cpp


#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <vector>

// Link with Ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

const int DEFAULT_PORT = 27015;
const int BUFFER_SIZE = 4096;

int main(int argc, char** argv) {
    WSADATA wsaData;
    SOCKET connectSocket = INVALID_SOCKET;
    struct addrinfo* result = nullptr, *ptr = nullptr;
    struct addrinfo hints;
    char recvbuf[BUFFER_SIZE];
    int recvbuflen = BUFFER_SIZE;

    // Validate and parse command line arguments for server address and port
    if (argc != 3) {
        std::cerr << "usage: tcp_client <serverip> <port>\n";
        return 1;
    }
    std::string serverIp = argv[1];
    std::string portStr = argv[2];

    // Initialize Winsock
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed with error: " << iResult << std::endl;
        return 1;
    }

    // Setup hints structure for getaddrinfo
    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;       // Allow IPv4 or IPv6
    hints.ai_socktype = SOCK_STREAM;   // TCP stream sockets
    hints.ai_protocol = IPPROTO_TCP;   // TCP protocol

    // Resolve the server address and port
    iResult = getaddrinfo(serverIp.c_str(), portStr.c_str(), &hints, &result);
    if (iResult != 0) {
        std::cerr << "getaddrinfo failed with error: " << iResult << std::endl;
        WSACleanup();
        return 1;
    }

    // Attempt to connect to an address until one succeeds
    for(ptr=result; ptr != nullptr ;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) {
            std::cerr << "socket failed with error: " << WSAGetLastError() << std::endl;
            continue; // Try next address
        }

        // Connect to server
        iResult = connect( connectSocket, ptr->ai_addr, (int)ptr->ai_addrlen );
        if (iResult == SOCKET_ERROR) {
            std::cerr << "connect failed with error: " << WSAGetLastError() << std::endl;
            closesocket(connectSocket);
            connectSocket = INVALID_SOCKET;
            continue; // Try next address
        }
        break; // Successfully connected
    }

    freeaddrinfo(result); // Free the linked list

    if (connectSocket == INVALID_SOCKET) {
        std::cerr << "Unable to connect to server!\n";
        WSACleanup();
        return 1;
    }

    // Send a message to the server
    std::string messageToSend = "Hello from the TCP client!";
    iResult = send( connectSocket, messageToSend.c_str(), (int)messageToSend.length(), 0 );
    if (iResult == SOCKET_ERROR) {
        std::cerr << "send failed with error: " << WSAGetLastError() << std::endl;
        closesocket(connectSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Bytes Sent: " << iResult << std::endl;

    // Receive data from the server
    do {
        iResult = recv(connectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            std::cout << "Bytes received: " << iResult << std::endl;
            std::string receivedData(recvbuf, iResult);
            std::cout << "Data: " << receivedData << std::endl;
        } else if (iResult == 0) {
            std::cout << "Connection closing...\n";
        } else {
            std::cerr << "recv failed with error: " << WSAGetLastError() << std::endl;
            break;
        }
    } while(iResult > 0);

    // Shutdown the connection since no more data will be sent
    iResult = shutdown( connectSocket, SD_SEND );
    if (iResult == SOCKET_ERROR) {
        std::cerr << "shutdown failed with error: " << WSAGetLastError() << std::endl;
        closesocket(connectSocket);
        WSACleanup();
        return 1;
    }

    // Cleanup
    closesocket(connectSocket);
    WSACleanup();

    return 0;
}
        

Explanation

  1. Include Headers: Essential headers for Winsock, I/O streams, strings, and vectors are included.
  2. Linker Directive: #pragma comment(lib, "Ws2_32.lib") ensures that the Winsock library is linked to the project.
  3. Constants: DEFAULT_PORT and BUFFER_SIZE define commonly used values.
  4. Winsock Initialization: WSAStartup() initializes the Winsock DLL.
  5. Socket Creation: socket() creates a TCP socket. getaddrinfo() is used to resolve the server's IP address and port into a usable format for the connect() function.
  6. Connection: connect() attempts to establish a connection with the specified server. The code iterates through possible addresses returned by getaddrinfo() until a connection is successful.
  7. Sending Data: send() is used to transmit data to the server.
  8. Receiving Data: recv() reads data from the server into a buffer. The loop continues as long as data is being received.
  9. Shutdown: shutdown() is called to indicate that no more data will be sent.
  10. Cleanup: closesocket() closes the connection, and WSACleanup() uninitializes Winsock.
Note: This example assumes you have a corresponding TCP server running that can accept connections and respond to the client's messages. Ensure that firewalls are configured to allow TCP traffic on the specified port.

Running the Example

  1. Compile the C++ code using a compatible compiler (e.g., Visual Studio).
  2. Ensure a TCP server is running on the target IP address and port.
  3. Execute the compiled client program from the command line, providing the server's IP address and port as arguments:

tcp_client.exe <server_ip_address> <server_port>
        

For example:


tcp_client.exe 127.0.0.1 27015
        

Further Improvements