libevent实现的HTTP Server

本文介绍了如何使用C语言结合libevent库创建一个支持多线程处理HTTP请求的接口。首先,通过APR库创建socket并绑定端口,然后在多个线程中使用event_base和evhttp进行监听。每个线程有自己的HTTP服务器,并通过http_server_handler处理POST和GET请求。对于POST请求,解析JSON数据;对于GET请求,解析查询参数。整个过程展示了libevent在并发处理HTTP请求中的应用。

在使用C语言编码时, 有时候需要实现一个HTTP接口, 我们可以选择使用libevent库来实现.
以下代码演示了使用libevent, 并同时支持多线程处理HTTP的请求.

头文件

引入的头文件:

#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/keyvalq_struct.h>

创建多线程监听

创建socket, 使用了APR库.

apr_socket_t* http_socket_create(apr_port_t port, apr_pool_t* pool)
{
	apr_status_t rv;
	apr_socket_t* socket = NULL;
	apr_sockaddr_t* addr;
	char msg[246];

	do
	{
		// 创建socket.
		rv = apr_socket_create(&socket, APR_INET, SOCK_STREAM, APR_PROTO_TCP, pool);
		if (APR_SUCCESS != rv)
		{
			apr_strerror(rv, msg, sizeof(msg));
			dzlog_fatal("failed to create socket, port:%u, reason:%s", port, msg);
			break;
		}

		rv = apr_sockaddr_info_get(&addr, 0, APR_INET, port, 0, pool);
		if (APR_SUCCESS != rv)
		{
			apr_strerror(rv, msg, sizeof(msg));
			dzlog_fatal("apr_sockaddr_info_get failed, reason:%s", msg);
			break;
		}

		apr_socket_opt_set(socket, APR_SO_NONBLOCK, 1);
		apr_socket_opt_set(socket, APR_SO_LINGER, 1);
		apr_socket_opt_set(socket, APR_SO_REUSEADDR, 1);

		// 绑定端口。
		rv = apr_socket_bind(socket, addr);
		if (APR_SUCCESS != rv)
		{
			apr_strerror(rv, msg, sizeof(msg));
			dzlog_fatal("failed to bind port %u", port);
			break;
		}

		return socket;
	} while (0);

	if (socket) apr_socket_close(socket);
	return NULL;
}

多线程监听:

	_server->http_socket = http_socket_create(_server->http_port, _server->pool);
	apr_socket_listen(_server->http_socket, 5);
	apr_os_sock_get(&fd, _server->http_socket);
	// 创建多个线程, 监听同一个socket.
	for (i = 0; i < _server->http_thread_number; i++)
	{
		struct event_base* base = event_base_new();
		struct evhttp* http = evhttp_new(base);

		_server->http_servers[i].base = base;
		_server->http_servers[i].http = http;

		evhttp_accept_socket(http, fd);
		// 设置入口函数(回调函数), 每个线程独立使用自身的MySQL句柄.
		evhttp_set_gencb(http, &http_server_handler, NULL);

		// 创建线程? 需要修改为多线程.
		apr_thread_create(&_server->http_servers[i].thread, NULL, &http_server_thread, base, _server->pool);
	}

线程函数:

static void* APR_THREAD_FUNC http_server_thread(apr_thread_t* thread, void* arg)
{
	struct event_base* base = (struct event_base*)arg;
	event_base_dispatch(base);
	return NULL;
}

HTTP请求入口函数:

static void http_server_handler(struct evhttp_request* req, void* arg)
{
	int code;
	const struct evhttp_uri* uri = evhttp_request_get_evhttp_uri(req);
	if (NULL == uri)
	{
		dzlog_error("http: evhttp_request_get_evhttp_uri null");
		evhttp_send_error(req, HTTP_BADREQUEST, NULL);
		return;
	}

	const char* url = evhttp_uri_get_path(uri);
	if (NULL == url)
	{
		dzlog_error("http: evhttp_uri_get_path fail");
		evhttp_send_error(req, HTTP_BADREQUEST, NULL);
		return;
	}
	
	struct evbuffer* evb = evbuffer_new();
	evbuffer_expand(evb, 4 * 1024);
	switch (evhttp_request_get_command(req))
	{
	case EVHTTP_REQ_POST:
		// 处理POST请求.
		{
			struct evbuffer* ib;
			const char* body;
			ib = evhttp_request_get_input_buffer(req);
			if (NULL != ib)
			{
				// 可能没有BODY
				evbuffer_add(ib, "\0", 1);
				body = (const char*)evbuffer_pullup(ib, -1);

				cJSON* root = cJSON_Parse(body);
				if (NULL == root)
				{
					dzlog_warn("HTTP: invalid JSON, URL=[%s], BODY=[%s]", url, body);
					code = HTTP_EXPECTATIONFAILED;
					mdf_set_errcode(evb, ERR_INVALID_JSON);
				}
				else
				{
					dzlog_debug("HTTP: Receive 'POST', URL='%s', BODY=%s", url, body);
					on_http_post(app, root, evb);
					cJSON_Delete(root);
				}
			}
			break;
		}
	case EVHTTP_REQ_GET:
		{
			struct evkeyvalq headers = { 0 };
			if (0 == evhttp_parse_query_str(evhttp_uri_get_query(evhttp_request_get_evhttp_uri(req)), &headers))
			{
				code = on_http_get(app, &headers, evb);
				evhttp_clear_headers(&headers);
			}
			break;
		}
		......
	}

	evhttp_send_reply(req, code, NULL, evb);
	evbuffer_free(evb);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值