我必须调用 WSAGetLastError() 两次吗?

问题描述 投票:0回答:1
Platform: Windows 10 x64
          MingW64 with Visual Studio 2022

我正在编写一个程序,为套接字添加超时功能。下面是代码的简化版本。

如下面的代码所示,Connect函数中用于处理错误的一些代码调用了两次WSAGetLastError。有这个必要吗?

问GPT才知道这是为了兼容多线程程序吧?

#include <cmath>
#include <cstdint>
#include <stdexcept>
#include <WS2Tcpip.h>

void throw_exception(const char *function, const int code, const char *content)
{
    char message[128]{};
    snprintf(message, sizeof(message), "%s[%d]: %s", function, WSAGetLastError(), content);
    throw std::runtime_error(message);
}

class SocketType {
public:
    int family;
    int type;
    int proto;
    SOCKET fd;
    SocketType(int family, int type, int proto)
    : family(family), type(type), proto(proto)
    {
        this->fd = socket(family, type, proto);
        if(this->fd == SOCKET_ERROR) {
            throw_exception("SocketType::SocketType", WSAGetLastError(), "Failed tp create socket.");
        }
    }
};

TIMEVAL create_timeval(double number)
{
    TIMEVAL tv{};
    double intPart, floatPart;
    floatPart = modf(number, &intPart);
    tv.tv_sec = (long)intPart;
    tv.tv_usec = (long)floatPart;
    return tv;
}

void Connect(SocketType Socket, std::string remote_addr, uint16_t remote_port, double timeout)
{
    ADDRINFO hints{
                .ai_family = Socket.family,
                .ai_socktype = Socket.type,
                .ai_protocol = Socket.proto};
    ADDRINFO *result{};

    u_long mode = true;
    fd_set my_fd_set;
    TIMEVAL tv = create_timeval(timeout);

    if(getaddrinfo(remote_addr.c_str(), std::to_string(remote_port).c_str(), &hints, &result)) {
        throw_exception("Connect", WSAGetLastError(), "Failed to call getaddrinfo.");
    }

    if(ioctlsocket(Socket.fd, FIONBIO, &mode)) {
        throw_exception("Connect", WSAGetLastError(), "Failed to call ioctlsocket[1].");
    }

    int err_code;
    if(connect(Socket.fd, result->ai_addr, result->ai_addrlen) == SOCKET_ERROR) {
        if(WSAGetLastError() == WSAEWOULDBLOCK) {
            printf("WSAEWOULDBLOCK in connect() - selecting.\n");
            for(;;) {
                FD_ZERO(&my_fd_set);
                FD_SET(Socket.fd, &my_fd_set);

                err_code = select(0, nullptr, &my_fd_set, nullptr, &tv);
                if((err_code == SOCKET_ERROR) && (WSAGetLastError() != WSAEINTR)) {
                    // I called twice here
                    printf("Error: %d\n", WSAGetLastError());
                    throw_exception("Connect", WSAGetLastError(), "Failed to call select.");
                } else if(err_code) {
                    int optVal;
                    int optVal_len;

                    optVal_len = sizeof(optVal);

                    err_code = getsockopt(Socket.fd, SOL_SOCKET, SO_ERROR, (char *)&optVal, &optVal_len);
                    if(err_code == SOCKET_ERROR) {
                        // I called twice here
                        printf("Error: %d\n", WSAGetLastError());
                        throw_exception("Connect", WSAGetLastError(), "Failed to call getsockopt.");
                    }

                    if(optVal) {
                        throw_exception("Connect", optVal, "Error in delayed connection.");
                    }
                    break;
                } else {
                    throw_exception("Connect", 0, "The call to select timed out.");
                }
            }
        } else {
            // I called twice here
            printf("Error: %d\n", WSAGetLastError());
            throw_exception("Connect", WSAGetLastError(), "Failed to connecting.");
        }

        mode = false;
        if(ioctlsocket(Socket.fd, FIONBIO, &mode)) {
            throw_exception("Connect", WSAGetLastError(), "Failed to call ioctlsocket[2].");
        }
    }
}

int main()
{
    WSADATA ws;
    WSAStartup(MAKEWORD(2,2), &ws);

    try {
        SocketType fd(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        Connect(fd, "xxxxxx", 4444, 5.0);
        char send_buffer[] = {
            "GET / HTTP/1.1\r\n"
            "Host: xxxxxxx\r\n"
            "Connection: close\r\n"
            "Accept: */*\r\n"
            "User-Agent: Android\r\n"
            "\r\n"};
        char recv_buffer[4096]{};

        if(send(fd.fd, send_buffer, strlen(send_buffer), 0) == SOCKET_ERROR) {
            throw_exception("main", WSAGetLastError(), "Failed to call send.");
        }
        if(recv(fd.fd, recv_buffer, sizeof(recv_buffer) - 1, 0) == SOCKET_ERROR) {
            throw_exception("main", WSAGetLastError(), "Failed to call recv.");
        }

        printf("%s\n", recv_buffer);

    } catch (std::exception &e) {
        printf("Error! %s\n", e.what());
    }

    WSACleanup();
    return 0;
}
c++ select network-programming winsock
1个回答
0
投票

您不需要调用

WSAGetLastError()
两次,但如果您这样做,那么它基本上只是
GetLastError()
的包装,它将最后一个错误存储在调用线程内,因此多次调用
(WSA)GetLastError()
将简单地返回相同的值,直到另一个系统调用重置最后一个错误。 通常首选调用
(WSA)GetLastError()
一次并将结果保存到局部变量中,然后根据需要使用它。

此外,您的

throw_exception()
忽略其
code
参数。不要这样做。

© www.soinside.com 2019 - 2024. All rights reserved.