Socket Programming with Winsock

This document provides a comprehensive guide to socket programming using the Windows Sockets API (Winsock). Winsock is a Microsoft-specific implementation of the Berkeley Sockets API, enabling network communication for Windows applications.

Introduction to Sockets

A socket is an endpoint for communication between two processes on a network. It's an abstract representation of one end of a two-way communication link between two programs running on the network. Winsock provides a set of functions that allow applications to:

Core Concepts

Socket Address Structures

Winsock uses specific structures to represent network addresses. The most common ones are:

Example of a SOCKADDR_IN structure:


struct sockaddr_in {
    short int sin_family;       // Address family (AF_INET for IPv4)
    unsigned short int sin_port;    // Port number
    struct in_addr sin_addr;    // IP address
    char sin_zero[8];           // Not used
};
            

Socket Types

Winsock supports various socket types, each suited for different communication needs:

Protocols

Winsock can be used with different transport layer protocols, including:

Key Winsock Functions

Here are some of the most fundamental Winsock functions:

Initialization and Cleanup


int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
int WSACleanup(void);
            

WSAStartup must be called before any other Winsock function. WSACleanup releases resources used by Winsock.

Socket Creation


SOCKET socket(int af, int type, int protocol);
            

Connection Establishment (TCP)


int connect(SOCKET s, const struct sockaddr *name, int namelen);
int bind(SOCKET s, const struct sockaddr *name, int namelen);
int listen(SOCKET s, int backlog);
SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
            

For servers:

  1. Create a socket.
  2. bind the socket to a local address and port.
  3. listen for incoming connections.
  4. accept a connection, which returns a new socket for communication with the client.

For clients:

  1. Create a socket.
  2. connect to the server's address and port.

Data Transfer


int send(SOCKET s, const char *buf, int len, int flags);
int recv(SOCKET s, char *buf, int len, int flags);
int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);
            

send/recv are for connection-oriented sockets (TCP). sendto/recvfrom are for connectionless sockets (UDP).

Example: Simple TCP Echo Client

Client Code Snippet

This demonstrates the basic structure of a TCP client that connects, sends data, and receives a response.


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

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

int main() {
    WSADATA wsaData;
    SOCKET clientSocket = INVALID_SOCKET;
    struct sockaddr_in serverAddr;
    const char* message = "Hello, Server!";
    char buffer[1024] = {0};

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

    // Create socket
    clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "socket creation failed: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // Prepare server address structure
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080); // Example port
    InetPton(AF_INET, L"127.0.0.1", &serverAddr.sin_addr); // Example IP

    // Connect to server
    if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "connect failed: " << WSAGetLastError() << std::endl;
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }

    // Send data
    send(clientSocket, message, strlen(message), 0);
    std::cout << "Sent: " << message << std::endl;

    // Receive data
    int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
    if (bytesReceived > 0) {
        buffer[bytesReceived] = '\0'; // Null-terminate the received data
        std::cout << "Received: " << buffer << std::endl;
    } else if (bytesReceived == 0) {
        std::cout << "Connection closed by server." << std::endl;
    } else {
        std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
    }

    // Cleanup
    closesocket(clientSocket);
    WSACleanup();
    return 0;
}
                

Error Handling

Winsock functions return specific error codes that can be retrieved using WSAGetLastError(). It's crucial to check the return values of all Winsock calls and handle errors appropriately.

Note: Remember to link against the ws2_32.lib library when compiling Winsock applications.

Further Reading