libevent實現http client
阿新 • • 發佈:2018-12-30
使用libevent實現了一個http client。
一直想找一個基於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程式。大概經歷下列步驟即可:
- 初始化event_base(後續要執行事件迴圈)
- 建立socket,設定為非同步,連線server
- 建立寫讀寫事件,先將寫事件加入事件迴圈
- 在寫事件回撥中向server端傳送請求並將讀事件加入事件迴圈
- 在讀事件回撥中處理資料,並根據資料是否讀取完畢決定是否繼續新增讀事件
至於協議的處理細節,根據程式目的可以自由實現。
我沒有使用bufferevent,網上有很多的例子,不再這裡提了。接下來我希望試驗一下UDP,看能否成功。