【问题标题】:Why std::string makes WSAGetLastError returning zero?为什么 std::string 使 WSAGetLastError 返回零?
【发布时间】:2018-07-22 19:34:06
【问题描述】:

总有一天。我为 C++ (Visual Studio 2015) 编写了 Socket 包装类,例如 Python 内置套接字库

所有功能都运行良好。但是,我发现我写的一些函数有问题。

使用std::string 的方法如.size().clear()WSAGetLastError() 后总是返回零。

std::string str = "Hello, world";
size_t recvsz = ::send(socket, str.c_str(), str.size(), 0);

// Why always return zero after using std::string's methods?
int err = ::WSAGetLastError();

因此,我无法知道socket的状态,socket死了,或者socket还在等待接收数据。

所以,我使用了在std::string 的方法之前存储WSAGetLastError() 返回值的方法,然后使用WSASetLastError() 恢复它。

std::string str = "Hello, world";

// Before using std::string's methods
int err = ::WSAGetLastError();

size_t recvsz = ::send(socket, str.c_str(), str.size(), 0);

// Restore error value after std::string's methods
::WSASetLastError(err);

我使用的方法是否正确?或者为什么std::string 会导致最后一个错误归零?

已编辑:哇...我错了.. :(。我使用 ::send 而不是 ::recv。抱歉让您感到困惑

SOCKET socket;
char buf[100] = {0x00,};
std::string data, temp_buf;
ssize_t recvsz = ::recv(socket, buf, 99, 0);

// When err has proper error code before std::string methods
// It just for a test
// int err = ::WSAGetLastError();

temp_buf = buf;
data += buf;

int err = WSAGetLastError(); // Always return zero!

【问题讨论】:

  • 如果您实际使用的是 C++17 而不是 C++11,则可以调用 str.data() 来获取非常量缓冲区。
  • 当函数没有失败时,WSAGetLastError() 的返回值是完全不可预知的。修复你的代码,只有在 recv() 返回 SOCKET_ERROR 时才调用它。
  • 你怎么知道零不是recv设置的?你是在强迫它失败吗?
  • 你在做什么之前 recvWSAGetLastError绝对无关之后 recv以防万一失败
  • 我使用的方法正确吗? - 不,错。你必须使用设置sendWSAGetLastError,以防万一失败,但不要恢复到之前的状态

标签: c++ sockets c++11 winapi winsock


【解决方案1】:

您不应该以这种方式写入std::string 的内部缓冲区。它由对象管理,您必须使用append()assign() 等函数对其进行更新,以便字符串可以跟踪其当前内容和长度。

此外,c_str() 返回 const char* 指针,您无法写入此类内存。

如果您确定您将始终写入正确数量的字节(因此写入的大小数据和内部存储的数据的实际长度将匹配),您可以使用operator[] 获取对特定字符的引用在字符串内部(大概是您的第一个)并将其地址用作目标缓冲区。


正如@AndyG 在他的评论中所注意到的,C++ 17 引入了std::basic_string::data(),它还返回指向实际缓冲区的指针。

您的代码应采用以下形式之一:

size_t recvsz = ::recv(socket, &str[0], str.size(), 0);

size_t recvsz = ::recv(socket, str.data(), str.size(), 0); // C++ 17, str must not be const

【讨论】:

  • 您可以覆盖内部缓冲区,但.c_str() 的结果可能不是那个缓冲区。而且,即使是这样,constness 也意味着该程序具有 UB,因此任何事情都可能发生(您所说的这一点)。
  • @LightnessRacesinOrbit 我完全同意。但是,为了确保正确的行为并保持字符串的状态一致,您必须确保始终写入正确数量的字节。如果出现差异,这将涉及对resize() 的额外调用。
  • 对不起.. 我在这个问题中使用了 ::send 而不是 ::recv。但同时使用 ::recv 和 ::send 也会发生相同的问题
  • @pr0ximo - 但同样的问题 - 到底是哪个问题?
  • 变量“socket”是一个异步socket。使用 ::recv 函数,我想从远程主机接收数据到名为“char buf [100]”的缓冲区。函数 ::recv 不会从远程接收数据。因此,使用 WSAGetLastError() 函数,我可以知道稍后我将尝试接收的套接字状态,或者我将断开主机。但是,使用 std::string 方法(.append(), .size()) 后,WSAGetLastError() 总是返回零。
【解决方案2】:

因为您在不属于您的只读存储器中写入数据,犯下了严重的轻率行为。简而言之,您破坏了计算机的内存,现在您的程序将无法正常运行。

使用兼容的 C++11 编译器,您可以:

ssize_t recvsz = ::recv(socket, &str[0], str.size(), 0);

那是因为&str[0] 是一个指向实际字符串缓冲区的指针(在非const 上下文中),并且因为从 C++11 开始保证数据会被存储 连续地。从技术上讲,在那之前,并没有那么多(尽管在实践中基本上总是如此)。

否则,您将不得不分配一个自己的char[] 缓冲区,用于接收数据。您几乎可以免费为其提供自动存储持续时间,除非您使用的是资源有限的嵌入式系统(但无论如何您都不会使用string)。

顺便说一句,请注意您的返回类型:size_t 是无符号的。 recv 返回一个带符号的 ssize_t,它可能为负数以指示错误情况。

const size_t BUF_SIZE = 256;
char buf[BUF_SIZE];

ssize_t recvsz = ::recv(socket, &buf[0], BUF_SIZE, 0);
if (recvsz < 0) {
   // ... handling
}

// Now, optionally:
std::string str(buf, recvsz);

现在,所有这些都解决了,答案是the "last error" is permitted to be changed even by a successful operation。所以,是的,如果你想保留它,你必须保存然后重新设置以前的“最后一个错误”值。这似乎确实具有可疑的价值——为什么你不处理它产生的错误?在导致错误后继续进行似乎是一种糟糕的业务方式。

【讨论】:

  • 啊!,使用 ::recv 接收数据到非常量缓冲区内存也是同样的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 2015-08-29
  • 1970-01-01
  • 2014-04-08
  • 1970-01-01
  • 1970-01-01
  • 2018-02-12
相关资源
最近更新 更多