1. 程式人生 > >libevent實現http client

libevent實現http client

使用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程式。大概經歷下列步驟即可:

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

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

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