TCP Server Example for Windows Networking

This example demonstrates how to create a basic TCP server application using Windows Sockets API (Winsock). This server will listen for incoming connections on a specified port, accept connections, receive data from clients, and send a simple response back.

Prerequisites

Server Code

Below is the C++ code for a simple TCP server. You can compile and run this code on a Windows machine.


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

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

const int DEFAULT_PORT = 27015;
const int BUFFER_SIZE = 512;

int main() {
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << std::endl;
        return 1;
    }

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;
    addrinfo hints, *result = NULL;

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

    iResult = getaddrinfo(NULL, std::to_string(DEFAULT_PORT).c_str(), &hints, &result);
    if (iResult != 0) {
        std::cerr << "getaddrinfo failed: " << iResult << std::endl;
        WSACleanup();
        return 1;
    }

    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        std::cerr << "Error at socket(): " << WSAGetLastError() << std::endl;
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        std::cerr << "bind failed with error: " << WSAGetLastError() << std::endl;
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    freeaddrinfo(result);

    if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
        std::cerr << "listen failed with error: " << WSAGetLastError() << std::endl;
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "TCP Server listening on port " << DEFAULT_PORT << "..." << std::endl;

    while (true) {
        ClientSocket = accept(ListenSocket, NULL, NULL);
        if (ClientSocket == INVALID_SOCKET) {
            std::cerr << "accept failed: " << WSAGetLastError() << std::endl;
            continue;
        }

        std::cout << "Client connected." << std::endl;

        char recvbuf[BUFFER_SIZE];
        int bytesReceived;

        bytesReceived = recv(ClientSocket, recvbuf, BUFFER_SIZE, 0);
        if (bytesReceived > 0) {
            std::cout << "Bytes received: " << bytesReceived << std::endl;
            std::cout << "Message: " << std::string(recvbuf, bytesReceived) << std::endl;

            const char* sendbuf = "Hello from TCP Server!";
            iResult = send(ClientSocket, sendbuf, (int)strlen(sendbuf), 0);
            if (iResult == SOCKET_ERROR) {
                std::cerr << "send failed: " << WSAGetLastError() << std::endl;
            }
            std::cout << "Sent response to client." << std::endl;
        } else if (bytesReceived == 0) {
            std::cout << "Client disconnected." << std::endl;
        } else {
            std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
        }

        closesocket(ClientSocket);
        std::cout << "Client connection closed." << std::endl;
    }

    closesocket(ListenSocket);
    WSACleanup();
    return 0;
}
        

Explanation

1. Initialization

The code starts by initializing the Winsock library using WSAStartup. This is crucial for any Winsock application.

2. Creating a Socket

A listening socket is created using the socket function. We specify:

3. Binding the Socket

The bind function associates the created socket with a specific IP address and port number. Here, we use NULL for the IP address (meaning the server will bind to all available interfaces) and DEFAULT_PORT.

4. Listening for Connections

The listen function puts the socket into a listening state, ready to accept incoming connection requests. SOMAXCONN indicates the maximum number of pending connections that can be queued.

5. Accepting Connections

The accept function blocks the server's execution until a client attempts to connect. When a connection is established, it returns a new socket descriptor (ClientSocket) representing the connection to that specific client. The original ListenSocket continues to listen for new connections.

6. Receiving and Sending Data

Once a client is connected:

7. Closing Sockets

After communication is finished, closesocket is used to close the client connection socket. Finally, WSACleanup is called to de-initialize the Winsock library when the server is shutting down.

How to Compile and Run

  1. Open Visual Studio.
  2. Create a new C++ Console Application project.
  3. Replace the default code with the provided server code.
  4. Ensure you are linking against Ws2_32.lib. In Visual Studio, this can be done by going to Project -> Properties -> Linker -> Input -> Additional Dependencies and adding Ws2_32.lib.
  5. Build the project (F7 or Build -> Build Solution).
  6. Run the executable. The server will start listening.

Testing the Server

You can test this server using a TCP client utility like:

When you connect with a client and send some text, the server should print the received message to its console and send back "Hello from TCP Server!".

Important: This is a simplified example. For production environments, consider robust error handling, multi-threading or asynchronous I/O for handling multiple clients concurrently, and proper security measures.

Example Client (Python)


import socket

HOST = 'localhost'
PORT = 27015

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    message = b'Hello TCP Server!'
    s.sendall(message)
    data = s.recv(1024)

print(f"Received: {data.decode()}")