在epoll上看到两个accept事件

Seeing two accept events on epoll

我第一次在 Linux 上玩 epoll,看到一些奇怪的行为。具体来说,当我将客户端连接到套接字时,我看到 epoll_wait 在服务器端发出了两个事件。当我第二次尝试调用 accept 时,出现 "temporary unavailable" 错误。

这是简单的客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

const char* msg = "friendly ping";

int main(int argc, char** argv) {
  if (argc != 2) {
    fprintf(stderr, "Usage: %s <socket>\n", argv[0]);
    exit(-1);
  }

  int sock = socket(AF_UNIX, SOCK_STREAM, 0);
  if (sock < 0) {
    perror("socket");
    exit(-1);
  }

  struct sockaddr_un addr;
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);

  if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    perror("Error connecting");
    exit(-1);
  }

  write(sock, msg, strlen(msg));
  close(sock);

  return 0;
}

服务器代码如下:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

const int kMaxEvents = 100;
const char *kSocketFile = "dummy_socket";

void MakeNonBlocking(int fd) {
  int flags, s;

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

  flags |= O_NONBLOCK;
  s = fcntl (fd, F_SETFL, flags);
  if (s == -1) {
    perror ("fcntl");
    exit(-1);
  }
}

void AcceptConnections(int sock, int epoll_fd) {
  struct epoll_event event;
  event.data.fd = sock;
  event.events = EPOLLIN;

  int insock = accept(sock, NULL, NULL);
  if (insock < 0) {
    perror("Error accepting connection");
    exit(-1);
  }

  MakeNonBlocking(insock);

  int s = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, insock, &event);
  if (s < 0) {
    perror("Epoll error adding accepted connection");
    exit(-1);
  }
  printf("Connection processed.\n");
}

int main(void) {
  int sock, efd, n;
  struct sockaddr_un addr;
  struct epoll_event event;
  struct epoll_event *events;
  char buf[1024];

  if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
    perror("Error creating socket.");
    exit(-1);
  }

  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, kSocketFile, sizeof(addr.sun_path) - 1);
  if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
    perror("Error binding name to socket");
    exit(-1);
  }

  if (listen(sock, SOMAXCONN) < 0) {
    perror("Error listening on socket");
    exit(-1);
  }

  MakeNonBlocking(sock);

  if ((efd = epoll_create1(0)) < 0) {
    perror("Epoll initialization error");
    exit(-1);
  }

  event.data.fd = sock;
  event.events = EPOLLIN;
  if (epoll_ctl(efd, EPOLL_CTL_ADD, sock, &event) < 0) {
    perror("Epoll error adding socket");
    exit(-1);
  }

  events = (struct epoll_event*) calloc(kMaxEvents, sizeof(event));
  if (!events) {
    perror("Error allocating event buffers");
    exit(-1);
  }

  while(1) {
    printf("Calling epoll_wait\n");
    if ((n = epoll_wait(efd, events, kMaxEvents, -1)) == -1) {
      perror("epoll_wait failure");
      exit(-1);
    }

    for (int i = 0; i < n; ++i) {
      printf("Checking event for fd = %d\n", events[i].data.fd);
      if (sock == events[i].data.fd) {
        AcceptConnections(sock, efd);
        continue;
      }

      int count = read(events[i].data.fd, buf, 100);
      if (count == 0) {
        close(events[i].data.fd);
      }
      write(1, buf, count);
    }
  }

  free(events);
  close(efd);
  close(sock);
  unlink(kSocketFile);
  return 0;
}

当我 运行 服务器并连接到客户端时,我得到:

Calling epoll_wait
Checking event for fd = 3
Connection processed.
Calling epoll_wait
Checking event for fd = 3
Error accepting connection: Resource temporarily unavailable

换句话说,我在侦听套接字上看到了两个事件。有什么想法吗?

要修复,请将 AcceptConnections 更改为:

void AcceptConnections(int sock, int epoll_fd) {
  struct epoll_event event;
  event.events = EPOLLIN;

  int insock = accept(sock, NULL, NULL);
  if (insock < 0) {
    perror("Error accepting connection");
    exit(-1);
  }

  // This is the important change.
  event.data.fd = insock;

  MakeNonBlocking(insock);

  int s = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, insock, &event);
  if (s < 0) {
    perror("Epoll error adding accepted connection");
    exit(-1);
  }
  printf("Connection processed.\n");
}

epoll 有时很有趣...

问题在于您在 AcceptConnections 中注册活动的方式。

epoll 接受两个不同的 fd 值,一个在 event.data.fd(用户不透明数据值)中,另一个在 epoll_ctl 函数调用的开头( fd 控制事件)。

event.data.fd 可以是任何东西,它是您在事件循环中读取的实际数据...

...在您的原始代码中,您将其设置为 listening 套接字而不是 client 套接字。

因此,当 client 套接字准备好读取数据时,会引发一个事件 event.data.fd 指向 listening 套接字而不是 客户端 套接字。

由于您没有清除事件(对于客户端套接字),它会根据您设置的数据(监听套接字 fd)反复引发。

祝你好运!