libevent高并发网络编程 - 04_libevent实现http服务器
文章目录
1 evhttp简介2 相关的APIevhttp_new()evhttp_free()evhttp_bind_socket()evhttp_set_gencb()evhttp_set_cb()evhttp_request_get_uri()evhttp_request_get_command()evhttp_request_get_input_headers()evhttp_request_get_input_buffer()evhttp_request_get_output_headers()evhttp_add_header()evhttp_request_get_output_buffer()evhttp_send_reply()
http服务器例子http服务器代码index.html文件运行效果
链接:
C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
1 evhttp简介
在libevent中,HTTP的实现主要是通过evhttp模块来完成的。evhttp提供了一个高层次的HTTP服务器接口,可以处理HTTP请求并发送HTTP响应。
在源码中,libevent的HTTP协议处理主要是通过evhttp模块来完成的。当客户端发起一个HTTP请求时,libevent将该请求解析为struct evhttp_request结构体表示,并调用用户设置的请求处理函数进行处理。
struct evhttp_request结构体定义了HTTP请求的各个字段,如请求行、请求头、请求正文等。例如,以下是struct evhttp_request结构体的部分定义:
struct evhttp_request {
int major; // 主版本号
int minor; // 次版本号
enum evhttp_cmd_type type; // 请求方法(GET、POST等)
char *uri; // 请求URI
struct evkeyvalq *input_headers;// 请求头
struct evbuffer *input_buffer; // 请求正文
};
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,
.....
};
当接收到HTTP请求数据时,libevent将会使用一个状态机来逐步解析HTTP请求。在此过程中,它将从输入流中读取一些数据并按照HTTP协议格式解析这些数据,以获得请求的不同部分。具体来说,HTTP请求包括请求行、请求头和请求正文三个部分,每个部分都有其特定的格式。
将HTTP请求解析为struct evhttp_request结构体,libevent就会调用用户设置的请求处理函数来处理该请求。对于GET请求等不包含请求正文的请求,可以直接在请求处理函数中进行处理;对于POST请求等包含请求正文的请求,则需要从struct evhttp_request结构体中获取请求正文数据并进行处理。
总之,libevent使用状态机来逐步解析HTTP请求,并将请求解析为struct evhttp_request结构体表示。这种设计使得libevent能够高效地处理HTTP请求,同时也方便了开发者对HTTP协议进行扩展和定制。
2 相关的API
evhttp_new()
evhttp_new 用于创建和初始化一个 evhttp 结构体,以便用于 HTTP 服务器。 evhttp 结构体表示一个 HTTP 服务器,它可以在一个或多个套接字上监听传入的 HTTP 请求,并将这些请求分派给适当的请求处理程序。
struct evhttp * evhttp_new(struct event_base *base)
base:即指向 `event_base` 结构体的指针,用于处理 HTTP 服务器的事件
evhttp_free()
evhttp_free() 用于释放 evhttp 结构体及其相关资源。调用该函数后,将销毁与 evhttp 相关联的所有信息,包括绑定的套接字以及任何未完成的请求。
void evhttp_free(struct evhttp* http)
evhttp_bind_socket()
evhttp_bind_socket用于将HTTP服务器绑定到指定的IP地址和端口号。它接受一个指向evhttp结构体的指针,一个字符串表示IP地址,以及一个16位整数表示端口号。
int evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port)
参数:
http:指向要绑定的evhttp对象的指针。
address:要绑定的IP地址。可以是一个IPv4或IPv6地址,也可以是一个域名。如果为NULL,则默认绑定到所有可用地址。
port:要绑定的端口号。
evhttp_set_gencb()
evhttp_set_gencb() 是 libevent 库中的一个函数,用于设置通用回调函数,该函数将在 HTTP 请求到达时被调用。
void
evhttp_set_gencb(struct evhttp *http,
void (*cb)(struct evhttp_request *, void *),
void *cbarg)
第一个参数是指向 evhttp 结构体的指针,表示要设置回调函数的 HTTP 服务器;
第二个参数是指向回调函数的指针,该回调函数的原型为 void (*cb)(struct evhttp_request *, void *),其中第一个参数是指向当前 HTTP 请求的指针,第二个参数是传递给 evhttp_set_gencb() 函数的第三个参数;
第三个参数是传递给回调函数的上下文参数 cbarg,它可以是任何数据类型的指针;
当 HTTP 请求到达并且没有与之匹配的特定 URI 或处理程序时,将调用设置的通用回调函数。注意,如果已经为某个 URI 注册了特定的处理程序,则不会调用通用回调函数来处理该 URI 的请求。
evhttp_set_cb()
evhttp_set_cb() 用于为 HTTP 服务器注册特定 URI 的请求处理程序回调函数。
evhttp_set_cb(struct evhttp *http,
const char *uri,
void (*cb)(struct evhttp_request *, void *),
void *cbarg)
第一个参数是指向 evhttp 结构体的指针,表示要设置回调函数的 HTTP 服务器;
第二个参数是字符串类型的 URI,表示要注册回调函数的 URI;
第三个参数是指向回调函数的指针,该回调函数的原型为 void (*cb)(struct evhttp_request *, void *),其中第一个参数是指向当前 HTTP 请求的指针,第二个参数是传递给 evhttp_set_cb() 函数的第四个参数;
四个参数是传递给回调函数的上下文参数 cbarg,它可以是任何数据类型的指针。
当 HTTP 请求到达并且与所注册的 URI 匹配时,将调用设置的回调函数。注意,如果同时为相同的 URI 注册了多个回调函数,则只会调用最后一个注册的回调函数。
evhttp_request_get_uri()
evhttp_request_get_uri() 用于获取 HTTP 请求的 URI(Uniform Resource Identifier)。
const char *
evhttp_request_get_uri(const struct evhttp_request *req) {
if (req->uri == NULL)
event_debug(("%s: request %p has no uri\n", __func__, (void *)req));
return (req->uri); //返回struct evhttp_reques中的URI
}
该函数只有一个参数,即指向 `evhttp_request` 结构体的指针,表示要获取 URI 的 HTTP 请求
函数返回一个字符串类型的指针,指向 HTTP 请求的 URI。URI 是客户端在请求服务器时发送的字符串,该字符串通常包含一个主机名、路径和查询字符串。例如,对于 “http://example.com/foo?bar=baz” 这样的请求,URI 将是 “/foo?bar=baz”。
需要注意的是,返回的指针指向的内存由 evhttp_request 结构体管理,因此不需要手动释放该指针指向的内存。
evhttp_request_get_command()
evhttp_request_get_command() 用于获取 HTTP 请求的方法类型。
enum evhttp_cmd_type
evhttp_request_get_command(const struct evhttp_request *req) {
return (req->type); //返回struct evhttp_reques中请求方法(GET、POST等)
}
函数返回一个枚举类型 evhttp_cmd_type 值,表示 HTTP 请求使用的方法类型。枚举类型包括以下值:
EVHTTP_REQ_GET: 使用 GET 方法。
EVHTTP_REQ_POST: 使用 POST 方法。
EVHTTP_REQ_HEAD: 使用 HEAD 方法。
EVHTTP_REQ_PUT: 使用 PUT 方法。
EVHTTP_REQ_DELETE: 使用 DELETE 方法。
EVHTTP_REQ_OPTIONS: 使用 OPTIONS 方法。
EVHTTP_REQ_TRACE: 使用 TRACE 方法。
EVHTTP_REQ_CONNECT: 使用 CONNECT 方法。
EVHTTP_REQ_PATCH: 使用 PATCH 方法。
evhttp_request_get_input_headers()
evhttp_request_get_input_headers() 用于获取 HTTP 请求中的输入头部。输入头部是从客户端发送到服务器的数据块,其中包含一些元信息,例如请求的内容类型、长度或来源等。
struct evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req)
{
return (req->input_headers); // 返回struct evhttp_reques中请求头
}
函数返回一个指向 evkeyvalq 结构体的指针,该结构体代表了 HTTP 请求的输入头部。 该结构体是一个键值对列表,其中每个键都是头部的名称,每个值都是头部的值。
evhttp_request_get_input_buffer()
evhttp_request_get_input_buffer() 用于获取 HTTP 请求的输入缓冲区。输入缓冲区是从客户端发送到服务器的数据块,其中包含了 HTTP 请求体以及可能的其他数据。
struct evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req)
{
return (req->input_buffer); // 返回struct evhttp_reques中请求正文
}
函数返回一个指向 evbuffer 结构体的指针,该结构体代表了 HTTP 请求的输入缓冲区。
evhttp_request_get_output_headers()
evhttp_request_get_output_headers() 用于获取 HTTP 请求的输出头部。输出头部是从服务器发送到客户端的元信息块,其中包含了一些关于响应的信息,例如响应状态码、内容类型或其他自定义头部等。
struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req)
{
return (req->output_headers);
}
函数返回一个指向 evkeyvalq 结构体的指针,该结构体代表了 HTTP 请求的输出头部。 该结构体是一个键值对列表,其中每个键都是头部的名称,每个值都是头部的值。
evhttp_add_header()
evhttp_add_header()用于向 evkeyvalq 结构体表示的头部列表添加一个新的键值对。
int evhttp_add_header(struct evkeyvalq *headers,
const char *key,
const char *value)
第一个参数是指向 evkeyvalq 结构体的指针,表示要添加头部的头部列表;
第二个参数是字符串类型的键名,表示要添加的头部的名称;
第三个参数是字符串类型的键值,表示要添加的头部的值。
需要注意的是,如果某个键已经存在于头部列表中,则会将现有键值对的值替换为新的值。如果要添加多个具有相同名称的头部,请使用逗号分隔它们的值。例如,可以使用"Accept-Encoding: gzip, deflate"来同时添加两个Accept-Encoding头部。
evhttp_request_get_output_buffer()
evhttp_request_get_output_buffer() 用于获取 HTTP 请求的输出缓冲区。输出缓冲区是服务器发送到客户端的数据块,其中包含了 HTTP 响应体以及可能的其他数据。
struct evbuffer *evhttp_request_get_output_buffer(struct evhttp_request *req)
{
return (req->output_buffer);
}
函数返回一个指向 evbuffer 结构体的指针,该结构体代表了 HTTP 请求的输出缓冲区。可以使用 evbuffer_add() 函数将内容添加到缓冲区中。
evhttp_send_reply()
evhttp_send_reply() 用于向客户端发送 HTTP 响应。该函数使用指定的状态码、原因短语和响应正文来构造 HTTP 响应。
void
evhttp_send_reply(struct evhttp_request *req,
int code,
const char *reason,
struct evbuffer *databuf)
第一个参数是指向 evhttp_request 结构体的指针,表示要发送响应的 HTTP 请求;
第二个参数是整数类型的状态码,表示响应的状态码;
第三个参数是字符串类型的原因短语,表示状态码的原因短语;
第四个参数是指向 evbuffer 结构体的指针,表示响应正文的内容。
需要注意的是,在调用 evhttp_send_reply() 函数之前,必须确保已经设置了响应状态码和必要的响应头部,并且已经将所有响应数据写入到输出缓冲区中。
http服务器例子
通过浏览器发送http请求,并解析url和http头部,并响应浏览器对文件和图片的请求。
http服务器代码
#include
#include
#include
#include
#include
#include
#ifndef _WIN32
#include
#endif
#include
#include
using namespace std;
#define WEBROOT "."
#define DEFAULTINDEX "index.html"
void http_cb(struct evhttp_request *request, void *arg)
{
cout << "http_cb" << endl;
//1 获取浏览器的请求信息
//uri
const char *uri = evhttp_request_get_uri(request);
cout << "uri:" << uri << endl;
//请求类型 GET POST
string cmdtype;
switch (evhttp_request_get_command(request))
{
case EVHTTP_REQ_GET:
cmdtype = "GET";
break;
case EVHTTP_REQ_POST:
cmdtype = "POST";
break;
}
cout << "cmdtype:" << cmdtype << endl;
// 消息报头
evkeyvalq *headers = evhttp_request_get_input_headers(request);
cout << "====== headers ======" << endl;
for (evkeyval *p = headers->tqh_first; p != NULL; p = p->next.tqe_next)
{
cout << p->key << ":" << p->value << endl;
}
// 请求正文 (GET为空,POST有表单信息 )
evbuffer *inbuf = evhttp_request_get_input_buffer(request);
char buf[1024] = { 0 };
cout << "======= Input data ======" << endl;
while (evbuffer_get_length(inbuf))
{
int n = evbuffer_remove(inbuf, buf, sizeof(buf) - 1);
if (n > 0)
{
buf[n] = '\0';
cout << buf << endl;
}
}
//2 回复浏览器
//状态行 消息报头 响应正文 HTTP_NOTFOUND HTTP_INTERNAL
string filepath = WEBROOT;
filepath += uri;
if (strcmp(uri, "/") == 0)
{
//默认加入首页文件
filepath += DEFAULTINDEX;
}
//消息报头
evkeyvalq *outhead = evhttp_request_get_output_headers(request);
// 要支持 图片 js css 下载zip文件
// 获取文件的后缀
// ./index.html
int pos = filepath.rfind('.');
string postfix = filepath.substr(pos + 1, filepath.size() - (pos + 1));
if (postfix == "jpg" || postfix == "gif" || postfix == "png")
{
string tmp = "image/" + postfix;
evhttp_add_header(outhead, "Content-Type", tmp.c_str());
}
else if (postfix == "zip")
{
evhttp_add_header(outhead, "Content-Type", "application/zip");
}
else if (postfix == "html")
{
evhttp_add_header(outhead, "Content-Type", "text/html;charset=UTF8");
//evhttp_add_header(outhead, "Content-Type", "text/html");
}
else if (postfix == "css")
{
evhttp_add_header(outhead, "Content-Type", "text/css");
}
//读取html文件返回正文
FILE *fp = fopen(filepath.c_str(), "rb");
if (!fp)
{
evhttp_send_reply(request, HTTP_NOTFOUND, "", 0);
return;
}
evbuffer *outbuf = evhttp_request_get_output_buffer(request);
for (;;)
{
int len = fread(buf, 1, sizeof(buf), fp);
if (len <= 0)break;
evbuffer_add(outbuf, buf, len);
}
fclose(fp);
evhttp_send_reply(request, HTTP_OK, "", outbuf);
}
int main(int argc,char *argv[])
{
#ifdef _WIN32
//初始化socket库
WSADATA wsa;
WSAStartup(MAKEWORD(2,2),&wsa);
#else
//忽略管道信号,发送数据给已关闭的socket
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
return 1;
#endif
std::cout << "test server!\n";
//创建libevent的上下文
event_base * base = event_base_new();
if (base)
{
cout << "event_base_new success!" << endl;
}
// http 服务器
//1 创建evhttp上下文
evhttp *evh = evhttp_new(base);
//2 绑定端口和IP
if (evhttp_bind_socket(evh, "0.0.0.0", 8080) != 0)
{
cout << "evhttp_bind_socket failed!" << endl;
}
//3 设定回调函数
evhttp_set_gencb(evh, http_cb, 0);
//事件分发处理
if(base)
event_base_dispatch(base);
if(base)
event_base_free(base);
if(evh)
evhttp_free(evh);
return 0;
}
index.html文件
test http server
test http server
1234567890
测试中文
运行效果