Talk about multiplexing two polls from the bug caused by select

I. Introduction

select supports a maximum of 1024 connections, and the maximum number of connected file descriptors cannot exceed 1024. If the program opens many files or uses a large page memory of 2MB, it may cause the number of open files to exceed 1024, thus causing unix socket Inexplicable problems arise. The IO multiplexing mechanism of poll is very similar to the usage of select. It uses a linked list instead of a bitmap, breaking through the limit of 1024 sockets. This article uses poll to re-implement the previous article. Function.

two poll

2.1 API description of poll

poll function prototype:

int poll(struct pollfd *fds, unsigned long nfds, int timeout); 
   
返回值:若有就绪描述符则为其数目,若超时则为0,若出错则为-1

struct pollfd is defined as follows:

struct pollfd {
    int    fd;       /* file descriptor */
    short  events;   /* events to look for */
    short  revents;  /* events returned */
 };

fd file descriptor; events The type of event to be detected; revents refers to the return event type, and multiple settings can be set; timeout is set to a negative number to wait forever; if it is 0, it means return immediately without blocking; if the value is greater than 0, it means poll The caller waits for the specified number of milliseconds before returning. The advantage over select is that you don't need to set the file descriptor every time.

Readable event types:

#define POLLIN     0x0001    /* any readable data available */
#define POLLPRI    0x0002    /* OOB/Urgent readable data */
#define POLLRDNORM 0x0040    /* non-OOB/URG data available */
#define POLLRDBAND 0x0080    /* OOB/Urgent readable data */

Writable event types:

#define POLLOUT    0x0004    /* file descriptor is writeable */
#define POLLWRNORM POLLOUT   /* no write type differentiation */
#define POLLWRBAND 0x0100    /* OOB/Urgent data can be written */

Error event type definition:

#define POLLERR    0x0008    /* 一些错误发送 */
#define POLLHUP    0x0010    /* 描述符挂起*/
#define POLLNVAL   0x0020    /* 请求的事件无效*/

2.2 Use poll to change to server code

#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <poll.h>

#define CLIENT_SIZE 100
#define SOCK_FILE "command.socket"
#define TOO_MANY "Too many client."

typedef struct unix_socket_infos_ {
  int socket;
  struct pollfd event_sets[CLIENT_SIZE];
  struct sockaddr_un client_addr;
} unix_socket_infos_t;

static int create_unix_socket(unix_socket_infos_t *this) {
  struct sockaddr_un addr;
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, SOCK_FILE, sizeof(addr.sun_path));
  addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
  int len = strlen(addr.sun_path) + sizeof(addr.sun_family) + 1;

  int listen_socket = socket(AF_UNIX, SOCK_STREAM, 0);
  if (listen_socket == -1) {
    perror("create socket error.\n");
    return -1;
  }
  int on = 1;
  /* set reuse option */
  int ret = setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
                       sizeof(on));
  unlink(SOCK_FILE);
  /* bind socket */
  ret = bind(listen_socket, (struct sockaddr *)&addr, len);
  if (ret == -1) {
    perror("bind error.\n");
    return -1;
  }
  printf("start to listen\n");
  ret = listen(listen_socket, 1);
  if (ret == -1) {
    perror("listen error\n");
    return -1;
  }
  ret = chmod(SOCK_FILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
  if (ret == -1) {
    perror("chmod error\n");
    return -1;
  }
  this->socket = listen_socket;
  return 1;
}

static int close_client(unix_socket_infos_t *this, int index) {
  int client = this->event_sets[index].fd;
  close(client);
  this->event_sets[index].fd = -1;
}

static int deal_client(unix_socket_infos_t *this, int index) {
  char buffer[1024] = {0};
  int ret = recv(this->event_sets[index].fd, buffer, sizeof(buffer) - 1, 0);
  if (ret <= 0) {
    if (ret == 0) {
      printf("lost connect.\n");
    } else {
      printf("recv error:%s \n", strerror(errno));
    }
    close_client(this, index);
    return -1;
  }
  if (ret < sizeof(buffer) - 2) {
    buffer[ret] = '\n';
    buffer[ret + 1] = 0;
  }
  fprintf(stderr, "client[%d]:%s", this->event_sets[index].fd, buffer);
  ret = send(this->event_sets[index].fd, buffer, strlen(buffer), MSG_NOSIGNAL);
  if (ret < 0) {
    perror("send error:");
  } else {
    fprintf(stderr, "server:%s", buffer);
  }
  return 1;
}

static int accept_client(unix_socket_infos_t *this ) {
  socklen_t len = sizeof(this->client_addr);
  char buffer[1024] = {0};
  int client = accept(this->socket, (struct sockaddr *)&(this->client_addr), &len);
  printf("client to comming:%d\n", client);
  if (client < 0) {
    perror("accept error\n");
    return -1;
  }
  memset(buffer, 0x0, 1024);
  int ret = recv(client, buffer, sizeof(buffer) - 1, 0);
  if (ret < 0) {
    perror("recv error\n");
    return -1;
  }
  if (ret < sizeof(buffer) - 2) {
    buffer[ret] = '\n';
    buffer[ret + 1] = 0;
  }
  fprintf(stderr, "client[%d][first]:%s", client, buffer);
  ret = send(client, buffer, strlen(buffer), MSG_NOSIGNAL);
  if (ret < 0) {
    perror("send error\n");
  } else {
    fprintf(stderr, "server[first]:%s", buffer);
  }
  int is_set = 0;
  for (int i = 0; i < CLIENT_SIZE; i++) {
    if (this->event_sets[i].fd < 0) {
      this->event_sets[i].fd = client;
      this->event_sets[i].events =  POLLRDNORM;
      is_set = 1;
      break;
    }
  }
  if (is_set == 0) {
    fputs(TOO_MANY, stdout);
    close(client);
    return -1;
  }
  return 1;
}

static int run_poll(unix_socket_infos_t *this) {
  struct timeval tv;
  int ret;
  tv.tv_sec = 0;
  tv.tv_usec = 200 * 1000;
  int ready_number;

  
  this->event_sets[0].fd = this->socket;
  this->event_sets[0].events = POLLRDNORM;

  while (1) {
    if ((ready_number = poll(this->event_sets, CLIENT_SIZE, -1)) < 0) {
      perror("poll error.");
    }
    if (this->event_sets[0].revents & POLLRDNORM) {
      accept_client(this);
      // 只有一个准备好的文件描述符
      if (--ready_number <= 0)
        continue;
    }
    for (int i = 1; i < CLIENT_SIZE; i++) {
      int socket_fd;
      if ((socket_fd = this->event_sets[i].fd) < 0) {
           continue;
      }

      if (this->event_sets[i].revents & (POLLRDNORM | POLLERR)) {
          deal_client(this, i);
          if (--ready_number <= 0)
            break;
      }

    }
  }
}

int main(int argc, char **argv) {
  unix_socket_infos_t unix_socket_infos;
  for (int i = 0; i < CLIENT_SIZE; i++) {
    unix_socket_infos.event_sets[i].fd = -1;
  }
  int ret = create_unix_socket(&unix_socket_infos);
  printf("start to loop\n");
  run_poll(&unix_socket_infos);
  return 0;
}

The code is simpler than select, and there is no 1024 limit. Compared with select, poll does not need to reset the monitored file descriptor every time, and separates the event from the file descriptor, so it is not very troublesome. In the kernel code, except for changing the bitmap of the select to a linked list, the other is not bad, and the poll method of the corresponding file of the file descriptor is also called circularly, as follows:

mask = f.file->f_op->poll(f.file, pwait);

Set the obtained mask to the returned event. The code is the same as select, all in select.c.

Whether the three sockets should be set to non-blocking mode

In the above code, we did not make special settings for the listening socket, which may cause problems. For a simple example, if the client is connected to the server, the client sends an RST message to the server, and the server The kernel deletes the full connection from the full connection queue, and the server returns from the poll. If accept cannot be called in time, the client connection in the full connection queue is just deleted by the kernel, and the call to accept cannot return.

Therefore, it is generally recommended to set the listening socket to non-blocking mode:

fcntl(fd, F_SETFL, O_NONBLOCK);

In fact, there may still be problems when using the blocking mode to listen to sockets from other clients. Like write, if there is a lot of data written, and the other party reads slowly, the sending window is 0 and blocked. The single thread is blocked and there is no poll. In the function, if there are other clients to connect, you need to wait, which will affect the performance (this has not been tested, the buffer is large enough).

Guess you like

Origin blog.csdn.net/mseaspring/article/details/126925954