目录
目录
前言
libevent 是一个轻量级的开源高性能事件通知库,支持多种 I/O 多路复用技术,内部使用select、epoll、kqueue、IOCP等系统调用管理事件机制。支持 I/O,定时器和信号等事件,支持注册事件优先级。可用来构建高并发的http服务端、tcp服务...
在此,基于libevent实现一个http服务端服务,并给出一个处理客户端GET、POST、PUT、DELETE请求的示例(至于接收表单多文件上传的接口,后续有空再实现)
一、安装libevent
ubuntu本机环境自动下载安装:apt-get install libevent-dev
如需为linux系统环境交叉编译,参照下面流程:
#下载:
get https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
tar -zxf libevent-2.1.12-stable.tar.gz
cd libevent-2.1.12-stable/
mkdir my_build
#编译安装到指定目录,需指定编译工具链路径(CC、)和安装路径(--prefix)
#如果不考虑openssl,所以选择参数--disable-openssl
./configure --prefix=$(pwd)/my_build \
--host=arm-linux-gnueabihf \
CC=/.../bin/arm-linux-gnueabihf-gcc \
--disable-openssl
#如需要openssl依赖,需指定openssl路径, 命令改为:
./configure --host=arm-linux-gnueabihf \
CC=/.../bin/arm-linux-gnueabihf-gcc \
CPPFLAGS="-I/home/zhouyihui/openssl-1.1.1a/my_build/include" \
LDFLAGS="-L/home/zhouyihui/openssl-1.1.1a/my_build/lib -lssl -lcrypto"
#编译安装
make -j4
make install
二、创建http服务器服务示例
在此,封装了一个http服务器接口,可直接使用
1、evhttp_server.h
/********************************
func:基于libevent封装的http服务器
author:zyh
date:2023.09.15
*********************************/
#ifndef _EV_HTTP_SERVER_H_
#define _EV_HTTP_SERVER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
//#include <event.h>
//#include <evhttp.h>
#include "event.h"
#include "evhttp.h"
/*------------------http服务器接口-----------------------*/
typedef struct {
const char *host;//地址,"0.0.0.0" or "127.0.0.1" (127.0.0.1服务端只能在本机上内部通信,无法接受来自其他机器的连接请求,不受外部影响)
int port;//端口
int timeout_sec;//设置超时时间(s),默认60
int max_work_pthread;//服务端处理客户端请求的最大子线程数,大于0
void (*cb)(struct evhttp_request *, void *);//接收处理消息的回调函数
void *arg;//预留给外部的自定义传参,可传到回调函数cb里面
/* *内部参数,外部中途不要修改 **/
pthread_t thread_id;
int thread_run_flag;//内部线程状态,0未运行,1运行
struct event_base **bases;
} evhttp_server_t;
//启动一个http服务器,同一进程可启动多个不同端口的http服务,返回:0成功,-1失败
int start_evhttp_server(evhttp_server_t *server);
int close_evhttp_server(evhttp_server_t *server);
int restart_evhttp_server(evhttp_server_t *server);
void http_cb_demo(struct evhttp_request *req, void *arg);//一个cb回调函数示例
#ifdef __cplusplus
}
#endif
#endif
2、evhttp_server.c
/********************************
func:基于libevent封装的接口服务示例
author:zyh
date:2023.09.15
*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <arpa/inet.h>//socket
//for http
#include "event.h"
#include "evhttp.h"
#include "event2/thread.h"
//
#include "evhttp_server.h"
/***-------------------
event_base_loopexit():
给定计时器到期后的下一次event_base_loop()迭代将正常完成(处理所有排队的事件),然后退出。
如果时间为NULL,则运行所有当前活动事件后退出
event_base_loopbreak():
让event_base立即退出循环。它与event_base_loopexit(base, NULL)的不同在于,
如果event_base当前正在执行某个激活事件,它将在执行完当前正在处理的事件后立即退出。
***/
//响应客户端下载文件的接口
int process_download_file(struct evhttp_request *req, char *filePath)
{
int ret = -1;
struct stat st;
FILE *fp = NULL;
struct evbuffer *out_buf = NULL;
//添加返回的内容类型,二进制文件
//evhttp_add_header(req->output_headers, "Content-Type", "application/octet-stream");
//输出缓冲区
out_buf = evbuffer_new();
if (!out_buf) {
evhttp_send_error(req, HTTP_INTERNAL, "Could not allocate buffer");
goto done;
}
if (stat(filePath, &st) < 0) {
fprintf(stderr, "<%s:%d>, stat %s failed\n", __FILE__, __LINE__, filePath);
evhttp_send_error(req, HTTP_NOTFOUND, "file is not exist");
goto done;
}
if (S_ISDIR(st.st_mode)) {//如果是文件夹,则出错
fprintf(stderr, "<%s:%d>, %s is directory\n", __FILE__, __LINE__, filePath);
evhttp_send_error(req, HTTP_NOTFOUND, "file is directory");
goto done;
}
fp = fopen(filePath, "rb");
if (!fp) {
fprintf(stderr, "<%s:%d>, Could not open %s\n", __FILE__, __LINE__, filePath);
evhttp_send_error(req, HTTP_NOTFOUND, "file is not exist");
goto done;
}
evbuffer_add_file(out_buf, fileno(fp), 0, st.st_size);//添加文件至evbuffer
evhttp_send_reply(req, HTTP_OK, "OK", out_buf);
ret = 0;
done:
if (fp) {
fclose(fp);
}
if (out_buf) {
evbuffer_free(out_buf);
}
return ret;
}
//响应客户端通过表单方式上传多个文件的接口
int process_upload_files(struct evhttp_request *req, const char *upload_dir)
{
return 0;
}
//http后端数据处理模块
void http_cb_demo(struct evhttp_request *req, void *arg)
{
const char *uri = evhttp_request_get_uri(req);//http的get和delete请求不能带body,只能通过uri带参
int method = evhttp_request_get_command(req);
const char *content_type = evhttp_find_header(req->input_headers, (const char*)"Content-Type");
//int length = evhttp_find_header(req->input_headers, "Content-Length")
int length = evbuffer_get_length(req->input_buffer);
evbuffer_add(req->input_buffer, "\0", 1);//需要添加这个,否者body会出现之前其它请求的内容
char *body = (char*)EVBUFFER_DATA(req->input_buffer);
//char *body = (char*)evbuffer_pullup(req->input_buffer, length);
printf("\nuri=%s\n", uri);
if (EVHTTP_REQ_GET == method) {
printf("method=GET\n");
} else if (EVHTTP_REQ_POST == method) {
printf("method=POST\n");
} else if (EVHTTP_REQ_PUT == method) {
printf("method=PUT\n");
} else if (EVHTTP_REQ_DELETE == method) {
printf("method=DELETE\n");
}
printf("content_type=%s\n", content_type);
printf("length=%d\n", length);
printf("body=%s\n", body);
if (NULL == uri) {
evhttp_send_error(req, HTTP_BADREQUEST, "Bab Request");
return;
}
/*响应返回内容,输出到客户端*/
//设置HTTP头信息
evhttp_add_header(req->output_headers, "Server", "myhttpd/v1.0.0");
evhttp_add_header(req->output_headers, "Connection", "keep-alive");
//输出缓冲区
const int reply_buf_size = 1024 * 10;
char *reply = NULL;
char filePath[128] = {0};
struct evbuffer *out_buf = NULL;
out_buf = evbuffer_new();
if (!out_buf) {
evhttp_send_error(req, HTTP_INTERNAL, "Could not allocate buffer");
goto done;
}
reply = (char *)calloc(reply_buf_size, sizeof(char));
if (!reply) {
evhttp_send_error(req, HTTP_INTERNAL, "Could not calloc reply buf");
goto done;
}
//处理具体内容
if (strstr(uri, "/download/")) {//下载文件接口"/download/.../xxx.x"
//添加返回的内容类型,二进制文件
evhttp_add_header(req->output_headers, "Content-Type", "application/octet-stream");
snprintf(filePath, sizeof(filePath), "./%s", uri + strlen("/download/"));
process_download_file(req, filePath);
} else if ( (0 == strcmp(uri, "/upload")) && strstr(content_type, "application/json")) {
process_upload_files(req, "./upload");
} else {
//添加返回的内容类型
//evhttp_add_header(req->output_headers, "Content-Type", "application/json");
evhttp_add_header(req->output_headers, "Content-Type", "application/json;charset=UTF-8");
//判断一下数据类型是否正确
if ( (NULL == content_type) || (NULL == strstr(content_type, "application/json")) ) {
snprintf(reply, reply_buf_size, "%s", "{\"code\":400,\"message\":\"Content-Type is not application/json\"}");
} else {
//处理一个个请求
if (0 == strcmp(uri, "/test")) {
snprintf(reply, reply_buf_size, "%s", "{\"code\":200,\"message\":\"ok\"}");
} else if (0 == strcmp(uri, "/test1")) {
snprintf(reply, reply_buf_size, "%s", "{\"code\":200,\"message\":\"ok\"}");
} else {
snprintf(reply, reply_buf_size, "%s", "{\"code\":400,\"message\":\"Bad Request, has no uri\"}");
}
}
printf("reply=%s\n", reply);
//添加数据到缓冲区
//evbuffer_add_printf(buf, "%s", reply);
evbuffer_add(out_buf, reply, strlen(reply));//如果reply不是字符串,而是二进制流,请输入实际长度
//发送响应
evhttp_send_reply(req, HTTP_OK, "OK", out_buf);
}
done:
/* Release the memory */
if (out_buf) {
evbuffer_free(out_buf);
}
if (reply) {
free(reply);
reply = NULL;
}
}
//单线程方式,直接使用libevent的http模块,此模块是为单线程设计*/
#if 0//不安全,已被其它函数和方式取代
int main(void)
{
evthread_use_pthreads();//必须,否则调用event_base_loopexit或者event_base_loopbreak终止不了循环,导致最终释放不了资源
//初始化event API
event_init();//创建的是一个全局的事件集合,event_init( )函数实现是由event_base_new( )封装而成的
//创建一个http server
struct evhttp *httpd;
httpd = evhttp_start("0.0.0.0", 1088);
evhttp_set_timeout(httpd, 64);
//指定generic callback
//evhttp_set_gencb(httpd, httpd_handler, NULL);
//也可以为特定的URI指定callback
//evhttp_set_cb(httpd, "/", httpd_handler, NULL);
evhttp_set_cb(httpd, "/test", httpd_handler, NULL);
//evhttp_set_cb(httpd, "/test1", httpd_handler1, NULL);
printf("http server start OK!\n");
//循环处理events
event_dispatch();// event_loopbreak()可终止侦听event_dispatch()的事件侦听循环;
//
evhttp_free(httpd);
printf("http server quit!\n");
return 0;
}
#endif
//单线程方式,直接使用libevent的http模块,此模块是为单线程设计*/
#if 0
//安全的方式
int main(int argc, char *argv[])
{
evthread_use_pthreads();//必须,否则调用event_base_loopexit或者event_base_loopbreak终止不了循环
struct event_base *base;
//初始化event_init
base = event_base_new();//创建的是一个局部的事件集合
/* 创建一个http server,使用libevent的http模块,此模块是为单线程设计*/
struct evhttp *ev_http;
ev_http = evhttp_new(base);
evhttp_bind_socket(ev_http, "0.0.0.0", 1088);
evhttp_set_timeout(ev_http, 60);//设置请求超时时间(s)
//指定generic callback
evhttp_set_gencb(ev_http, httpd_handler, NULL);
//evhttp_set_cb(ev_http, "/test", httpd_handler, NULL);//也可以为特定的URI指定callback
printf("http server start OK!\n");
//循环处理events
event_base_dispatch(base);//外部调用event_base_loopbreak() /event_base_loopexit()可终止循环;
//释放资源
if (ev_http) {
evhttp_free(ev_http);
}
if (base) {
event_base_free(base);
}
printf("http server quit!\n");
}
#endif
/**
多线程方式,需要自行设计线程池以便提高吞吐量,每个工作线程中都要运行一个event_base_loop和一个evhttp实例
(这些evhttp实例需要用evhttp_bind_socket绑定到相同的端口上),具体参考官方的sample,
https://github.com/libevent/libevent
**/
static int http_init(const char *host, int port)
{
int nfd = socket(AF_INET, SOCK_STREAM, 0);
if (nfd < 0) {
return -1;
}
const int one = 1;
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_port = htons(port);
if (0 == strcmp(host, "127.0.0.1")) {//服务端程序只能在本机上测试,无法接受来自其他机器的连接请求
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
} else {//服务端程序可以接受任何机器的连接请求,即绑定到此机器的所有网卡上
addr.sin_addr.s_addr = INADDR_ANY;
}
//printf("nfd=%d\n", nfd);
if (bind(nfd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(nfd);
return -1;
}
if (listen(nfd, 128) < 0) {
close(nfd);
return -1;
}
int flags = fcntl(nfd, F_GETFL, 0);//设置socket为非阻塞
if (0 > flags) {
close(nfd);
return -1;
}
if (0 > fcntl(nfd, F_SETFL, flags | O_NONBLOCK)) {
close(nfd);
return -1;
}
return nfd;
}
static void *worker_func(void *arg)
{
// printf("http server start pthread [%ld]\n", pthread_self());
event_base_dispatch((struct event_base *) arg);
// printf("http server exit pthread [%ld]\n", pthread_self());
return NULL;
}
static void *evhttp_server_loop(void *arg)
{
evhttp_server_t *server = (evhttp_server_t *)arg;
const char *httpd_listen_host = server->host;
int port = server->port;
int i = 0;
int socket_fd = -1;
int max_work_pthread = server->max_work_pthread;
server->bases = (struct event_base **)calloc(max_work_pthread, sizeof(struct event_base *));
//struct evhttp *httpds[max_work_pthread];
//pthread_t thread_list[max_work_pthread];
struct evhttp **httpds = (struct evhttp **)calloc(max_work_pthread, sizeof(struct evhttp *));
pthread_t *thread_list = (pthread_t *)calloc(max_work_pthread, sizeof(pthread_t));
evthread_use_pthreads();//必须,使用多线程,需要先初始化线程支持, 否则调用event_base_loopexit或者event_base_loopbreak终止不了循环
while (server->thread_run_flag) {
//socket
socket_fd = http_init(httpd_listen_host, port);
if (socket_fd < 0) {
fprintf(stderr, "<%s:%d>, http_init fail, %d端口已被占用,重新创建...\n", __func__, __LINE__, port);
sleep(2);
continue;
}
//http
for (i = 0; i < max_work_pthread; i++) {
server->bases[i] = event_base_new();
httpds[i] = evhttp_new(server->bases[i]);
evhttp_accept_socket(httpds[i], socket_fd);
//
if (0 >= server->timeout_sec) {
evhttp_set_timeout(httpds[i], 60);//设置超时时间(s)
} else {
evhttp_set_timeout(httpds[i], server->timeout_sec);//设置超时时间(s),一般60
}
//evhttp_set_gencb(httpds[i], httpd_handler, NULL);
evhttp_set_gencb(httpds[i], server->cb, server->arg);
pthread_create(&thread_list[i], NULL, worker_func, (void *)server->bases[i]);
}
usleep(100*1000);
printf("<%s:%d>, http server start OK! (ip:%s port:%d)\n", __func__, __LINE__, httpd_listen_host, port);
for (i = 0; i < max_work_pthread; i++) {//堵塞等待,直到服务关闭
pthread_join(thread_list[i], NULL);
}
//关闭释放
if (socket_fd > 0) {
close(socket_fd);
}
for (i = 0; i < max_work_pthread; i++) {
if (httpds[i]) {
evhttp_free(httpds[i]);//有时会出现堵塞,测试发现必须先在event_base_free之前调用才不会堵塞
httpds[i] = NULL;
}
if (server->bases[i]) {
event_base_free(server->bases[i]);
server->bases[i] = NULL;
}
}
free(server->bases);
server->bases = NULL;
free(httpds);
httpds = NULL;
free(thread_list);
thread_list = NULL;
printf("<%s:%d>, http server exit success!\n", __func__, __LINE__);
break;
}
return NULL;
}
//启动一个http服务器,同一进程可启动多个不同端口的服务
//返回:0成功,-1失败
int start_evhttp_server(evhttp_server_t *server)
{
int ret = 0;
ret = pthread_create(&server->thread_id, NULL, evhttp_server_loop, server);
if (ret < 0) {
fprintf(stderr, "<%s:%d>, pthread_create evhttp_server_loop is failed\n", __func__, __LINE__);
return -1;
}
server->thread_run_flag = 1;
return 0;
}
int close_evhttp_server(evhttp_server_t *server)
{
if (1 == server->thread_run_flag) {
server->thread_run_flag = 0;
for (int i = 0; i < server->max_work_pthread; i++) {
event_base_loopbreak(server->bases[i]);//先断开事件循环
}
pthread_join(server->thread_id, NULL);
}
sleep(1);//延时一下
return 0;
}
int restart_evhttp_server(evhttp_server_t *server)
{
close_evhttp_server(server);
return start_evhttp_server(server);
}
3、evhttp_server_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>
#include "libevent_api.h"
//创建http服务器
int main(int argc, char *argv[])
{
evhttp_server_t server1 = {
.host = "0.0.0.0",
.port = 1080,
.timeout_sec = 60,
.max_work_pthread = 4,
.cb = http_cb_demo
};
start_evhttp_server(&server1);
sleep(10*60);
printf("关闭\n");
close_evhttp_server(&server1);
return 0;
}
4、编译运行,手动单独编译:
创建并复制代码文件,手动 gcc/g++ 编译
g++ -o http_server_test evhttp_server_demo.c evhttp_server.c evhttp_server.h -lpthread -levent -levent_pthreads -Wall