原始问题:我写了一些客户端代码来监视键盘的按下和服务器向其发送消息的sockfd。问题是来自服务器的第一条消息之后,epoll不再由来自服务器的消息触发。另外,每次我输入大约10次按键时,都会为sockfd触发epoll并读取一包字符(即使服务器已经发送了许多消息)。让我感到困惑的是,如果我一次只发送一个字符,epoll就能做出正确的 react 。超过一个的结果将与以前相同(epoll不 react )。
编辑:我意识到,如果将STDIN_FILENO设置为非阻塞,我将在适当的时间从服务器获取消息。但是,该程序也将进入无限循环,并始终触发STDIN_IN。我想现在的问题是如何正确地将epoll与ncurses结合使用,以使我们不会陷入无限循环。
这是我的代码:
如何使用:
我对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_wait
的POLLIN
。仅在收到更多数据后返回。如果曾经。这就是边缘触发的epoll
的工作方式。如果您打算使用沿边沿触发的
epoll
逻辑,则手册页说明您必须使用非阻塞套接字,并且仅在套接字完全为空或已满(换句话说,反复循环读取或写入内容)后再调用epoll_wait
直到你不能)。或者,不要使用边缘触发的
epoll
事件。但是,即使在非边缘触发的epoll
的情况下,您也应使用非阻塞套接字。您将不得不深入阅读手册页,但是您会发现epoll
包含poll
,poll
包含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/