【Networking】libevent的学习笔记

来源:互联网 发布:紫砂壶店铺淘宝排名 编辑:程序博客网 时间:2024/05/17 21:52
一. 学习背景

libevent是一个很有名的开源库了, 在网络通讯方面应用很广。libevent是一个事件触发、异步事件的高性能开源网络库,在Windows、Linux、BSD、Mac OS这些平台上都适用。它内部使用了select、epoll等系统调用,作为底层网络库已被广泛应用。

二. libevent主要特点

1) 基于事件驱动, 高性能
2) 轻量级,专注于网络通信场景
3) 跨平台,支持Windows、Linux、BSD、Mac OS
4) 支持select、poll、epoll、kqueue等多路复用技术

三. 安装libevent

我在linux下安装的是2.1.6-beta版本(下载链接), 详细步骤如下:
   
wget "https://github.com/libevent/libevent/archive/release-2.1.6-beta.tar.gz"
tar -zxvf release-2.1.6-beta.tar.gz
cd libevent-release-2.1.6-beta
./configure
make
make install
注: 2.1.6解压后有可能找不到configure文件, 我借用了2.0.22版本的configure文件, 运行后似乎能正常安装。

四. libevent的主要组件

1) evhttp: HTTP客户端/服务端的逻辑实现
2) evdns: DNS客户端/服务端的逻辑实现
3) evrps: RPC的逻辑实现
4) evutil: 隔离平台差异性的通用功能
5) event/evelibeventnt_base:
     libevent的核心基础;
     事件驱动的平台无关的非阻塞IO抽象API, 通知程序进行套接字的读写操作;
     提供超时处理、系统信号的检测等。
6) bufferevent:
     基于事件的更上层封装;
     除通知套接字可读/写外, 还提供了基于缓冲区的读写功能。
7) evbuffer: 在bufferevent层下实现的缓冲功能,并提供缓冲区下存取操作的对外方法

五. http server/client的C++示例

服务端server.cpp示例
#include <stdlib.h>
#include <stdio.h>

#include "event2/http.h"
#include "event2/event.h"
#include "event2/buffer.h"

#define BUF_LEN 4096

void request_receive_callback(struct evhttp_request* request, void* arg)
{
  const struct evhttp_uri* evhttp_uri = evhttp_request_get_evhttp_uri(request);
  char url[BUF_LEN];
  evhttp_uri_join(const_cast<struct evhttp_uri*>(evhttp_uri), url, BUF_LEN);

  printf("Accept request of url:%s\n", url);
// 创建用于响应请求的对象
  struct evbuffer* evbuf = evbuffer_new();
  if (!evbuf)
  {
  printf("Create evbuffer failed!\n");
  return ;
  }

// 填充响应内容
  evbuffer_add_printf(evbuf, "This is response as request of %s", url);
// 发送响应体
  evhttp_send_reply(request, HTTP_OK, "OK", evbuf);
// 释放buffer
  evbuffer_free(evbuf);
}

int main(int argc, char** argv)
{
  if (argc != 2)
  {
  printf("usage:%s port\n", argv[0]);
  return 1;
  }

  int port = atoi(argv[1]);
  if (port <= 0)
  {
  printf("port invalid:%s\n", argv[1]);
  return 1;
  }

// 初始化event base对象
  struct event_base* base = event_base_new();
  if (!base)
  {
  printf("create event_base failed!\n");
  return 1;
  }

// 初始化 http 请求对象
  struct evhttp* http = evhttp_new(base);
  if (!http)
  {
  printf("create evhttp failed!\n");
  return 1;
  }

// 绑定监听地址与端口
  if (evhttp_bind_socket(http, "0.0.0.0", port) != 0)
  {
  printf("Bind socket failed when listen port:%d\n", port);
  return 1;
  }

// 设置请求处理回调方法
  evhttp_set_gencb(http, request_receive_callback, NULL);
// 派发事件
  event_base_dispatch(base);
  return 0;
}

客户端client.cpp示例
#include "event2/http.h"
#include "event2/http_struct.h"
#include "event2/event.h"
#include "event2/buffer.h"
#include "event2/dns.h"
#include "event2/thread.h"

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/queue.h>
#include <event.h>

void remote_read_callback(struct evhttp_request* remote_rsp, void* arg)
{
  event_base_loopexit((struct event_base*)arg, NULL);
}

// 打印响应头部信息
int read_header_callback(struct evhttp_request* remote_rsp, void* arg)
{
  fprintf(stderr, "< HTTP/1.1 %d %s\n", 
evhttp_request_get_response_code(remote_rsp),
evhttp_request_get_response_code_line(remote_rsp));
  struct evkeyvalq* headers = evhttp_request_get_input_headers(remote_rsp);
  struct evkeyval* header;
  TAILQ_FOREACH(header, headers, next)
  {
  fprintf(stderr, "< %s: %s\n", header->key, header->value);
  }
  fprintf(stderr, "< \n");
  return 0;
}

// 打印响应主题内容
void read_chunked_callback(struct evhttp_request* remote_rsp, void* arg)
{
  char buf[4096];
  struct evbuffer* evbuf = evhttp_request_get_input_buffer(remote_rsp);
  int n = 0;
  while ((n = evbuffer_remove(evbuf, buf, 4096)) > 0)
  {
  fwrite(buf, n, 1, stdout);
  }
printf("\n");
}

void request_error_callback(enum evhttp_request_error error, void* arg)
{
  fprintf(stderr, "Request failed.\n");
  event_base_loopexit((struct event_base*)arg, NULL);
}

void connection_close_callback(struct evhttp_connection* connection, void* arg)
{
  fprintf(stderr, "Connection has been closed.\n");
  event_base_loopexit((struct event_base*)arg, NULL);
}

int main(int argc, char** argv)
{
  if (argc != 2)
  {
  printf("Usage: %s url", argv[1]);
  return 1;
  }
  char* url = argv[1];
  struct evhttp_uri* uri = evhttp_uri_parse(url);
  if (!uri)
  {
  fprintf(stderr, "Parse url failed!\n");
  return 1;
  }

  const char* host = evhttp_uri_get_host(uri);
  if (!host)
  {
  fprintf(stderr, "Parse host failed!\n");
  return 1;
  }

  int port = evhttp_uri_get_port(uri);
  if (port < 0) port = 80;

  const char* request_url = url;
  const char* path = evhttp_uri_get_path(uri);
  if (path == NULL || strlen(path) == 0)
  {
  request_url = "/";
  }

  printf("url:%s host:%s port:%d path:%s request_url:%s\n", url, host, port, path, request_url);

  // 初始化event_base
  struct event_base* base = event_base_new();
  if (!base)
  {
  fprintf(stderr, "Create event base failed!\n");
  return 1;
  }
// 初始化evdns_base_new
  struct evdns_base* dnsbase = evdns_base_new(base, 1);
  if (!dnsbase)
  {
  fprintf(stderr, "Create dns base failed!\n");
  }

// 创建evhttp_request对象,设置返回状态响应的回调函数
  struct evhttp_request* request = evhttp_request_new(remote_read_callback, base);
  evhttp_request_set_header_cb(request, read_header_callback);
  evhttp_request_set_chunked_cb(request, read_chunked_callback);
  evhttp_request_set_error_cb(request, request_error_callback);

  struct evhttp_connection* connection = evhttp_connection_base_new(base, dnsbase, host, port);
  if (!connection)
  {
  fprintf(stderr, "Create evhttp connection failed!\n");
  return 1;
  }
// 创建连接对象成功后, 设置关闭回调函数
  evhttp_connection_set_closecb(connection, connection_close_callback, base);
// 添加http头部
  evhttp_add_header(evhttp_request_get_output_headers(request), "Host", host);
// 发起http请求
  evhttp_make_request(connection, request, EVHTTP_REQ_GET, request_url);
// 派发事件
  event_base_dispatch(base);

  return 0;
}

编译并运行server

g++ server.cpp -L/usr/local/lib -levent -o httpServer
./httpServer 7000

编译并运行client

g++ client.cpp -L/usr/local/lib -levent -o httpClient
./httpClient http://127.0.0.1:7000/

六. 小结

如果用python tornado框架来实现这个功能, 可能就只需很简洁的几行代码。所以关于技术选型的问题, 还是要考虑开发效率与程序性能这二者之间的综合考虑。
0 0
原创粉丝点击