【问题标题】:Properly writing to a nonblocking socket in C++在 C++ 中正确写入非阻塞套接字
【发布时间】:2011-03-10 09:31:37
【问题描述】:

我在尝试将阻塞套接字服务器转换为非阻塞套接字服务器时遇到了一个奇怪的问题。虽然使用阻塞套接字发送消息时只收到一次,但使用非阻塞套接字时,消息似乎被接收了无数次。 这是更改的代码:

return ::write(client, message, size);

// Nonblocking socket code
int total_sent = 0, result = -1;
while( total_sent < size ) {
   // Create a temporary set of flags for use with the select function
   fd_set working_set;
   memcpy(&working_set, &master_set, sizeof(master_set));

   // Check if data is available for the socket - wait 1 second for timeout
   timeout.tv_sec = 1;
   timeout.tv_usec = 0;
   result = select(client + 1, NULL, &working_set, NULL, &timeout);

    // We are able to write - do so
   result = ::write(client, &message[total_sent], (size - total_sent));
   if (result == -1) {
      std::cerr << "An error has occured while writing to the server."
              << std::endl;
      return result;
   }
   total_sent += result;
}

return 0;

编辑:主集的初始化如下所示:

// Private member variables in header file
fd_set master_set;
int sock;

...

// Creation of socket in class constructor
sock = ::socket(PF_INET, socket_type, 0);

// Makes the socket nonblocking
fcntl(sock,F_GETFL,0);

FD_ZERO(&master_set);
FD_SET(sock, &master_set);

...

// And then when accept is called on the socket
result = ::accept(sock, NULL, NULL);
if (result > 0) {
   // A connection was made with a client - change the master file
   // descriptor to note that
   FD_SET(result, &master_set);
}

我已经确认,在这两种情况下,代码只会针对违规消息调用一次。此外,客户端代码根本没有改变 - 有人有什么建议吗?

【问题讨论】:

  • 请注意,非阻塞套接字返回 -1 并带有 errno==EWOULDBLOCK 或 errno==EAGAIN 而不是阻塞。
  • master_set 长什么样子?
  • 您没有使用 fd_sets 或未正确选择,请仔细阅读它们......
  • strace 下运行你的程序,你会看到真正发生了什么。 (另外,更改 return 0; 以返回 total_sent; ,以及给出的其他建议)

标签: c++ sockets


【解决方案1】:

我不相信这段代码在“非阻塞”版本中真的只调用过一次(引用因为它还不是真正的非阻塞,正如 Maister 指出的那样,look here),再次检查。如果阻塞和非阻塞版本一致,非阻塞版本应该返回total_sent(或大小)。使用return 0,调用者可能会认为没有发送任何内容。这会导致无限发送......这不是正在发生的事情吗?

您的“非阻塞”代码也很奇怪。无论如何,您似乎都使用 select 使其阻塞...好吧,超时时间为 1s,但是您为什么不使它真正成为非阻塞呢?即:删除所有select 内容并测试write() 中的错误情况,其中errno 为EWOULDBLOCKselectpoll 用于多路复用。

您还应该检查选择错误并使用 FD_ISSET 检查套接字是否真的准备好。如果 1 秒超时真的发生了怎么办?或者如果选择因某种中断而停止?并且如果在写入中发生错误,您还应该写入哪个错误,这比您的通用消息更有用。但是我猜这部分代码还远未完成。

据我了解您的代码可能看起来有点像这样(如果代码在唯一线程或线程中运行,或者在接受连接时分叉会更改细节):

// Creation of socket in class constructor
sock = ::socket(PF_INET, socket_type, 0);
fcntl(sock, F_SETFL, O_NONBLOCK);

// And then when accept is called on the socket
result = ::accept(sock, NULL, NULL);
if (result > 0) {
   // A connection was made with a client
   client = result;
   fcntl(client, F_SETFL, O_NONBLOCK);
}

// Nonblocking socket code
result = ::write(client, &message[total_sent], (size - total_sent));
if (result == -1) {
  if (errno == EWOULDBLOCK){
      return 0;
  }
  std::cerr << "An error has occured while writing to the server."
          << std::endl;
  return result;
}
return size;

【讨论】:

  • 每当他添加对 EWOULDBLOCK/EAGAIN 的检查时,我们应该告诉他检查 EINTR ;P
  • @ninjalj:是的。这就是为什么我在回答中写了“如果选择因某种中断而停止”。
  • 我已经按照您的建议更改了代码,以使套接字实际上是非阻塞的 - 非常感谢您的建议。原来 send 函数实际上只被调用了一次,但是 message 参数的大小设置错误,导致它不断循环。但是,您的帮助对于这项工作至关重要 - 谢谢!
【解决方案2】:
fcntl(sock,F_GETFL,0);

如何使套接字不阻塞?

fcntl(sock, F_SETFL, O_NONBLOCK);

另外,你没有检查你是否真的可以写入套接字非阻塞样式

FD_ISSET(client, &working_set);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-09
    • 2013-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多