我正在以我认为是 TCP 套接字的典型方式使用 epoll(主要基于此示例,但稍微适应了 C++);一个主侦听套接字绑定到该端口,并且每个新的连接套接字(来自 accept())也被添加以在准备接收 recv() 时发出警报。我创建了一个测试脚本,基本上用连接和发送/接收来锤击它。当任何单个客户端连接时,它将无休止地完美运行。
但是,添加第二个同时测试客户端将导致其中一个挂起并失败。经过几天的调试,我终于决定让它把它正在使用的套接字 ID 吐出到一个文件中,我对我发现的东西感到困惑。
当一个脚本启动时,我只得到一个流,在这种情况下是 6。然而,当第二个脚本启动时,我得到一个 7 流。只有7。它保持在 7,专门与第二个客户端通信,完全忽略第一个,直到第一个超时并关闭。(然后,当客户端 2 重新连接时,它会获得 ID 6。)
值得注意的是,这个测试脚本没有使用持久连接,它会在来回几次消息后断开连接并重新连接(为了更准确的模拟)。但即使如此,客户端 1 也被忽略。如果我将超时设置得足够高,客户端 2 实际上有时间退出,它仍然不会从客户端 1 恢复,因为它等待的任何东西都会丢失。
这是正常行为吗,epoll(或一般的套接字)在新任务出现时完全放弃之前的任务?有什么我必须指定的选项吗?
编辑:这是我可以展示的尽可能多的代码;我不一定期望“这是你做错了”,更多的是“这些是一些会破坏/修复类似情况的东西”。
#define EVENTMODE (EPOLLIN | EPOLLET | EPOLLRDHUP | EPOLLHUP)
#define ERRCHECK (EPOLLERR | EPOLLHUP | EPOLLRDHUP)
//Setup event buffer:
struct epoll_event* events = (epoll_event*)calloc(maxEventCount, sizeof(event));
//Setup done, main processing loop:
int iter, eventCount;
while (1) {
//Wait for events indefinitely:
eventCount = epoll_wait(pollID, events, maxEventCount, -1);
if (eventCount < 0) {
syslog(LOG_ERR, "Poll checking error, continuing...");
continue;
}
for (iter = 0; iter<eventCount; ++iter) {
int currFD = events[iter].data.fd;
cout << "Working with " << events[iter].data.fd << endl;
if (events[iter].events & ERRCHECK) {
//Error or hangup:
cout << "Closing " << events[iter].data.fd << endl;
close(events[iter].data.fd);
continue;
} else if (!(events[iter].events & EPOLLIN)) {
//Data not really ready?
cout << "Not ready on " << events[iter].data.fd << endl;
continue;
} else if (events[iter].data.fd == socketID) {
//Event on the listening socket, incoming connections:
cout << "Connecting on " << events[iter].data.fd << endl;
//Set up accepting socket descriptor:
int acceptID = accept(socketID, NULL, NULL);
if (acceptID == -1) {
//Error:
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
//NOT just letting us know there's nothing new:
syslog(LOG_ERR, "Can't accept on socket: %s", strerror(errno));
}
continue;
}
//Set non-blocking:
if (setNonBlocking(acceptID) < 0) {
//Error:
syslog(LOG_ERR, "Can't set accepting socket non-blocking: %s", strerror(errno));
close(acceptID);
continue;
}
cout << "Listening on " << acceptID << endl;
//Add event listener:
event.data.fd = acceptID;
event.events = EVENTMODE;
if (epoll_ctl(pollID, EPOLL_CTL_ADD, acceptID, &event) < 0) {
//Error adding event:
syslog(LOG_ERR, "Can't edit epoll: %s", strerror(errno));
close(acceptID);
continue;
}
} else {
//Data on accepting socket waiting to be read:
cout << "Receive attempt on " << event.data.fd << endl;
cout << "Supposed to be " << currFD << endl;
if (receive(event.data.fd) == false) {
sendOut(event.data.fd, streamFalse);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:代码已经修改,边缘触发的删除确实会阻止 epoll 锁定到一个客户端。它仍然存在客户端未接收数据的问题;正在调试以查看是同一问题还是其他问题。
编辑:在不同的西装中似乎是相同的错误。它确实尝试在第二个套接字上接收,但进一步的日志记录报告说它实际上几乎每次都命中 EWOULDBLOCK。有趣的是,日志报告的活动比保证的要多得多 - 超过 150,000 行,而我预计大约有 60,000 行。删除所有“Would block”行将其减少到我期望的数量……瞧,结果行创建了完全相同的模式。将边缘触发重新置于停止状态会阻止行为,显然可以防止它无缘无故地尽可能快地旋转轮子。仍然没有解决原来的问题。
编辑:只是为了涵盖我的基础,我想我会在发送端做更多的调试,因为挂起的客户端显然正在等待它永远不会收到的消息。但是,我可以确认服务器对其处理的每个请求都发送响应;挂起客户端的请求完全丢失,因此从未响应。
我还确保我的接收循环读取直到它实际命中 EWOULDBLOCK(这通常是不必要的,因为我的消息头的前两个字节包含消息大小),但它没有改变任何东西。
'另一个编辑:我可能应该澄清一下,该系统使用请求/回复格式,并且接收、处理和发送都是一次性完成的。正如您可能猜到的,这需要读取接收缓冲区直到它为空,这是边缘触发模式的主要要求。如果收到的消息不完整(这不应该发生),服务器基本上会向客户端返回 false,虽然从技术上讲,错误仍然允许客户端继续执行另一个请求。
调试已确认挂起的客户端将发出请求,并等待响应,但该请求永远不会触发 epoll 中的任何内容 - 它在第二个客户端连接后完全忽略第一个客户端。
我也删除了接受后立即接收的尝试;在十万次尝试中,它一次都没有准备好。
更多编辑:很好,很好 - 如果有一件事可以促使我从事任意任务,那就是质疑我的能力。所以,在这里,一切都一定出错的函数:
bool receive(int socketID)
{
short recLen = 0;
char buff[BUFFERSIZE];
FixedByteStream received;
short fullSize = 0;
short diff = 0;
short iter = 0;
short recSoFar = 0;
//Loop through received buffer:
while ((recLen = read(socketID, buff, BUFFERSIZE)) > 0) {
cout << "Receiving on " << socketID << endl;
if (fullSize == 0) {
//We don't know the size yet, that's the first two bytes:
fullSize = ntohs(*(uint16_t*)&buff[0]);
if (fullSize < 4 || recLen < 4) {
//Something went wrong:
syslog(LOG_ERR, "Received nothing.");
return false;
}
received = FixedByteStream(fullSize);
}
diff = fullSize - recSoFar;
if (diff > recLen) {
//More than received bytes left, get them all:
for (iter=0; iter<recLen; ++iter) {
received[recSoFar++] = buff[iter];
}
} else {
//Less than or equal to received bytes left, get only what we need:
for (iter=0; iter<diff; ++iter) {
received[recSoFar++] = buff[iter];
}
}
}
if (recLen < 0 && errno == EWOULDBLOCK) {
cout << "Would block on " << socketID << endl;
}
if (recLen < 0 && errno != EWOULDBLOCK) {
//Had an error:
cout << "Error on " << socketID << endl;
syslog(LOG_ERR, "Connection receive error: %s", strerror(errno));
return false;
} else if (recLen == 0) {
//Nothing received at all?
cout << "Received nothing on " << socketID << endl;
return true;
}
if (fullSize == 0) {
return true;
}
//Store response, since it needs to be passed as a reference:
FixedByteStream response = process(received);
//Send response:
sendOut(socketID, response);
return true;
}
Run Code Online (Sandbox Code Playgroud)
如您所见,它在遇到错误后无法循环。我可能不会经常使用 C++,但我已经编码了足够长的时间来在寻求帮助之前检查此类错误。
bool sendOut(int socketID, FixedByteStream &output)
{
cout << "Sending on " << socketID << endl;
//Send to socket:
if (write(socketID, (char*)output, output.getLength()) < 0) {
syslog(LOG_ERR, "Connection send error: %s", strerror(errno));
return false;
}
return true;
}
Run Code Online (Sandbox Code Playgroud)
如果它是 EWOULDBLOCK 呢?就像我的主板融化一样 - 我会修复它。但它还没有发生,所以我不打算修复它,我只是确保我知道它是否需要修复。
不,process() 不对套接字做任何事情,它只接受并返回一个固定长度的字符数组。同样,该程序与一个客户端完美配合,而不是两个或更多客户端。
最后编辑:经过更多的调试,我找到了问题的根源。我会继续回答自己。
event.data.fd?你为什么要尝试使用它?events[iter].data.fd是具有您想要获得的价值的一个。您可能希望更清楚地命名变量,以避免将来出现此问题,这样就不会浪费每个人的时间。这显然不是epoll的问题。