基于libevent实现http通信

文章介绍了libevent库中的evhttp接口,用于创建HTTP服务器和客户端。通过示例代码展示了如何使用这些接口来处理HTTP请求,包括请求类型、消息报头、正文的获取和设置,以及如何响应请求。此外,还提供了HTTP服务器和客户端的简单实现,包括文件服务和请求回调函数的使用。
摘要由CSDN通过智能技术生成

evhttp接口说明

//创建http上下文
evhttp *evhttp_new(struct event_base *base);
//清理http上下文,只有在没有请求时才能调用
void evhttp_free(struct evhttp *http);

//绑定socket
int evhttp_bind_socket(struct evhttp *http, const char *address, ev_uint16_t port);

//指定回调函数
void evhttp_set_gencb(struct evhttp *http, void (*cb)(struct evhttp_request *, void *), void *arg);

//获取uri:http://localhost:8080/...
evhttp_uri *evhttp_request_get_evhttp_uri(const struct evhttp_request *req);
//取出uri转为字符串
const char *evhttp_request_get_uri(const struct evhttp_request *req);
//获取请求的类型
enum evhttp_cmd_type evhttp_request_get_command(const struct evhttp_request *req);
/*
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,
EVHTTP_REQ_OPTIONS   = 1 << 5,
EVHTTP_REQ_TRACE     = 1 << 6,
EVHTTP_REQ_CONNECT   = 1 << 7,
EVHTTP_REQ_PATCH     = 1 << 8
*/
//获取evbuffer, 消息正文
evbuffer *evhttp_request_get_input_buffer(struct evhttp_request *req);
//获取消息报头,链表存储,可以遍历访问所有节点
evkeyvalq *evhttp_request_get_input_headers(struct evhttp_request *req);
//添加消息报头
int evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *value);
struct evkeyvalq *evhttp_request_get_output_headers(struct evhttp_request *req);
//发送(不止一种)
void evhttp_send_reply(struct evhttp_request *req, int code, const char *reason, struct evbuffer *databuf);
/*
code:状态码,一系列宏,200,404
reason:原因,可为NULL
databuf:要发送的消息正文,可以通过evhttp_request_get_output_buffer(),也可以自己创建
*/

Http Server

#include <event2/event.h>
#include <event2/listener.h>
#include <event2/http.h>
#include <event2/keyvalq_struct.h>
#include <event2/buffer.h>
using namespace std;

#defin ROOTDIR "."
void http_cb(struct evhttp_request *req, void *arg)
{
    //解析请求报文
	const char *uri = evhttp_request_get_uri(req);
	cout << "uri:" << uri << endl;//http://127.0.0.1:8080/text.html    uri:/text.html
	
	string cmdtype;
	//获取请求类型
	switch(evhttp_request_get_command(req))
	{
	case EVHTTP_REQ_GET:
		cmdtype = "GET";
		break;
	case EVHTTP_REQ_POST:
		cmdtype = "POST";
		break;
	default
		break;
	}

	//消息报头
	evkeyvalq *headers = evhttp_request_get_input_headers(req);
	cout << "headers:" << endl;
	for(evkeyvalq *i = headers->tqh_first; i != NULL; i = i->next.tqe_next)
	{
		cout << i->key << ":" << i->value << endl;
	}
	//请求正文(get一般为空,post为表单信息)
	evbuffer *evb = evhttp_request_get_input_buffer(req);
	char buf[1024] = {0};
	while(evbuffer_get_length(evb))
	{
		int len = evbuffer_remove(evb, buf, sizeof(buf)-1);
		if(len > 0)
		{
			buf[len] = 0;
			cout << buf << endl;
		}
	}

	string filepath = ROOTDIR;
	filepath += uri;
	if(strcmp(uri, "/")==0)
	{
		filepath += "index.html";
	}
	
	evkeyvalq *outheaders = evhttp_request_get_output_headers(req);

	//获取文件后缀
	int pos = filepath.rfind('.');
	string postfix = filepath.substr(pos+1, filepath.size() - (pos + 1));
	if(postfix == "jpg" || postfix == "gif" || postfix == "png")
	{
		//添加消息报头,不添加相应消息报头,图片可能会被当作文本数据处理,无法显示,html不受影响
		string tmp = "image/" + postfix;
		evhttp_add_header(outheaders, "Content-Type", tmp.c_str());
	}
	else if(postfix == "zip")
	{
		evhttp_add_header(outheaders, "Content-Type", "application/zip");
	}
	else if(postfix == "html")
	{
		//evhttp_add_header(outheaders, "Content-Type", "text/html;charset=UTF8");配服务器的字符集格式
		evhttp_add_header(outheaders, "Content-Type", "text/html");
	}
	else if(postfix == "css")
	{
		evhttp_add_header(outheaders, "Content-Type", "text/css");
	}
	
	FILE *fp = fopen(filepath.c_str(), "rb");
	if(fp)
	{
		//响应报文
		evbuffer *outbuf = evhttp_request_get_output_buffer(req);
		int len = 0;
		char data[1024] = {0};
		do
		{
			len = fread(data, 1, sizeof(data), fp);
			evbuffer_add(outbuf, data, len);
		}while(len > 0);
		fclose(fp);
		
		evhttp_send_reply(req, HTTP_OK, "success", outbuf);
	}
	else
	{
		evhttp_send_reply(req, HTTP_NOTFOUND, "", 0);
	}
}

int main()
{
	event_base *base = event_base_new();
	
	//创建evhttp上下文
	evhttp *evh = evhttp_new(base);
	//绑定ip、port
	int ret = evhttp_bind_socket(evh, "127.0.0.1", 8080);
	if(ret)
		cerr << "evhttp_bind_socket error" << endl;
	
	//设置回调,每次请求到来调用回调
	evhttp_set_gencb(evh, http_cb, 0);
	
	event_base_dispatch(base);
	event_base_free(base);
	evhttp_free(evh);
	return 0;
}

Http Client

//连接回调,请求失败也会进入回调
void con_cb(struct evhttp_request *req, void *ctx)
{
	event_base *base = (event_base *)ctx;
	if(req == NULL)//请求失败,例如请求发出,服务端未响应
	{
		//错误打印
		int errcode = EVUTIL_SOCKET_ERROR();
		cout << evutil_socket_error_to_string(errcode) << endl;;
		return;
	}
	
	const char *path = evhttp_request_get_uri(req);
	string filepath = ".";
	filepath += path;
	
	//需要判断路径是否包含目录需要创建
	
	FILE *fp = fopen(filepath.c_str(), "wb");
	if(!fp)
	{
		cout << "file error" << endl;
	}

	//获取返回的状态码
	cout << evhttp_request_get_response_code(req) << endl;//200
	//包含原因字符串
	cout << evhttp_request_get_response_code_line(req) << endl;//ok

	char buf[1024] = {0};
	evbuffer *input = evhttp_request_get_input_buffer(req);
	//通过req获取evbuffer
	for(;;)
	{
		int len = evbuffer_remove(input, buf, sizeof(buf));
		if(len <=0 )
			break;
		if(!fp)
			continue;
		fwrite(buf, 1, len, fp);
	}
	if(fp)
		fclose(fp);
	event_base_loopbreak(base);
}

void HttpGet()
{
	event_base *base = event_base_new();
	
	string url = "http://127.0.0.1:8080/index.html";
	
	//通过url解析获取uri(资源地址信息)
	evhttp_uri *uri = evhttp_uri_parse(url.c_str());
	//获取协议类型:比如是http还是https,不能直接赋值给string,可能为NULL,会荡掉
	const char *scheme = evhttp_uri_get_scheme(uri);
	cout << scheme << endl;
	//host
	const char *host = evhttp_uri_get_host(uri);
	cout << host << endl;
	//port
	int port = evhttp_uri_get_port(uri);
	if(port < 0)
	{
		if(strcmp(scheme, "http")==0)
		{
			port = 80;
		}
	}
	cout << port << endl;
	//请求的文件路径
	const char *path = evhttp_uri_get_path(uri);
	if(!path || strlen(path)==0)
	{
		path = "/";
	}
	cout << path << endl;
	//?后面的内容
	const char *query = evhttp_uri_get_query(uri);
	if(query)
	{
		cout << query << endl;//id=1
	}
	
	//建立连接
	bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);//-1内部创建socket
	evhttp_connection *evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev, host, port);
	//建立连接信息,NULL为dns的base域名解析,没用上置空
	
	//创建请求,设置回调
	evhttp_request *req = evhttp_request_new(con_cb, base);
	
	//设置请求报头
	evkeyvalq *headers = evhttp_request_get_output_headers(req);
	evhttp_add_header(headers, "Host", host);
	
	//发起请求
	evhttp_make_request(evcon, req, EVHTTP_REQ_GET, path);//path根据需求传值,也可将需要的query传过去
	
	event_base_dispatch(base);
	event_base_free(base);
}

void HttpPost()
{
	event_base *base = event_base_new();
	
	string url = "http://127.0.0.1:8080/index.html";
	
	//通过url解析获取uri(资源地址信息)
	evhttp_uri *uri = evhttp_uri_parse(url.c_str());
	//获取协议类型:比如是http还是https,不能直接赋值给string,可能为NULL,会荡掉
	const char *scheme = evhttp_uri_get_scheme(uri);
	cout << scheme << endl;
	//host
	const char *host = evhttp_uri_get_host(uri);
	cout << host << endl;
	//port
	int port = evhttp_uri_get_port(uri);
	if(port < 0)
	{
		if(strcmp(scheme, "http")==0)
		{
			port = 80;
		}
	}
	cout << port << endl;
	//请求的文件路径
	const char *path = evhttp_uri_get_path(uri);
	if(!path || strlen(path)==0)
	{
		path = "/";
	}
	cout << path << endl;
	//?后面的内容
	const char *query = evhttp_uri_get_query(uri);
	if(query)
	{
		cout << query << endl;//id=1
	}
	
	//建立连接
	bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);//-1内部创建socket
	evhttp_connection *evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev, host, port);
	//建立连接信息,NULL为dns的base域名解析,没用上置空
	
	//创建请求,设置回调
	evhttp_request *req = evhttp_request_new(con_cb, base);
	
	//设置请求报头
	evkeyvalq *headers = evhttp_request_get_output_headers(req);
	evhttp_add_header(headers, "Host", host);
	
	//发送post数据
	evbuffer *output = evhttp_request_get_output_buffer(req);
	evbuffer_add_printf(output, "id=%d&name=%s", 1, "abc");//格式化输出
	
	//发起请求
	evhttp_make_request(evcon, req, EVHTTP_REQ_POST, path);//path根据需求传值,也可将需要的query传过去
	
	event_base_dispatch(base);
	event_base_free(base);
}

int main()
{
	HttpGet();
	HttpPost();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值