Hello Windows Sockets 2 - server

The Winsock API, also known as WSA, implements the socket paradigm not much differently from the BSD (Berkeley Software Distribution) concept. Here I am going to write a simple echo application that should help to familiarize with its basic functionality.

Initializing Winsock

To use Winsock 2 we need to link Ws2_32.lib (if the 32 bit version is OK for you), and we can do that specifying it in the project Properties - Linker - Input - Additional Dependencies field. Alternatively, we could use a pragma directive:
#pragma comment(lib, "Ws2_32.lib")
Then include files required are winsock2.h and, almost ever, ws2tcpip.h, the WinSock2 extension for TCP/IP protocols header.

The winsock code should be within two calls that take care of initializing and shutting down the socket support. Typically your code should look like:
WSADATA wsaData; // 1
int failCode = WSAStartup(MAKEWORD(2,2), &wsaData); // 2
if(failCode)
{
    std::cout << "WSAStartup failed: " << failCode << std::endl;
    return;
}

// 3
// ...

WSACleanup(); // 4
1. WSADATA is a structure containing a few data related to the current active Winsock API.
2. WSAStartup() try to initialize winsock for the passed version support (2.2 here) and sets the wsadata structure. If it works fine it returns 0, otherwise it returns an error code.
3. Your winsock code goes here.
4. Winsock shutdown.

Open/close socket

To create a socket we need to specify an ADDRINFO structure. Here is the how to for the server:
void server(const char* port) // 1
{
    ADDRINFO hints;
    wsa::set(hints, AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP); // 2

    ADDRINFO* ai = wsa::get(NULL, port, &hints); // 3
    if(ai)
    {
        SOCKET sk = wsa::createServerSocket(ai); // 4
        freeaddrinfo(ai); // 5

        if(sk != INVALID_SOCKET)
        {
            coreServer(sk); // 6
            closesocket(sk);
        }
    }
}
1. The user tells on which port the socket should sit.
2. See below for this tiny utility function I have written thinking it makes the code clearer. The addrinfo is initialize to say to winsock that we want to create a server TCP v4 socket.
3. A second tiny utility function of mine, to wrap a call to getaddrinfo() that returns an available addrinfo matching our request, or NULL.
4. Another utility function, this one wrapping a call to socket(), that creates a winsock socket, and bind() it to the passed address.
5. Once we get the socket, we can get rid of the addrinfo.
6. If we have got a good socket, we can do the real job.
7. We are done with the socket, so we close it.

Let see the utility functions used above:
namespace wsa
{
    void set(ADDRINFO& that, int flags =0, int family =0, int type =0, int protocol =0, // 1
        size_t addrlen =0, char* name =0, SOCKADDR* addr =0, ADDRINFO* next =0)
    {
        that.ai_flags = flags;
        that.ai_family = family;
        that.ai_socktype = type;
        that.ai_protocol = protocol;
        that.ai_addrlen = addrlen;
        that.ai_canonname = name;
        that.ai_addr = addr;
        that.ai_next = next;
    }

    ADDRINFO* get(const char* host, const char* port, ADDRINFO* hints)
    {
        ADDRINFO* ai = NULL;
        int failCode = getaddrinfo(host, port, hints, &ai); // 2
        if(failCode)
        {
            std::cout << "getaddrinfo failed: " << failCode << std::endl;
            return NULL;
        }
        return ai;
    }

    SOCKET createSocket(ADDRINFO* ai) // 3
    {
        SOCKET sk = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
        if(sk == INVALID_SOCKET)
        {
            std::cout << "error creating socket: " << WSAGetLastError() << std::endl;
            return INVALID_SOCKET;
        }
        return sk;
    }

    SOCKET createServerSocket(ADDRINFO* ai)
    {
        SOCKET sk = createSocket(ai); // 4

        if(sk != INVALID_SOCKET && bind(sk, ai->ai_addr, ai->ai_addrlen) == SOCKET_ERROR) // 5
        {
            closesocket(sk); // 6

            std::cout << "Unable to bind: " << WSAGetLastError() << std::endl;
            return INVALID_SOCKET;
        }
        return sk;
    }
}
1. It ensures all the fields are set to zero/NULL or to a valid value.
2. Wraps a call to getaddrinfo().
3. First step in the creation of a server socket, it wraps a call to socket().
4. After creating a socket in (3) ...
5. ... we bind it to the passed address.
6. Some error handling.

Accepting a client

After all this setting up, we can finally do something with our socket:
void coreServer(SOCKET skServer)
{
    if(listen(skServer, SOMAXCONN) == SOCKET_ERROR) // 1
    {
        std::cout << "Listen failed with error: " << WSAGetLastError() << std::endl;
        return;
    }

    SOCKET skClient = accept(skServer, NULL, NULL); // 2
    if(skClient == INVALID_SOCKET)
    {
        std::cout << "accept failed: " << WSAGetLastError() << std::endl;
        return;
    }

    echoLoop(skClient); // 3
    closesocket(skClient);
}
1. We have a passive (server) socket, bound to a specific address. Before working on it, we have to prepare it to listen for incoming traffic.
2. Here we hangs till we receive an input from a client socket. In this simple example, there could be just one client accessing the server. Once the server accepts a client connection, it has no way to accept another one of them.
3. The real job is done here.
4. And finally we close the client socket.

Receive and send

We have the client socket counterpart. We can receive on it, and send back some data to it. Here we implements an echo procedure:
void echoLoop(SOCKET skClient)
{
    char buffer[BUFLEN];

    int rSize;
    do {
        rSize = recv(skClient, buffer, BUFLEN, 0); // 1
        if(rSize > 0)
        {
            std::cout << "Buffer received: '";
            std::for_each(buffer, buffer + rSize, [](char c){ std::cout << c; });
            std::cout << '\'' << std::endl;

            int sSize = send(skClient, buffer, rSize, 0); // 2
            if(sSize == SOCKET_ERROR)
            {
                std::cout << "send failed: " << WSAGetLastError() << std::endl;
                return;
            }
            std::cout << sSize << " bytes sent back" << std::endl;
        }
        else if(rSize == 0)
            std::cout << "closing connection ..." << std::endl;
        else
        {
            std::cout << "Error receiving: " << WSAGetLastError() << std::endl;
            return;
        }
    } while(rSize > 0);
}
1. Receive until the client shuts down the connection
2. Echo the buffer back to the sender

If we run the server alone, it will hang forever waiting for a client. We'll see it in the next post, but you can already go to github and get the full client/server source code. This example is based on the official Getting started with Winsock topic on MSDN.

No comments:

Post a Comment