libevent evhttp使用

一、libevent evhttp使用
1、基本流程
http服务端使用到的借口函数及流程如下
1)、创建event_base和evhttp
struct event_base *event_base_new(void);
struct evhttp *evhttp_new(struct event_base *base);
2)、绑定地址和端口
int evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port);
3)、设置处理函数
void evhttp_set_gencb(struct evhttp *http,
    void (*cb)(struct evhttp_request *, void *), void *arg);
4)、派发事件循环
int event_base_dispatch(struct event_base *);

代码:

#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 
#include 

void HttpGenericCallback(struct evhttp_request* request, void* arg)
{
    const struct evhttp_uri* evhttp_uri = evhttp_request_get_evhttp_uri(request);
    char url[8192];
    evhttp_uri_join(const_cast(evhttp_uri), url, 8192);

    printf("accept request url:%s\n", url);

    struct evbuffer* evbuf = evbuffer_new();
    if (!evbuf)
    {
        printf("create evbuffer failed!\n");
        return ;
    }

    //HTTP header
        evhttp_add_header(request->output_headers, "Server", "myhttp v1.0");
        evhttp_add_header(request->output_headers, "Content-Type", "text/plain; charset=UTF-8");//utf8
        //evhttp_add_header(request->output_headers, "Content-Type", "text/html");
          evhttp_add_header(request->output_headers, "Access-Control-Allow-Origin", "*");//跨域
        evhttp_add_header(request->output_headers, "Connection", "close");

   evbuffer_add_printf(evbuf, "Server response. Your request url is %s", url);
    evhttp_send_reply(request, HTTP_OK, "OK", evbuf);
    evbuffer_free(evbuf);
}

void specific_handler(struct evhttp_request *req, void *arg)
{
}

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 error:%s\n", argv[1]);
        return 1;
    }

    struct event_base* base = event_base_new();
    if (!base)
    {
        printf("create event_base failed!\n");
        return 1;
    }

    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! port:%d\n", port);
        return 1;
    }
    int http_option_timeout = 120; //in seconds
    evhttp_set_timeout(http, http_option_timeout);
    //evhttp_set_allowed_methods( http , EVHTTP_REQ_GET);
        //指定generic callback
    evhttp_set_gencb(http, HttpGenericCallback, NULL);
    //Set a callback for a specified URI
    evhttp_set_cb(http, "/spec", specific_handler, NULL);

    event_base_dispatch(base);
    evhttp_free(http);
    return 0;
}

多线程的Http Server
两种方式:
1、启多个线程,每个子线程持有一个event_base和evhttp,接受请求和处理业务逻辑在同一个子线程中。
2、主线程持有一个event_base和evhttp,主线程接受到请求放入队列,子线程中只处理业务逻辑。
在上面的Http Server中,处理Http请求的回调函数generic_handler和定时器读取文件的回调函数read_file_timer_cb都在同一个event_base的dispatch中,并且都在同一个进程中,使用多线程可以改善程序的性能,下面是一个来自网络的多线程Http Server:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int httpserver_bindsocket(int port, int backlog);
int httpserver_start(int port, int nthreads, int backlog);
void* httpserver_Dispatch(void *arg);
void httpserver_GenericHandler(struct evhttp_request *req, void *arg);
void httpserver_ProcessRequest(struct evhttp_request *req);
 
int httpserver_bindsocket(int port, int backlog) {
  int r;
  int nfd;
  nfd = socket(AF_INET, SOCK_STREAM, 0);
  if (nfd < 0) return -1;
 
  int one = 1;
  r = setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(int));
 
  struct sockaddr_in addr;
  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(port);
 
  r = bind(nfd, (struct sockaddr*)&addr, sizeof(addr));
  if (r < 0) return -1;
  r = listen(nfd, backlog);
  if (r < 0) return -1;
 
  int flags;
  if ((flags = fcntl(nfd, F_GETFL, 0)) < 0
      || fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0)
    return -1;
 
  return nfd;
}
 
int httpserver_start(int port, int nthreads, int backlog) {
  int r, i;
  int nfd = httpserver_bindsocket(port, backlog);
  if (nfd < 0) return -1;
  pthread_t ths[nthreads];
  for (i = 0; i < nthreads; i++) {
    struct event_base *base = event_init();
    if (base == NULL) return -1;
    struct evhttp *httpd = evhttp_new(base);
    if (httpd == NULL) return -1;
    r = evhttp_accept_socket(httpd, nfd);
    if (r != 0) return -1;
    evhttp_set_gencb(httpd, httpserver_GenericHandler, NULL);
    r = pthread_create(&ths[i], NULL, httpserver_Dispatch, base);
    if (r != 0) return -1;
  }
  for (i = 0; i < nthreads; i++) {
    pthread_join(ths[i], NULL);
  }
}
 
void* httpserver_Dispatch(void *arg) {
  event_base_dispatch((struct event_base*)arg);
  return NULL;
}
 
void httpserver_GenericHandler(struct evhttp_request *req, void *arg) {
      httpserver_ProcessRequest(req);
}
 
void httpserver_ProcessRequest(struct evhttp_request *req) {
    struct evbuffer *buf = evbuffer_new();
    if (buf == NULL) return;
    
    //here comes the magic
}
 
int main(void) {
    httpserver_start(80, 10, 10240);
}

3)关键函数
获取客户端请求的URI
使用req->uri或使用函数const char *evhttp_request_uri(struct evhttp_request *req);//即(evhttp_request_uri(req);)。

对获取的URI进行解析和其他操作
使用函数void evhttp_parse_query(const char *uri, struct evkeyvalq *args);
可对uri的参数进行解析,结果保存在struct evkeyvalq的key-value pairs中,例如:
    char *uri = "http://foo.com/?id=1&infor=hello";
    struct evkeyvalq args;
    evhttp_parse_query(uri, &args);
    //然后通过evhttp_find_header等函数获取各个参数及对应的值
    evhttp_find_header(&args, "id"); //得到test
    evhttp_find_header(&args, "infor"); //得到some thing  
如下两个函数对URI进行encode和decode:
    char *evhttp_encode_uri(const char *uri);
    char *evhttp_decode_uri(const char *uri);
URI encode的结果是所有非alphanumeric及-_的字符都被类似于%和一个2位16进制字符替换(其中空格被+号替换)。如上两个函数返回的字符串需要free掉。 
Escape特殊的HTML字符
char *evhttp_htmlescape(const char *html);
特殊字符:&被替换为&;”被替换为";’被替换为'; <被替换为<;>被替换为>。该函数返回的字符串需要free掉。
处理HTTP headers相关的函数 
HTTP headers保存在req的input_headers中,这个是struct evkeyvalq 的结构体(key-value pairs),使用如下函数可对其进行修改:
const char *evhttp_find_header(const struct evkeyvalq *, const char *);
int evhttp_remove_header(struct evkeyvalq *, const char *);
int evhttp_add_header(struct evkeyvalq *, const char *, const char *);
void evhttp_clear_headers(struct evkeyvalq *);
设定只识别get请求,
evhttp_set_allowed_methods( httpd , EVHTTP_REQ_GET);
设置后,只处理get请求,其他请求返回405 Method not allowed,我自己使用的时候发现返回的是501 Not Implemented 
请求类型如下:
enum evhttp_cmd_type {
    EVHTTP_REQ_GET     = 1 << 0,
    EVHTTP_REQ_POST    = 1 << 1,
    EVHTTP_REQ_HEAD    = 1 << 2,
    EVHTTP_REQ_PUT     = 1 << 3,
    EVHTTP_REQ_DELETE  = 1 << 4,
    EVHTTP_REQ_OPTIONS = 1 << 5,
    EVHTTP_REQ_TRACE   = 1 << 6,
    EVHTTP_REQ_CONNECT = 1 << 7,
    EVHTTP_REQ_PATCH   = 1 << 8
};

#define  BUF_MAX   2048                    //最大BUF数据长度
//解析post请求数据
void get_post_message(char *buf, struct evhttp_request *req)
{
    size_t post_size = 0;

    post_size = evbuffer_get_length(req->input_buffer);//获取数据长度
    printf("====line:%d,post len:%d\n", __LINE__, post_size);
    if (post_size <= 0)
    {
        printf("====line:%d,post msg is empty!\n", __LINE__);
        return;
    }
    else
    {
        size_t copy_len = post_size > BUF_MAX ? BUF_MAX : post_size;
        printf("====line:%d,post len:%d, copy_len:%d\n", __LINE__, post_size, copy_len);
        memcpy(buf, evbuffer_pullup(req->input_buffer, -1), copy_len);
        buf[post_size] = '\0';
        printf("====line:%d,post msg:%s\n", __LINE__, buf);
    }
}


//处理post请求
void http_handler_testpost_msg(struct evhttp_request *req, void *arg)
{
    if (req == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "input param req is null.\n");
        return;
    }

    char buf[BUF_MAX] = { 0 };
    get_post_message(buf, req);//获取请求数据,一般是json格式的数据
    if (buf == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "get_post_message return null.\n");
        return;
    }
    else
    {
        //可以使用json库解析需要的数据
        printf("====line:%d,request data:%s", __LINE__, buf);
    }

    //回响应
    struct evbuffer *retbuff = NULL;
    retbuff = evbuffer_new();
    if (retbuff == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "retbuff is null.\n");
        return;
    }
    evbuffer_add_printf(retbuff, "Receive post request,Thamks for the request!\n");
    evhttp_send_reply(req, HTTP_OK, "Client", retbuff);
    evbuffer_free(retbuff);
}

//解析http头,主要用于get请求时解析uri和请求参数
char *find_http_header(struct evhttp_request *req, struct evkeyvalq *params, const char *query_char)
{
    
    if (req == NULL || params == NULL || query_char == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "input params is null.\n");
        return NULL;
    }

    struct evhttp_uri *decoded = NULL;
    char *query = NULL;
    char *query_result = NULL;
    const char *path;
    const char *uri = evhttp_request_get_uri(req);//获取请求uri
    
    if (uri == NULL)
    {
        printf("====line:%d,evhttp_request_get_uri return null\n", __LINE__);
        return NULL;
    }
    else
    {
        printf("====line:%d,Got a GET request for <%s>\n", __LINE__, uri);
    }

    /*
    //解码uri
    decoded = evhttp_uri_parse(uri);
    if (!decoded)
    {
        printf("====line:%d,It's not a good URI. Sending BADREQUEST\n", __LINE__);
        evhttp_send_error(req, HTTP_BADREQUEST, 0);
        return;
    }
    //获取uri中的path部分
    path = evhttp_uri_get_path(decoded);
    if (path == NULL)
    {
        path = "/";
    }
    else
    {
        printf("====line:%d,path is:%s\n", __LINE__, path);
    }

    //获取uri中的参数部分
    query = (char*)evhttp_uri_get_query(decoded);
    if (query == NULL)
    {
        printf("====line:%d,evhttp_uri_get_query return null\n", __LINE__);
        return NULL;
    }*/

    //查询指定参数的值
    evhttp_parse_query_str(query, params);
    query_result = (char*)evhttp_find_header(params, query_char);
    return query_result;
}


//处理get请求
void http_handler_testget_msg(struct evhttp_request *req, void *arg)
{

    if (req == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "input param req is null.\n");
        return;
    }

    char *sign = NULL;
    char *data = NULL;
    struct evkeyvalq sign_params = { 0 };
    sign = find_http_header(req, &sign_params, "sign");//获取get请求uri中的sign参数
    if (sign == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "request uri no param sign.\n");
    }
    else
    {
        printf("====line:%d,get request param: sign=[%s]\n", __LINE__, sign);
    }
    
    data = find_http_header(req, &sign_params, "data");//获取get请求uri中的data参数
    if (data == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "request uri no param data.\n");
    }
    else
    {
        printf("====line:%d,get request param: data=[%s]\n", __LINE__, data);
    }
    printf("\n");

    //回响应
    struct evbuffer *retbuff = NULL;
    retbuff = evbuffer_new();
    if (retbuff == NULL)
    {
        printf("====line:%d,%s\n", __LINE__, "retbuff is null.");
        return;
    }
    evbuffer_add_printf(retbuff, "Receive get request,Thamks for the request!");
    evhttp_send_reply(req, HTTP_OK, "Client", retbuff);
    evbuffer_free(retbuff);
}

注意:
1)使用event_base_loopbreak或event_base_loopexit无法让event_base_dispatch退出循环。解决方法:
要让libevent使用多线程需要在创建event_base之前调用evthread_use_pthreads()。在windows平台下,使用evthread_use_windows_threads。
2)evhttp_set_cb和evhttp_set_gencb区别
设置事件处理函数,evhttp_set_cb针对每一个事件(请求)注册一个处理函数,evhttp_set_gencb函数,是对所有请求设置一个统一的处理函数。
    evhttp_set_cb(http_server, "/post", http_handler_testpost_msg, NULL);
    evhttp_set_cb(http_server, "/get", http_handler_testget_msg, NULL);    

3)“对不完全的类型‘struct evhttp_request’的非法使用”错误解决方法:增加#include "event2/http_struct.h"

#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"

4)在windows中vs编写代码时,引用windows.h、winsock2.h、evhttp.h会出现sockaddr类型重定义问题。解决方法:

#define _WINSOCKAPI_
#include 
#include 

//这样写的作用实际上就是, 告诉windows.h不要包含winsock.h文件了

5)使用libevent处理http请求时,带中文的url乱码。
解决方法:使用evhttp_decode_uri函数对uri路径进行解码,解码出来的是utf-8编码的路径,如果需要将utf-8编码转换成gbk编码。

6)evhttp接受POST数据,网上流传的方法是:char *post_data = (char *) EVBUFFER_DATA(req->input_buffer);
这是一个坑,这里因为evhttp的处理机制问题,他读取的是evhttp预留的整个内存空间的内容,这样就会造成你获取到正确内容后面跟着一堆垃圾内容。普通处理还好,遇到加密解密就容易出错。
获取POST数据正确方法:

int buffer_data_len = EVBUFFER_LENGTH(req->input_buffer);
char *post_data = (char *) malloc(buffer_data_len + 1);
memset(post_data, 0, buffer_data_len + 1);
memcpy(post_data, EVBUFFER_DATA(req->input_buffer), buffer_data_len);
//处理业务
free(post_data);

7)get数据、post数据解析。

//解析URI的参数(即GET方法的参数)

char *decode_uri = strdup((char*) evhttp_request_uri(req));
struct evkeyvalq http_query_get;
evhttp_parse_query(decode_uri, &http_query_get);
free(decode_uri);
const char *http_get_name = evhttp_find_header(&http_query_get, "name");

//解析URI的参数(即POST方法的参数)

//获取POST数据(如果数据是json格式),直接用jsoncpp解析。

//获取POST数据(如果数据是name=value格式),使用vhttp_parse_query解析。
struct evkeyvalq http_query_post;
int buffer_data_len = EVBUFFER_LENGTH(req->input_buffer);
char *decode_post_uri = (char *) malloc(buffer_data_len + 3);
memset(decode_post_uri, 0, buffer_data_len + 3);
char *post_data = decode_post_uri + 2;
memcpy(post_data, EVBUFFER_DATA(req->input_buffer), buffer_data_len);
 //拼凑成get样式
 decode_post_uri[0] = '/';
 decode_post_uri[1] = '?';
 ////////////////////////////////////////
evhttp_parse_query(decode_post_uri, &http_query_post);
const char *http_post_name = evhttp_find_header(&http_query_post, "name");

8)创建evhttp_connection对象,并设置回调函数,这里的回调函数是和连接状态相关的函数。

struct evhttp_connection *evhttp_connection_base_new(struct event_base *base, 
    struct evdns_base *dnsbase, const char *address, unsigned short port);
void evhttp_connection_set_closecb(struct evhttp_connection *evcon,
    void (*)(struct evhttp_connection *, void *), void *);

9)、使用不当导致内存泄露
evhttp当客户端请求到来时, 服务器端调用了evhttp_send_reply_start();表示开始向客户端推送数据(使用 HTTP chunked), 最后当 long-polling 结束时, 应该调用evhttp_send_reply_end();来关闭连接, 释放 libevent 的内存。
不过, 如果客户端提前终止了请求, 会导致什么呢? 会导致连接关闭的回调函数 evhttp_connection_set_closecb() 被调用, 但是, 在这个回调函数里, 我没有调用 evhttp_send_reply_end(), 所以导致了内存泄露。

void On_Close(struct evhttp_connection * conn,void * args)
{
evhttp_send_reply_end((struct evhttp_request *)args);
}

void HttpGenericCallback(struct evhttp_request* req, void* arg)
{
struct bufferevent * bev = evhttp_connection_get_bufferevent(req->evcon);
bufferevent_enable(bev,EV_READ);
evhttp_connection_set_closecb(req->evcon,On_Close,req);
//...
}

非上面原因导致内存泄露解决方法:调用malloc_trim(0);  回收资源。

二、libevent编译
1、Windows下编译libevent及使用
1)下载地址:http://libevent.org/ ,下载版本:libevent-2.0.22-stable.tar.gz
      解压, 目录为...\libevent-2.0.22-stable(自己的目录)
2)修改以下三个文件,添加宏定义:

在以下3个文件开头添加“#define _WIN32_WINNT 0x0500”

libevent-2.0.21-stable\event_iocp.c

libevent-2.0.21-stable\evthread_win32.c

libevent-2.0.21-stable\listener.c

3)如果出现libevent-2.0.22-stable\minheap-internal.h(76) : error C2065: “UINT32_MAX”: 未声明的标识符

在该文件中添加#include “stdint.h”

4)在Makefile.nmake中的CFLAGS中加入/ZI选项,同时去掉/Ox优化选项,这样生成的lib库会带有调试信息。
打开VS2015命令工具,切换到解压后的libevent目录,然后执行nmake /f Makefile.nmake命令进行编译。
先输入cd/d D:\aa_zhj\a_work\projects\libevent\libevent-2.0.22-stable切换目录,然后输入nmake /f Makefile.nmake进行编译。
libevent默认编译为32位lib,若需要编译为64位,则需修改Makefile.nmake文件,在LIBFLAGS处添加MACHINE:X64(LIBFLAGS=/nologo/MACHINE:X64 )。
编译成功后再libevent-2.0.22-stable目录下生成三个lib文件:
libevent.lib、libevent_core.lib、libevent_extras.lib

VS2015下使用lib
环境配置:
项目下建一个Lib目录,将上面三个lib文件copy到该目录下。
新建一个Include目录,将libevent-2.0.21-stable\include文件夹下的所有内容和WIN32-Code文件夹下的所有内容拷贝到新建的include目录下,两个event2目录下的文件可合并在一起。
项目属性设置
包含目录,添加上面的Include目录;
库目录,添加上面的Lib目录;
连接器输入:ws2_32.lib;wsock32.lib;libevent.lib;libevent_core.lib;libevent_extras.lib;
//初始化winsocket
#ifdef WIN32
   WSADATA wsaData;
    if(WSAStartup(MAKEWORD(2,2) , &wsaData) != 0) 
    {
        return -1;
    }
#endif
//和释放
#ifdef WIN32
    WSACleanup();  
#endif   

2、linux下编译libevent
https://blog.csdn.net/littlebeat123/article/details/74010877

参考文章:
https://blog.csdn.net/xuleisdjn/article/details/78500055

你可能感兴趣的:(网络编程)