我编写了一些客户端代码,用于监视键盘按下情况以及服务器向其发送消息的sockfd。问题是来自服务器的第一条消息之后,epoll不再由来自服务器的消息触发。另外,每次我输入大约10次按键时,都会为sockfd触发epoll并读取一包字符(即使服务器已经发送了许多消息)。让我感到困惑的是,如果我一次只发送一个字符,epoll就能做出正确的反应。超过一个的结果将与以前相同(epoll不反应)。
编辑:我意识到,如果将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
C0]个字节就可以了。当它返回到epoll_wait
()时,它将再次等待。即使套接字上还有更多数据要读取。这就是边沿触发的epoll
事件的工作方式。您正在以下描述边缘触发的epoll事件:
问题是服务器发出第一条消息后,epoll否来自服务器的消息触发的时间更长。另外,每次我输入大约10次按键,sockfd触发了epoll,其中一个读取字符包(即使服务器已发送)很多消息)。
正确。您的epoll
被边沿触发。所显示的逻辑仅使用第一个数据报,即使套接字有更多未读数据报也再次调用epoll
。
让我感到困惑的是,如果我只在时间,epoll能够做出适当的反应。超过一个,将结果与之前相同(epoll不反应)。
再次纠正。如果仅发送一条消息,则该消息将由显示的逻辑处理,然后epoll
再次等待。
边沿触发的epoll
将等待,直到事件再次在套接字上发生。句号故事结局。如果套接字上已发生该事件,则边缘触发的epoll将等待而不是立即返回。 TLDR:如果套接字中有未读的数据,epoll_wait
的POLLIN
ing将不会立即返回。仅在收到更多数据后返回。如果曾经。这就是边缘触发epoll
的工作方式。
[如果您打算使用边沿触发的epoll
逻辑,则手册页说明您必须使用非阻塞套接字,并且仅在套接字完全为空或已满(即,重复循环阅读或编写内容,直到不能这样做为止。
或者,不要使用边沿触发的epoll_wait
事件。但是,即使在无边沿触发的epoll
的情况下,您should仍应使用非阻塞套接字。您必须深入阅读手册页,但是您会发现epoll
包含epoll
的内容,以及poll
包含poll
的内容,您会在其中找到以下宝石:
select
因此,实际上,对于最大的可移植性,事实证明,使用边缘触发的epoll还是水平触发的epoll并不重要。为避免追赶鬼,最佳实践似乎是(根据可用的文档),始终将非阻塞式套接字与select / poll / epoll一起使用,并继续向前推动铲子,直到用完铲子的雪为止然后召集一次选择/投票/投票。