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