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

这篇具有很好参考价值的文章主要介绍了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>

运行效果

libevent高并发网络编程 - 04_libevent实现http服务器,libevent C++高并发网络编程,网络,http,服务器文章来源地址https://www.toymoban.com/news/detail-611112.html

到了这里,关于libevent高并发网络编程 - 04_libevent实现http服务器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【网络编程】高性能并发服务器源码剖析

      hello !大家好呀! 欢迎大家来到我的网络编程系列之洪水网络攻击,在这篇文章中, 你将会学习到在网络编程中如何搭建一个高性能的并发服务器,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!! 希望这篇文章能

    2024年04月15日
    浏览(56)
  • Linux学习之网络编程3(高并发服务器)

    Linux网络编程我是看视频学的,Linux网络编程,看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。 问题: 根据上一个笔记,我们可以写出一个简单的服务端和客户端通信,但是我们发现一个问题——服务器只能连接一个

    2024年02月01日
    浏览(50)
  • 网络编程(8.14)TCP并发服务器模型

    作业: 1. 多线程中的newfd,能否修改成全局,不行,为什么? 2. 多线程中分支线程的newfd能否不另存,直接用指针间接访问主线程中的newfd,不行,为什么? 多线程并发服务器模型原代码: 1.将newfd改成全局变量效果:  答:不行,因为newfd是全局变量的话,客户端连接后生成

    2024年02月13日
    浏览(49)
  • Linux网络编程:多进程 多线程_并发服务器

    文章目录: 一:wrap常用函数封装 wrap.h  wrap.c server.c封装实现 client.c封装实现 二:多进程process并发服务器 server.c服务器 实现思路 代码逻辑  client.c客户端 三:多线程thread并发服务器 server.c服务器 实现思路 代码逻辑  client.c客户端 ​​​​   read 函数的返回值 wrap.h  wrap

    2024年02月12日
    浏览(56)
  • 【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装

    前言 一、线程池介绍 💻线程池基本概念 💻线程池组成部分 💻线程池工作原理  二、线程池代码封装 🌈main.cpp 🌈ThreadPool.h 🌈ThreadPool.cpp 🌈ChildTask.h  🌈ChildTask.cpp 🌈BaseTask.h 🌈BaseTask.cpp 三、测试效果 四、总结 📌创建线程池的好处 本文主要学习 Linux内核编程 ,结合

    2024年01月16日
    浏览(95)
  • Linux网络编程:线程池并发服务器 _UDP客户端和服务器_本地和网络套接字

    文章目录: 一:线程池模块分析 threadpool.c 二:UDP通信 1.TCP通信和UDP通信各自的优缺点 2.UDP实现的C/S模型 server.c client.c 三:套接字  1.本地套接字 2.本地套 和 网络套对比 server.c client.c threadpool.c   server.c client.c server.c client.c

    2024年02月11日
    浏览(66)
  • Java 网络编程 —— 创建非阻塞的 HTTP 服务器

    HTTP 客户程序必须先发出一个 HTTP 请求,然后才能接收到来自 HTTP 服器的响应,浏览器就是最常见的 HTTP 客户程序。HTTP 客户程序和 HTTP 服务器分别由不同的软件开发商提供,它们都可以用任意的编程语言编写。HTTP 严格规定了 HTTP 请求和 HTTP 响应的数据格式,只要 HTTP 服务器

    2024年02月06日
    浏览(47)
  • Linux 网络编程学习笔记——十二、高性能 I/O 框架库 Libevent

    在处理 I/O 事件、信号和定时事件时,需要考虑如下三个问题: 统一事件源:很明显,统一处理这三类事件既能使代码简单易懂,又能避免一些潜在的逻辑错误。 可移植性:不同的操作系统具有不同的 I/O 复用方式,比如 Solaris 的 dev/poll 文件,FreeBSD 的 kqueue 机制,Linux 的

    2023年04月08日
    浏览(56)
  • 计算机网络套接字编程实验-TCP多进程并发服务器程序与单进程客户端程序(简单回声)

    1.实验系列 ·Linux NAP-Linux网络应用编程系列 2.实验目的 ·理解多进程(Multiprocess)相关基本概念,理解父子进程之间的关系与差异,熟练掌握基于fork()的多进程编程模式; ·理解僵尸进程产生原理,能基于|sigaction()或signal(),使用waitpid()规避僵尸进程产生; ·

    2024年02月12日
    浏览(47)
  • 【网络编程】demo版TCP网络服务器实现

    UDP和TCP的区别: 对于TCP协议有几个特点: 1️⃣ 传输层协议 2️⃣ 有连接(正式通信前要先建立连接) 3️⃣ 可靠传输(在内部帮我们做可靠传输工作) 4️⃣ 面向字节流 对于UDP协议有几个特点: 1️⃣ 传输层协议 2️⃣ 无连接 3️⃣ 不可靠传输 4️⃣ 面向数据报 可以看到

    2024年02月06日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包