c++ - 在 OS X 上轮询和选择奇怪的行为

标签 c++ macos sockets c++11

我在 OS X 上发现了一个关于轮询的有趣的事情。让我在这里说明一下。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <set>
#include <poll.h>


#define POLL_SIZE 32

int set_nonblock(int fd) {
  int flags;

  if(-1 == (flags = fcntl(fd, F_GETFL, 0))) {
    flags = 0;
  }

  return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main() {

  int master_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  std::set<int> slave_sockets;

  struct sockaddr_in SockAddr;
  SockAddr.sin_family = AF_INET;
  SockAddr.sin_port = htons(12345);
  SockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

  bind(master_socket, (struct sockaddr *)(&SockAddr), sizeof(SockAddr));

  set_nonblock(master_socket);

  listen(master_socket, SOMAXCONN);

  struct pollfd set[POLL_SIZE];
  set[0].fd = master_socket;
  set[0].events = POLL_IN;

  while(true) {

    unsigned int index = 1;
    for(auto iter = slave_sockets.begin(); iter != slave_sockets.end(); iter++) {
      set[index].fd = *iter;
      set[index].events = POLL_IN;
      index++;
    }

    unsigned int set_size = 1 + slave_sockets.size();

    poll(set, set_size, -1);

    for(unsigned int i = 0; i < set_size; i++) {
      if(set[i].revents & POLL_IN) {
        if(i) {

          static char buffer[1024];

          // This line works as expected.
          // int recv_size = read(set[i].fd, buffer, 1024);

          // This line sends messages in infinite loop?
          // I'm checking this with `telnet 127.0.0.1 12345`
          int recv_size = recv(set[i].fd, buffer, 1024, SO_NOSIGPIPE);

          if ((recv_size == 0) && (errno != EAGAIN)) {
            shutdown(set[i].fd, SHUT_RDWR);
            close(set[i].fd);
            slave_sockets.erase(set[i].fd);
          } else if(recv_size > 0) {
            send(set[i].fd, buffer, recv_size, SO_NOSIGPIPE);
          }
        } else {
          int slave_socket = accept(master_socket, 0, 0);
          set_nonblock(slave_socket);
          slave_sockets.insert(slave_socket);
        }
      }
    }
  }

  return 0;
}

这个程序是一个用 C++11 编写的基本回显服务器(但它更像是普通的旧 C)。

我在 Linux 上观察到的行为:应用程序启动,接受客户端套接字(我正在使用 telnet 127.0.0.1 12345),我输入 “ping”,然后按 RET只返回一个“ping”。

Linux 规范:

1) clang++ -v

clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)
Target: x86_64-pc-linux-gnu
Thread model: posix

2) uname -a

Linux julian-dell 4.4.0-112-generic #135-Ubuntu SMP Fri Jan 19 11:48:36 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

我在 OS X 上观察到的行为:应用程序启动,接受客户端套接字,我写“ping”,按 RET 并得到无限量的 “pings” 返回。在 OS X 上阻止 poll 的唯一方法是使用 read 而不是 recvsocket 读取。

OS X 规范:

1) clang++ -v

Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.3.0
Thread model: posix

2) uname -a

Darwin Julians-MacBook-Pro.local 17.3.0 Darwin Kernel Version 17.3.0: Thu Nov  9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64 x86_64

我的问题是:它是 OS X(还有 BSD?)上的错误、预期行为,还是我只是在我的代码中犯了一个错误,Linux 以某种方式忽略了这个错误?而且我不完全理解将 recv 更改为 read 会如何影响 poll 行为 - 它们不是同一个系统调用吗。

最佳答案

SO_NOSIGPIPE 是一个 socket option 不打算传递给 recv() 系列函数。

在您的 OS X 安装中,SO_NOSIGPIPE 设置与 MSG_PEEK 相同的位,这意味着您的 recv() 调用实际上并没有耗尽套接字的数据缓冲区,而只是“查看”可用数据。因此,数据保留在套接字后面,当然,套接字会无限期地轮询可读。

(假设您打算设置 MSG_NOSIGNAL,但是,正如 @melpomene 在评论中指出的那样,此行为属于在套接字上发送数据,而不是接收数据。)

关于c++ - 在 OS X 上轮询和选择奇怪的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48468060/

相关文章:

python - 在 python 中制作 UDP 套接字时使用什么主机?

c - 在边缘触发模式下从 epolled 非阻塞套接字接收

c++ - 将 argv 复制到 char 数组时出错

c++ - 如何可靠地计算对象的内存地址

c++ - 如何使用 IOCP 发送文件?

macos - 为什么我的 Mac 上有两个 uid 为 0 的用户?

objective-c - 如何在xib cocoa Objective-C中实现 ScrollView

c++ - 是否有良好/广泛采用的 c++ 模板编码约定/标准?

java - 尝试设置 WebLogic Server 但找不到 JDK

java - 无法在 Android 上接收 UDP 数据包?