libevent高并发网络编程 - 04_libevent实现http服务器


链接: 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 <event2/event.h>
#include <event2/listener.h>
#include <event2/http.h>
#include <event2/keyvalq_struct.h>
#include <event2/buffer.h>
#include <string.h>
#ifndef _WIN32
#include <signal.h>
#endif
#include <iostream>
#include <string>
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文件

<HTML>
<HEAD>
<meta charset= "UTF8" />
<TITLE>test http server</TITLE>
</HEAD>
<BODY>
    <h1>test http server</h1>
    1234567890
      测试中文  
    <img src="test.jpg">
    <form method="post">
        <input type="text" name="username" />
        <input type="password" name="password" />
        <input type="submit" name="submit"/>
    </form>
</BODY>
</HTML>

运行效果

请添加图片描述

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
libevent是一个开源的事件驱动库,可以用来开发高并发网络应用程序。下面是一个使用libevent实现的简单的HTTP服务器示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <event2/event.h> #include <event2/http.h> void http_cb(struct evhttp_request *req, void *arg) { const char *uri = evhttp_request_get_uri(req); printf("Request URI: %s\n", uri); struct evbuffer *evb = evbuffer_new(); if (evb == NULL) { fprintf(stderr, "Failed to create evbuffer\n"); evhttp_send_error(req, HTTP_INTERNAL, "Internal Server Error"); return; } evbuffer_add_printf(evb, "Hello World!"); evhttp_send_reply(req, HTTP_OK, "OK", evb); evbuffer_free(evb); } int main(int argc, char *argv[]) { struct event_base *base = event_base_new(); if (base == NULL) { fprintf(stderr, "Failed to create event_base: %s\n", strerror(errno)); return EXIT_FAILURE; } struct evhttp *httpd = evhttp_new(base); if (httpd == NULL) { fprintf(stderr, "Failed to create evhttp: %s\n", strerror(errno)); return EXIT_FAILURE; } if (evhttp_bind_socket(httpd, "0.0.0.0", 8080) != 0) { fprintf(stderr, "Failed to bind to 0.0.0.0:8080: %s\n", strerror(errno)); return EXIT_FAILURE; } evhttp_set_cb(httpd, "/", http_cb, NULL); printf("HTTP server started on 0.0.0.0:8080\n"); event_base_dispatch(base); evhttp_free(httpd); event_base_free(base); return EXIT_SUCCESS; } ``` 这个HTTP服务器监听本地8080端口,处理根路径的请求,返回"Hello World!"。可以使用curl命令测试: ``` $ curl http://localhost:8080/ Hello World! ``` 这只是一个简单的示例,实际的HTTP服务器需要更多的功能和处理逻辑。libevent提供了丰富的API和事件处理机制,可以很方便地实现高并发、高性能的网络应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值