我目前正在用c++构建一个服务器,所以当我没有任何请求时我使用epoll_wait来阻塞。它工作得很好,直到我决定向 epollfd_set 添加一个管道。我不知道 epoll_wait 是否可以管理管道,但由于它是一个文件描述符,我认为它可以。于是我就这么做了,然后奇怪的事情发生了。当 epoll 停止阻塞时,我检查了 epollfd_set 并且没有像这样的 fd 数组:[] = {1,2}, 我得到 [] = {2,2},第一个套接字消失并被 2 取代。我知道 epoll 应该只更改事件而不是 fd。你能告诉我发生了什么事吗?
我在一个简单的主文件中重现了该错误。
#include <iostream>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
bool loop;
struct epoll_event fds[255];
int main()
{
int pipefd[2];
//create a pipe, pipefd[0] is the read fd and pipefd[1] is the write one.
if(pipe(pipefd) == -1)
{
std::cout<<"erreur"<<std::endl;
}
int epollfd = epoll_create1(0);
if (epollfd == -1) {
std::cout<<"erreur"<<std::endl;
}
if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) == -1) {
std::cout<<"erreur"<<std::endl;
}
//adding it to the fds set
fds[0].events = EPOLLIN| EPOLLET;
fds[0].data.fd = pipefd[0];
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, pipefd[0], &fds[0]) == -1) {
std::cout <<"erreur"<<std::endl;
}
int on = 1;
//Bind socket to adress and add some parameters
sockaddr_in bindParams;
bindParams.sin_family =AF_INET;
bindParams.sin_port = htons(2000);
int sock = socket(AF_INET,SOCK_STREAM,0);
inet_aton("127.0.0.1",&bindParams.sin_addr);
bind(sock,(struct sockaddr *)&bindParams,(socklen_t)sizeof(bindParams));
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,(char *)&on,(socklen_t)sizeof(on));
fcntl(sock, F_SETFL, O_NONBLOCK);
//add it to the fds set
fds[1].events = EPOLLIN | EPOLLET;
fds[1].data.fd = sock;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &fds[1]) == -1)
{
std::cout<<"erreur"<<std::endl;
}
listen(fds[1].data.fd, 5);
std::cout << "this is the first fd : " <<fds[0].data.fd<<std::endl;
std::cout << "this is the second fd : " <<fds[1].data.fd<<std::endl;
std::cout <<"before epollwait"<<std::endl;
do {
int fdr = epoll_wait(epollfd, fds, 255, -1);
std::cout <<"after epollwait"<<std::endl;
std::cout << "this is the first fd : " <<fds[0].data.fd<<std::endl;
std::cout << "this is the second fd : " <<fds[1].data.fd<<std::endl;
if(fds[0].events == EPOLLIN)
{
std::cout << "pipe called\n";
}
if(fds[1].events == EPOLLIN)
{
std::cout << "listener called\n";
}
}while(true);
return 0;
};
还要退出 epoll_wait,你必须使用像 netcat 这样的客户端在端口 2000 上发送一些东西。我希望这只是我的理解,这是不好的而不是 epoll_wait 的真正错误。
我希望您能够解决这个恼人的问题。预先感谢。
所示代码对
epoll
事件的使用似乎基于一种误解,即 epoll
事件的功能类似于 poll()
管理其文件描述符数组的方式。这是不真实的。
所示代码仔细初始化
fds[0]
,并将其提供给 EPOLL_CTL_ADD
,初始化 fds[1]
,并将其提供给 EPOLL_CTL_ADD
。然后,所示代码调用 epoll_wait
,并假设 fds[0]
将具有第一个文件描述符的事件,而 fds[1]
将具有任何第二个文件描述符的事件。
这不是
epoll
的工作原理。对于初学者来说,fds[0]
可用于设置由 EPOLL_CTL_ADD
使用的两个文件描述符。 EPOLL_CTL_ADD
吞噬掉你喂给它的每一口食物,然后将其储存起来,安全地保存在内核中。
之后,
epoll_wait
向您抛出事件来自 epoll 集中所有文件描述符,一次一个事件。
epoll_wait
的返回值告诉您传递给epoll_wait
的epoll数组参数中有多少个事件。显示的代码预计总是有两个事件,fds[0]
将是pipefd[0]
的事件,fds[1]
将是sock
的事件:
if(fds[0].events == EPOLLIN)
{
std::cout << "pipe called\n";
}
if(fds[1].events == EPOLLIN)
{
std::cout << "listener called\n";
}
这是错误的。
epoll_wait
可能会返回 1。这意味着 fds[0]
包含一个事件。就是这样。 fds[0].data.fd
告诉您哪个文件描述符生成了事件,它可以是 pipefd[0]
或 sock
。
如果
epoll_wait
返回2,则表示fds[0]
和fds[1]
都有事件,并且任何文件描述符都可以出现在fds[0].data.fd
和fds[1].data.fd
中。
此时,您将被引导查看 epoll(7) 手册页中的“建议使用示例”。请大饱眼福,看看用代码向您解释如何正确执行此操作的示例:
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
您必须捕获 epoll_wait
的返回值。不要忽视它。把它放在你面前,并以敬畏和尊重的态度握住它。因为你需要使用它:
for (n = 0; n < nfds; ++n) {
epoll_wait
的返回值告诉您有多少个事件。
if (events[n].data.fd == listen_sock) {
然后检查每个事件的 data.fd
,它会告诉您该事件针对哪个 文件描述符,然后进行相应操作。