在网络库中初始化 Winsock 的最佳实践

Best practice for initializing Winsock in a networking library

我正在编写一个小型库,它在内部使用 OS 套接字 API 进行一些网络连接。

在Windows上,需要在开头调用WSAStartup,在结尾调用WSACleanup来初始化Winsock。但是 documentation 表示允许多次初始化 Winsock 然后清理相同的次数,因为它在内部使用引用计数。

现在我正在解决一个难题。图书馆作家的更好做法是什么?

  1. 提供调用 WSAStartup/WSACleanup 的全局函数 initializeLibrary/terminateLibrary 并指示用户在其应用程序的 beginning/end 调用它们.
  2. 在我的 类 的 constructors/destructors 内部调用 WSAStartup/WSACleanup,根本不要打扰用户。

现在我看到第二个选项看起来更方便,但它是个好主意吗?这样做没有任何隐藏的不良后果吗?它会对性能产生影响吗?图书馆秘密地做这件事是一种好习惯吗?

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

我可能忽略了这里的要点,但对我来说 any 库的要点是从 end-user 中抽象出细节,使用它 hassle-free。当然,抽象可能会在某种程度上限制灵活性,但我不认为这里是这种情况。请注意,我只是在解决 'secrecy' 问题,老实说,我不知道它如何影响 e。 G。性能。

What is a better practice for library writers?

您在这里错过了第三个选项。我通常反对 init()finalize() 函数,因为它违背了 RAII - 用户可能只是忘记调用它们。但是,您可以设计一个 'token' class,仅在应用程序的 'root' 处创建,需要使用 API 的任何其他部分。考虑一下:

#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) {
    }
    // ...
};

我不是 boost::asio 用户,但据我所知,他们就是这样做的。 io_context class 作为 ApiKey.

如果您需要更多信息,可以在他们的源代码中查找:

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