在epoll上看到两个接受事件

时间:2016-06-19 18:47:47

标签: c linux sockets epoll

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

这是简单的客户端代码:

#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

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

1 个答案:

答案 0 :(得分:1)

要解决此问题,请将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函数调用的开头({{ 1}}控制事件)。

fd可以是任何内容,它是您在事件循环中读取的实际数据...

...在原始代码中,您将其设置为侦听套接字,而不是客户端套接字。

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

由于您没有清除事件(对于客户端套接字),因此会使用您设置的数据(侦听套接字fd)重复引发该事件。

祝你好运!