epoll未由服务器发送给客户端的消息触发

问题描述 投票:0回答:1

我编写了一些客户端代码,用于监视键盘按下情况以及服务器向其发送消息的sockfd。问题是来自服务器的第一条消息之后,epoll不再由来自服务器的消息触发。另外,每次我输入大约10次按键时,都会为sockfd触发epoll并读取一包字符(即使服务器已经发送了许多消息)。让我感到困惑的是,如果我一次只发送一个字符,epoll就能做出正确的反应。超过一个的结果将与以前相同(epoll不反应)。

编辑:我意识到,如果将STDIN_FILENO设置为非阻塞,我将在适当的时间从服务器获取消息。但是,该程序也将进入无限循环,始终触发STDIN_IN。我想现在的问题是如何正确地将epoll与ncurses结合使用,以使我们不会陷入无限循环。

这是我的代码:

使用方法:

  1. clang ++ client.cpp-导致-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;
}
c++ sockets udp epoll
1个回答
0
投票
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 C0]个字节就可以了。当它返回到epoll_wait()时,它将再次等待。即使套接字上还有更多数据要读取。这就是边沿触发的epoll事件的工作方式。您正在以下描述边缘触发的epoll事件:

问题是服务器发出第一条消息后,epoll否来自服务器的消息触发的时间更长。另外,每次我输入大约10次按键,sockfd触发了epoll,其中一个读取字符包(即使服务器已发送)很多消息)。

正确。您的epoll被边沿触发。所显示的逻辑仅使用第一个数据报,即使套接字有更多未读数据报也再次调用epoll

让我感到困惑的是,如果我只在时间,epoll能够做出适当的反应。超过一个,将结果与之前相同(epoll不反应)。

再次纠正。如果仅发送一条消息,则该消息将由显示的逻辑处理,然后epoll再次等待。

边沿触发的epoll将等待,直到事件再次在套接字上发生。句号故事结局。如果套接字上已发生该事件,则边缘触发的epoll将等待而不是立即返回。 TLDR:如果套接字中有未读的数据,epoll_waitPOLLIN ing将不会立即返回。仅在收到更多数据后返回。如果曾经。这就是边缘触发epoll的工作方式。

[如果您打算使用边沿触发的epoll逻辑,则手册页说明您必须使用非阻塞套接字,并且仅在套接字完全为空或已满(即,重复循环阅读或编写内容,直到不能这样做为止。

或者,不要使用边沿触发的epoll_wait事件。但是,即使在无边沿触发的epoll的情况下,您should仍应使用非阻塞套接字。您必须深入阅读手册页,但是您会发现epoll包含epoll的内容,以及poll包含poll的内容,您会在其中找到以下宝石:

select

因此,实际上,对于最大的可移植性,事实证明,使用边缘触发的epoll还是水平触发的epoll并不重要。为避免追赶鬼,最佳实践似乎是(根据可用的文档),始终将非阻塞式套接字与select / poll / epoll一起使用,并继续向前推动铲子,直到用完铲子的雪为止然后召集一次选择/投票/投票。

© www.soinside.com 2019 - 2024. All rights reserved.