【问题标题】:libev + non-blocking socket continuously invokes callbacklibev + 非阻塞套接字不断调用回调
【发布时间】:2018-02-15 11:22:20
【问题描述】:

我正在使用 libev + 非阻塞套接字向服务器发送请求。我使用 Keep Alive 是因为我需要通过同一连接将未来的请求发送到目的地。

行为 运行程序,它会按预期获取 URL 和日志到控制台。 完成此操作后,等待并且不要按 ctrl+c 退出程序。

预期 应用程序应保持打开状态,因为事件循环正在等待未来的响应,但不应在初始响应后控制台记录任何内容。

实际 让应用程序运行。 30 多秒后,它将开始一次又一次地控制台记录相同的响应而没有结束。

问题 为什么 libev 在未发送新请求且未收到新响应数据时反复调用我的回调(example_cb)?我该如何解决这个问题?

#include <ev.h>
#include <stdio.h>
#include <iostream>
#include <ctype.h>
#include <cstring>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sstream>
#include <fstream>
#include <string>

using namespace std;

void sendRequest(int sockfd)
{
  puts("------");
  puts("sendRequest() was called");
  stringstream ss;
  ss << "GET /posts/11 HTTP/1.1\r\n"
      << "Host: jsonplaceholder.typicode.com\r\n"
      << "Accept: application/json\r\n"
      << "\r\n";
  string request = ss.str();

  if (send(sockfd, request.c_str(), request.length(), 0) != (int)request.length()) {
    cout << "Error sending request." << endl;
    exit(1);
  }
  cout << "Request sent. No err occured." << endl;
}

static void delay_cb(EV_P_ ev_timer *w, int revents)
{
  puts("------");
  puts("delay_cb() was called");
  sendRequest(3);
}


static void example_cb(EV_P_ ev_io *w, int revents)
{
  puts("------");
  puts("example_cb() was called");
  int sockfd = 3;

  size_t len = 80*1024, nparsed; // response must be <= 80 Kb
  char buf[len];
  ssize_t recved;

  recved = recv(sockfd, &buf, len, 0);

  if (recved < 0) {
    perror("recved was <1");
  }

  // don't process keep alives
  if (buf[0] != '\0') {
    std::cout << buf << std::endl;
  }

  // clear buf
  buf[0] = '\0';
  std::cout << "buf after clear attempt: " << buf << std::endl;
}

int example_request()
{
  std::string hostname = "jsonplaceholder.typicode.com";
  int PORT = 80;

  struct sockaddr_in client;
  struct hostent * host = gethostbyname(hostname.c_str());
  if (host == NULL || host->h_addr == NULL) {
    cout << "Error retrieving DNS information." << endl;
    exit(1);
  }

  bzero(&client, sizeof(client));
  client.sin_family = AF_INET;
  client.sin_port = htons( PORT );
  memcpy(&client.sin_addr, host->h_addr, host->h_length);

  // create a socket
  int sockfd = socket(PF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    cout << "Error creating socket." << endl;
    exit(1);
  }
  cout << "Socket created" << endl;

  // enable keep alive
  int val = 1;
  setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof val);

  if (connect(sockfd, (struct sockaddr *)&client, sizeof(client)) < 0) {
    close(sockfd);
    cout << "Could not connect" << endl;
    exit(1);
  }
  cout << "Socket connected" << endl;

  // make non-blocking
  int status = fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
  if (status == -1) {
    perror("ERROR making socket non-blocking");
  }
  std::cout << "Socket set to non-blocking" << std::endl;

  std::cout << "Sockfd is: " << sockfd << std::endl;
  return sockfd;
}

int main(void)
{
  // establish socket connection
  int sockfd = example_request();

  struct ev_loop *loop = EV_DEFAULT;

  ev_io example_watcher;
  ev_io_init(&example_watcher, example_cb, sockfd, EV_READ);
  ev_io_start(loop, &example_watcher);

  // used to send the request 2 sec later
  ev_timer delay_watcher;
  ev_timer_init(&delay_watcher, delay_cb, 2, 0.0);
  ev_timer_start(loop, &delay_watcher);

  ev_run(loop, 0);

  return 0;
}

编辑:根据 cmets 的建议更新代码

【问题讨论】:

  • 可能是因为您收到长度为 0 的 keepalive,并且您将 buf(可能充满垃圾)原样转储到 cout,而没有将终止 null 放在适当的位置或无论如何关心大小存储在那里的有效数据。还有为什么int sockfd = 5;?您不能像那样手动为套接字描述符分配随机数。
  • Sockfd 是 5 只是因为这就是我在最小测试中的情况。以后不会硬编码了
  • 您正在为尚未打开的文件描述符添加观察程序。打开文件(socket),然后添加观察者。
  • @vtt 谢谢。我在使用数据后更新了example_cb 将buf 设置为\0,并添加了对'\0' 的检查以确定是否收到了真实数据;这让我可以正确处理真实数据。您的保活理论似乎是合理的,但是 example_cb 被称为大约 100x/秒,这似乎比收到保活的频率更高。
  • @davmac 谢谢。我更新了main() 以在添加观察者之前创建套接字连接(并且还将我的硬编码 sockfd 更新为 3 以使其在更改后保持工作)。但是example_cb() 仍然被一遍又一遍地调用。

标签: c++ sockets libev


【解决方案1】:

问题的根源是您没有检查对应于对方关闭连接的recved == 0条件。发生这种情况时,操作系统会将套接字设置为“关闭模式”(至少在 linux 下)总是准备好读取,随后对 recv 的调用将总是返回 0。

所以你需要做的是检查那个条件,在文件描述符上调用close(fd);(之前可能是shutdown)和在相关的观察者上调用ev_io_stop。如果你想在那个时候继续,那么你必须打开一个新的套接字和eo_io_start新的观察者。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-20
    • 1970-01-01
    • 2010-10-31
    • 2013-10-15
    • 2012-11-26
    • 1970-01-01
    相关资源
    最近更新 更多