Как правильно epoll для ncurses - PullRequest
       36

Как правильно epoll для ncurses

1 голос
/ 23 декабря 2019

Оригинальный вопрос: я написал некоторый клиентский код, который контролирует нажатия клавиш и sockfd, на который сервер отправляет сообщения. Проблема в том, что после первого сообщения с сервера epoll больше не вызывается сообщениями с сервера. Кроме того, каждый раз, когда я нажимаю клавишу около 10 раз, для sockfd запускается epoll и читается одна пачка символов (даже если сервер уже отправил много сообщений). Что еще больше усложняет мою путаницу, так это то, что если я отправляю только одного персонажа за раз, epoll может правильно реагировать. Все, что больше одного, будет иметь тот же результат, что и раньше (epoll не реагирует).

Редактировать: я понимаю, что если я установлю STDIN_FILENO на неблокирующую, я получу сообщения от сервера в соответствующее время. Тем не менее, программа также войдет в бесконечный цикл с STDIN_IN, который всегда запускается. Наверное, теперь вопрос в том, как правильно использовать epoll с ncurses, чтобы мы не были в бесконечном цикле.

Вот мой код:

Как использовать:

  1. clang ++ client.cpp -lncurses -o cli
  2. clang ++ server.cpp -o ser
  3. . / Ser 8080
  4. открыть другой терминал
  5. . / cli 127.0.0.1 8080

Я новичок в epoll, поэтому боюсь, что-то пропустил. Пожалуйста, дайте мне знать, что не так с моим кодом!

server.cpp:


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <chrono>
#include <errno.h>
#include <ifaddrs.h>
#include <sys/epoll.h>

#define TO_CLI_BUF_SIZE 32
#define FROM_CLI_BUF_SIZE 8

int main(int argc, char ** argv){

  //seed rand
  srand(time(NULL));

  int sockfd; // socket
  int port; // my port to listen on
  struct sockaddr_in serveraddr; // server's address
  struct sockaddr_in clientaddr;
  socklen_t clientlen;
  int currentAddrMax = 0;
  struct hostent * hostp; //host info
  char * hostaddrp; // host adddr string
  char toClientBuf[TO_CLI_BUF_SIZE];
  char fromClientBuf[FROM_CLI_BUF_SIZE];

  if(argc != 2){
    perror("usage: file <port>");
    exit(1);
  }
  port = atoi(argv[1]);

  // create socket
  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if(sockfd<0){
    perror("ERROR: opening socket.");
    exit(1);
  }

  bzero((char*) &serveraddr, sizeof(serveraddr));
  serveraddr.sin_family = AF_INET;
  serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
  serveraddr.sin_port = htons((unsigned short)port);

  if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
    perror("ERROR on bind");
    exit(1);
  }

  bzero(fromClientBuf, FROM_CLI_BUF_SIZE);
  clientlen = sizeof(clientaddr);
  int n = recvfrom(sockfd, fromClientBuf,FROM_CLI_BUF_SIZE, 0, (struct sockaddr*) &clientaddr, &(clientlen));

  while (1){ 
    bzero(toClientBuf, TO_CLI_BUF_SIZE);
    strcpy(toClientBuf, "alkjhkfqulw8fl128lh1oufo183hf1l\0"); // I want to send 32 TO_CLI_BUF_SIZE
    int amountOfBytes = TO_CLI_BUF_SIZE; // anything greater than 1 will not work
    int n = sendto(sockfd, toClientBuf, amountOfBytes, 0, (struct sockaddr *) &clientaddr, clientlen);
    if(n < 0) {
      perror("ERROR in sendto");
      exit(1);
    }

    sleep(1); // sleep 1 sec
  }

  return 0;

}

client.cpp:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <ncurses.h>
#include <sys/epoll.h>
#include <sys/fcntl.h>
#define FROM_SER_BUF_SIZE 32
#define TO_SER_BUF_SIZE 8

int main(int argc, char **argv){

  int sockfd, portno, n;
  socklen_t serverlen;
  struct sockaddr_in serveraddr;
  struct hostent *server;
  char *hostname;

  char toServerBuf[TO_SER_BUF_SIZE];
  char fromServerBuf[FROM_SER_BUF_SIZE];

  if (argc != 3) {
    perror("usage: filename <hostname> <port>\n");
    exit(0);
  }
  hostname = argv[1];
  portno = atoi(argv[2]);

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd < 0) {
    perror("ERROR: opening sockets\n");
    exit(0);
  }

  server = gethostbyname(hostname);
  if (server == NULL) {
    fprintf(stderr,"ERROR, no such host as %s\n", hostname);
    exit(0);
  }

  bzero((char *) &serveraddr, sizeof(serveraddr));
  serveraddr.sin_family = AF_INET;
  bcopy((char *)server->h_addr, 
  (char *)&serveraddr.sin_addr.s_addr, server->h_length);
  serveraddr.sin_port = htons(portno);
  serverlen = sizeof(serveraddr);
  bzero(toServerBuf, TO_SER_BUF_SIZE);
  n = sendto(sockfd, toServerBuf, TO_SER_BUF_SIZE, 0, ( struct sockaddr *) &serveraddr, serverlen);
  if (n < 0){
    perror("ERROR: sendto");
    exit(0);
  }

  if(connect(sockfd, (struct sockaddr *)&serveraddr, serverlen) < 0) { 
    printf("\n Error : Connect Failed \n"); 
    exit(0); 
  } 
  fcntl(sockfd, F_SETFL, O_NONBLOCK); 
  nodelay(stdscr, true);
  fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); 
  initscr();
  noecho();
  int keyboardtick = 0;
  int servertick = 0;
  int ep = epoll_create1(0);
  struct epoll_event e1,e2, e[2]; // e1 for serverfd, e2 for stdin
  memset(&e1, 0, sizeof(struct epoll_event));
  e1.events = EPOLLIN; 
  e1.data.fd = sockfd;
  epoll_ctl(ep, EPOLL_CTL_ADD, sockfd, &e1); 
  memset(&e2, 0, sizeof(struct epoll_event));
  e2.events = EPOLLIN; 
  e2.data.fd = STDIN_FILENO;
  epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &e2);

  mvprintw(0,0,"ticks from server: %d",servertick);
  mvprintw(2,0,"ticks from keyboard: %d",keyboardtick);
  while (1){ 
    int n = epoll_wait(ep, e, 2, -1);
    for(int i = 0; i < n; i++){
      if (e[i].data.fd == sockfd) { // from server
        //
        bzero(fromServerBuf, FROM_SER_BUF_SIZE);
        n = recvfrom(sockfd, fromServerBuf, FROM_SER_BUF_SIZE, 0,( struct sockaddr *) &serveraddr, &serverlen);
        if(n < 0) {
          perror("ERROR in recv");
          exit(1);
        }
        servertick+=n;
        mvprintw(0,0,"ticks from server: %d",servertick);
      }else if(e[i].data.fd == STDIN_FILENO){
        char c = getch();
        keyboardtick++;
        mvprintw(2,0,"ticks from keyboard: %d",keyboardtick);
      }
    }
    refresh();
  }


  endwin();

  return 0;
}

1 Ответ

0 голосов
/ 23 декабря 2019
e1.events = EPOLLIN|EPOLLET;

Вы выбрали использование epoll событий, запускаемых по фронту, с этим параметром.

Страница руководства epoll посвящена длительному обсуждению того, что это значит, и как использовать края. правильно сработало epoll событий. Вы должны просмотреть это описание.

Давайте продолжим:

    int n = epoll_wait(ep, e, 2, -1);

// ...
        n = recvfrom(sockfd, fromServerBuf, FROM_SER_BUF_SIZE, 0,( struct sockaddr *) &serveraddr, &serverlen);

Если epoll был вызван этим сокетом, получившим более FROM_SER_BUF_SIZE байт, это будет считывать только первый FROM_SER_BUF_SIZEбайт - другими словами, отправитель был достаточно быстрым, чтобы отправить достаточно данных, он будет отлично читать первые FROM_SER_BUF_SIZE байт. Когда он вернется к epoll_wait (), он снова будет ждать. Даже если на сокете есть больше данных для чтения. Вот как срабатывают события epoll. Вы описываете события epoll, инициируемые ребром, ниже:

Проблема в том, что после первого сообщения с сервера epoll больше не инициируется сообщениями с сервера. Кроме того, каждый раз, когда я нажимаю клавишу около 10 раз, для sockfd запускается epoll и читается одна пачка символов (даже если сервер уже отправил много сообщений).

Исправить. Ваш epoll сработал по краю. Показанная логика потребляет только первую дейтаграмму и снова вызывает epoll, даже если в сокете больше непрочитанных дейтаграмм.

Что еще больше запутывает меня, так это то, что я посылаю только один символ за раз,Эполл способен правильно реагировать. Все, что больше одного, будет иметь тот же результат, что и раньше (epoll не реагирует).

Исправьте снова. Если отправляется только одно сообщение, оно обрабатывается с помощью показанной логики, а затем epoll ждет снова.

Инициируемый фронтом epoll будет ожидать, пока событие не произойдет в сокете снова. Полная остановка. Конец истории. Если событие уже произошло в сокете, эполл, инициируемый ребром, будет ждать, а не возвращаться немедленно. TLDR: если в сокете есть непрочитанные данные, epoll_wait ing для POLLIN не вернется немедленно. Возвращается только после получения дополнительных данных. Если когда-нибудь. Именно так работают epoll с включенным фронтом.

Если ваше намерение состоит в использовании логики epoll с инициируемым фронтом, на странице руководства объясняется, что вы должны использовать неблокирующие сокеты, и снова вызывайте epoll_waitтолько после того, как сокет будет полностью пуст или заполнен (другими словами, периодически повторяйте циклическое чтение или запись, пока не сможете).

Или не используйте инициируемые фронтом события epoll. Однако вы должны использовать неблокирующие сокеты даже с незапущенными epoll с. Вам придется углубиться в страницы справочника, но вы найдете epoll с poll и poll с select, где вы найдете следующий драгоценный камень:

   Under Linux, select() may report a socket file descriptor as "ready for
   reading", while nevertheless a subsequent read blocks.  This could  for
   example  happen  when  data  has arrived but upon examination has wrong
   checksum and is discarded.  There may be other circumstances in which a
   file  descriptor is spuriously reported as ready.  Thus it may be safer
   to use O_NONBLOCK on sockets that should not block.

Итак, на самом деле, для максимальной переносимости оказывается, что не имеет большого значения, используете ли вы эпгол, инициируемый фронтом или уровнем. Чтобы избежать погони за призраками, рекомендуется использовать (основываясь на имеющейся документации) всегда использовать неблокирующие сокеты с select / poll / epoll и продолжать толкать лопату вперед до тех пор, пока не кончится снег на лопату, и толькозатем вызовите выбор / опрос / epoll.

...