Hello Windows Sockets 2 - client

The Winsock API is a collection of quite low level functions. As we have seen in the previous post, writing an echo server, substantially a simple procedure (wait for data, send them back, shutdown when the connection closes), it is not complicated but longish, we have access to the tiniest detail in the process. The most tedious part of the job is that we have to specify any single step by hand, even the ones that could be easily automatized. A slender C++ wrapper would be enough to make the job much more simpler, but till now I haven't found around anything like that. Any suggestion is welcomed.

Let's now complete our echo application, thinking about the client side.

Initializing Winsock

This is the same stuff that we have already seen for the server side. We need to enclose out code in a pair of function call to WSAStartup() and WSACleanup(), the Ws2_32.lib should be linked to our project, and we have to include a couple of header files, winsock2.h and ws2tcpip.h, in our C/C++ source file.

Open/close socket

Almost the same as for the server:
void client(const char* host, const char* port) // 1
{
    ADDRINFO hints;
    wsa::set(hints, 0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP); // 2

    ADDRINFO* ai = wsa::get(host, port, &hints);
    if(ai)
    {
        wsa::list(ai); // 3

        SOCKET sk = wsa::createClientSocket(ai);
        freeaddrinfo(ai);

        if(sk != INVALID_SOCKET)
        {
            coreClient(sk); // 4
            closesocket(sk);
        }
    }
}
1. The user should provide us both machine address an port, so that we can connect to the server socket.
2. Tiny variation, we specify AF_UNSPEC as address family, so that both TCP/IP v4 and v6 are accepted, when available. This does not make much sense here, since we already know that the server is using a TCP/IP v4, but in a real case scenario could be a useful trick.
3. We asked to winsock for all the address info matching our requirements. If its current version supports both TCP/IP v4 and v6, we should get both of them. This tiny utility function, see it below, checks all the retrieved address info and dump to the console some of their settings.
4. The rest of the code is just the same of what we have already seen for the server, before calling the specific client code, we create the socket, and then we'll close it. Notice also that we had to free by hand the memory associated to the address info.

As promised, here is the address info listing utility function:
namespace wsa
{
    void list(ADDRINFO* ai)
    {
        while(ai)
        {
            std::cout << ai->ai_family << ' ' << ai->ai_socktype << ' ' << ai->ai_protocol << std::endl;
            ai = ai->ai_next; // 1
        }
    }
}
1. ADDRINFO is actually a linked list of records. Its ai_next field points to the next element, if any. So here we are looping on all the items available.

Send and receive

Here is the client specific code:
void coreClient(SOCKET sk)
{
    char* message = "Hello Winsock 2!";

    int size = send(sk, message, strlen(message), 0); // 1
    if(size == SOCKET_ERROR)
    {
        std::cout << "send failed: " << WSAGetLastError() << std::endl;
        return;
    }

    std::cout << size << " bytes sent" << std::endl;

    if(shutdown(sk, SD_SEND) == SOCKET_ERROR) // 2
    {
        std::cout << "shutdown failed: " << WSAGetLastError() << std::endl;
        return;
    }

    do { // 3
        char buffer[BUFLEN];
        size = recv(sk, buffer, BUFLEN, 0); // 4
        if(size > 0)
        {
            std::cout << "Buffer received: '";
            std::for_each(buffer, buffer + size, [](char c){ std::cout << c; });
            std::cout << '\'' << std::endl;
        }
        else if(!size)
            std::cout << "Connection closed." << std::endl;
        else
            std::cout << "recv failed: " << WSAGetLastError() << std::endl;
    } while(size > 0);
}
1. The message is sent through the socket, winsock returns the number of bytes actually sent, or an error code.
2. This simple application sends just one message, so here we can already close the send component of the socket, releasing some resources doing that, still keeping alive its receive component.
3. Loop until we receive data from the server
4. The received data is put in the buffer, its size is returned by the call. A zero size message is interpreted as a shutdown signal coming from the server. A negative value has to be intepreted as an error code.

If we run the client alone, it won't find any server socket to connect, and would return an error to the user.

The full client/server source code is on github.

No comments:

Post a Comment