libevent實現http client

NO IMAGE

使用libevent實現了一個http client。

請給我的決賽文章《Qt Quick 影象處理例項之美圖秀秀(附原始碼下載)》投票,謝謝。

一直想找一個基於libevent實現的client端的例子,沒找著合適的,自己做了一個。遇到一個問題,發出http請求後,對方總是無反應。今天研究evhttp原始碼,忽然發現了一個愚蠢的錯誤:傳送請求時少了一個空行(”\r\n”)。啊,太有才了耶。

其實是想利用libevent的事件處理機制,想在linux和安卓兩個系統上共享一些功能模組,先拿httpclient做個試驗。用到了http_parser來解析httpheader,特此感謝。

是在純C的環境下試驗,我實現了兩個輔助結構體(通過函式指標模仿類的實現)。struct c_string用於字串處理,struct tag_value_list用來儲存http header field和header value。我的試驗是熟悉libevent,這些程式碼掠過。

來看一下main.c的程式碼,主要是如何使用libevent。

#include "event2/event-config.h"
#include "event2/event_compat.h"
#include "event2/event.h"
#include "event2/util.h"
#include "event2/bufferevent.h"
#include "event2/dns.h"
#include "event2/buffer.h"
#include "http_client.h"
#ifndef WIN32
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include <string.h>
void write_cb(evutil_socket_t sock, short flags, void * args)
{
struct http_client *httpc = (struct http_client *)args; 
struct c_string * string = httpc->request_string(httpc);
int len = string->len(string);
int sent = 0;
int ret = 0;
printf("connected, write headers: %s\n", string->data);
ret = send(sock, string->data, len, 0);
while(ret != -1)
{
sent  = ret;
if(sent == len) break;
ret = send(sock, string->data   sent, len - sent, 0);
}
delete_c_string(string);
event_add((struct event*)httpc->user_data[1], 0);
}
void read_cb(evutil_socket_t sock, short flags, void * args)
{
struct http_client *httpc = (struct http_client*)args;
int ret = recv(sock, httpc->parse_buffer, PARSE_BUFFER_SIZE, 0);
printf("read_cb, read %d bytes\n", ret);
if(ret > 0)
{
httpc->process_data(httpc, httpc->parse_buffer, ret);
}
else if(ret == 0)
{
printf("read_cb connection closed\n");
event_base_loopexit((struct event_base*)httpc->user_data[0], NULL);
return;
}
if(httpc->finished(httpc) != 0)
{
event_add((struct event*)httpc->user_data[1], 0);
}
}
static evutil_socket_t make_tcp_socket()
{
int on = 1;
evutil_socket_t sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
evutil_make_socket_nonblocking(sock);
#ifdef WIN32
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char *)&on, sizeof(on));
#else
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
#endif
return sock;
}
static struct http_client * make_http_client(struct event_base * base, const char *url)
{
struct http_client * httpc = new_http_client();
/* initialize http client */
httpc->user_data[0] = base;
if(0 == httpc->parse_url(httpc, url) )
{
httpc->add_request_header(httpc, "Accept", "*/*");
httpc->add_request_header(httpc, "User-Agent", "test http client");
return httpc;
}
delete_http_client(httpc);
printf("parse url failed\n");
return 0;
}
int download(struct event_base * base, const char *url)
{
evutil_socket_t sock = make_tcp_socket();
struct sockaddr_in serverAddr;
struct http_client * httpc = make_http_client(base, url);
struct event * ev_write = 0;
struct event * ev_read = 0;
struct timeval tv={10, 0};
if(!httpc) return -1;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(httpc->port);
#ifdef WIN32
serverAddr.sin_addr.S_un.S_addr = inet_addr(httpc->host);
#else
serverAddr.sin_addr.s_addr = inet_addr(httpc->host);
#endif
memset(serverAddr.sin_zero, 0x00, 8);
connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
ev_write = event_new(base, sock, EV_WRITE, write_cb, (void*)httpc);
ev_read = event_new(base, sock, EV_READ , read_cb, (void*)httpc);
httpc->user_data[1] = ev_read;
event_add(ev_write, &tv);
return 0;
}
int main(int argc, char** argv)
{
struct event_base * base = 0;
#ifdef WIN32
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
(void) WSAStartup(wVersionRequested, &wsaData);
#endif
if(argc < 2)
{
printf("usage: %s http://111.222.333.44:8080/xxx.htm\n    now only support ip.\n", argv[0]);
return 1;
}
base = event_base_new();
if( 0 == download(base, argv[1]) )
{
event_base_dispatch(base);
event_base_free(base);
}
else
{
printf("prepare download failed for %s\n", argv[1]);
}
return 0;
}

例子比較簡單,通過命令列傳參,下載指定的URL代表的資源,下載到的資源也沒有處理(後續可以通過給struct http_client新增資料處理的回撥介面來處理資料)。目前可以在windows和cent os上執行。

程式不嚴謹,一些錯誤未處理,一些記憶體未釋放。僅僅演示如何使用libevent實現client程式。大概經歷下列步驟即可:

  1. 初始化event_base(後續要執行事件迴圈)
  2. 建立socket,設定為非同步,連線server
  3. 建立寫讀寫事件,先將寫事件加入事件迴圈
  4. 在寫事件回撥中向server端傳送請求並將讀事件加入事件迴圈
  5. 在讀事件回撥中處理資料,並根據資料是否讀取完畢決定是否繼續新增讀事件

至於協議的處理細節,根據程式目的可以自由實現。

我沒有使用bufferevent,網上有很多的例子,不再這裡提了。接下來我希望試驗一下UDP,看能否成功。