Search code examples
c++socketsnetwork-programmingwinsock2

Best practice for initializing Winsock in a networking library


I'm writing a small library that internally does some networking using the OS socket API.

On Windows it is required to initialize the Winsock by calling WSAStartup at the beginning and WSACleanup at the end. However documentation says it is allowed to initialize Winsock multiple times and then cleanup same number of times, because it uses reference counting internally.

And now i'm solving a dilema. What is a better practice for library writers?

  1. Provide global functions initializeLibrary/terminateLibrary that calls the WSAStartup/WSACleanup and instruct the user to call them at the beginning/end of his application.
  2. Call the WSAStartup/WSACleanup internally in constructors/destructors of my classes and don't bother user about it at all.

Now i see the second option looks more convenient, but is it a good idea? Aren't there any hidden bad consequences of doing it? Can it have performance impact? Is it a good practice for a library to do this secretly?


Solution

  • Is it a good practice for a library to do this secretly?

    I may be missing the point here, but to me the point of any library is to abstract the details away from the end-user, making using it hassle-free. Of course, abstraction may somewhat limit flexibility, but I don't feel like this is the case here. Note, that I'm only addressing the 'secrecy' issue, I honestly don't know how it affects e. g. performance.

    What is a better practice for library writers?

    You're missing the third option here. I am generally against init() and finalize() functions, as it goes against RAII - users may simply forget to call those. However, you could design a 'token' class, created only at the 'root' of the application, that is required to make use of any other parts of the API. Consider this:

    #include <WinSock2.h>
    
    class ApiKey {
    public:
        ApiKey() {
            auto wsadata = WSADATA();
            auto startupResult = WSAStartup(MAKEWORD(2, 2), &wsadata);
            // ...
        }
    
        ~ApiKey() {
            auto cleanupResult = WSACleanup();
            // ...
        }
    };
    
    class Socket {
    public:
        Socket(ApiKey& key) {
        }
        // ...
    };
    

    I'm not a boost::asio user, but as far as I know, that's how they do it. The io_context class serves as the ApiKey.

    You can look it up in their source code if you want some more info:

    https://github.com/boostorg/asio/blob/develop/include/boost/asio/detail/impl/winsock_init.ipp

    https://github.com/boostorg/asio/blob/develop/include/boost/asio/io_context.hpp