c++ - 如何正确轮询ncurses

标签 c++ sockets udp ncurses epoll

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

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

这是我的代码:

如何使用:

  • clang++ client.cpp-导致-o cli
  • clang++ server.cpp -o ser
  • ./ser 8080
  • 打开另一个终端
  • ./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;
    }
    

    最佳答案

    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事件:

    The problem is after the first message from the server, epoll is no longer triggered by messages from the server. Also, every time I enter key-presses around 10 times, epoll is triggered for sockfd and one pack of characters are read (even though the server has already sent alot of messages).



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

    What adds to my confusion is the if I only send one character at a time, epoll is able to react properly. Anything more than one, will have the same result as before (epoll doesn't react).



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

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

    如果您打算使用沿边沿触发的epoll逻辑,则手册页说明您必须使用非阻塞套接字,并且仅在套接字完全为空或已满(换句话说,反复循环读取或写入内容)后再调用epoll_wait直到你不能)。

    或者,不要使用边缘触发的epoll事件。但是,即使在非边缘触发的epoll的情况下,您也应使用非阻塞套接字。您将不得不深入阅读手册页,但是您会发现epoll包含pollpoll包含select,您将在其中找到以下gem:

       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.
    

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

    关于c++ - 如何正确轮询ncurses,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59449827/

    相关文章:

    c++ - 安装 VS2010 后,如何使用 CMAKE for Windows-SDK 编译器?

    关于数组的 C++ 问题

    java - Spring集成UDP服务器不监听

    c - connect() 失败后,socket 是否无法使用?

    c - 如何了解 UDP 数据包在我的 ubuntu C 程序中被丢弃的位置

    http - 为什么 HTTP 使用 TCP?

    c++ - std::forward 和引用折叠的工作

    C++ 重载函数调用自身的更多参数版本

    node.js - ERR_CERT_INVALID : When trying to connect client using ngx-socket-io to nodejs socketio using HTTPS self-assigned certificate

    python - 如何从 HTTP 服务器下载图像 [Python/sockets]