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

时间:2018-01-26 19:13:29

标签: 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编写的基本echo服务器(但它更像是普通的旧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代替recv来从socket读取。

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行为 - 它们不是同一个系统调用。

1 个答案:

答案 0 :(得分:1)

SO_NOSIGPIPE是socket option  不打算传递给var PouchDB = require('pouchdb'); var fs = require('fs'); var child = require('child_process'); var originalDB = "dbName"; var tempDB = "dbName_clean"; var serviceName = "Apache CouchDB"; var couchDBDataPath = "C:\\bin\\couchdb\\couchdb.2.1.1\\data\\"; var db = new PouchDB('http://URL/' + originalDB); var db2 = new PouchDB('http://URL/' + tempDB); console.log("Compacting"); return db.compact().then(function (result) { console.log("Compacting done, replicating"); var batch_size = 100; return db2.destroy().then(function () { db2 = new PouchDB('http://URL/' + tempDB); return db.replicate.to(db2, { batch_size: batch_size, filter: function (doc, req) { return !doc._deleted; } }).on('change', function (info) { console.log("batch done"); }).on('complete', function () { console.log('complete'); }).on('paused', function (err) { // replication paused (e.g. replication up to date, user went offline) console.log("paused", err); }).on('active', function () { // replicate resumed (e.g. new changes replicating, user went back online) console.log("Active"); }).on('denied', function (err) { // a document failed to replicate (e.g. due to permissions) console.log("Denied", err); }).on('error', function (err) { // handle error console.log("error", err); // reject(err); }).then(function () { //replicate done, rename everything var date = new Date(); console.log("Stopping service"); child.execSync('net stop "' + serviceName + '"'); console.log("Service Stopped"); var newName; var counter = 0; do { newName = "_" + date.getFullYear() + date.getMonth() + date.getDay() + "_" + counter; counter++; } while (fs.existsSync(couchDBDataPath + originalDB + newName + ".couch") || fs.existsSync(couchDBDataPath + "." + originalDB + "_design" + newName)); console.log("Renaming original couch to backup labeled", originalDB + newName); fs.renameSync(couchDBDataPath + originalDB + ".couch", couchDBDataPath + originalDB + newName + ".couch"); fs.renameSync(couchDBDataPath + "." + originalDB + "_design", couchDBDataPath + "." + originalDB + newName + "_design"); console.log("Renaming clean couch back to original", tempDB); fs.renameSync(couchDBDataPath + tempDB + ".couch", couchDBDataPath + originalDB + ".couch"); fs.mkdirSync(couchDBDataPath + "." + originalDB + "_design"); console.log("Starting service"); child.execSync('net start "' + serviceName + '"'); console.log("Service started"); }); }); }).catch(function (err) { console.log(err); }); 函数族。

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

(据推测,您打算设置MSG_NOSIGNAL,但是,正如@melpomene在注释中指出的那样,此行为与在套接字上发送数据有关,而不是接收它。)