libevent庫使得高併發響應HTTP Server的編寫變得很容易。整個過程包括以下幾部:初始化,建立HTTP Server, 指定callback, 進入事件循環。另外在回調函數中,能夠獲取客戶端請求(request的HTTP Header和參數等),進行響應的處理,再將結果發送給客戶端(response的HTTP Header和內容,如html代碼)。php
libevent除了設置generic的callback,還能夠對特定的請求路徑設置對應的callback(回調/處理函數)。html
示例代碼(方便往後參考編寫須要的HTTP server)git
#include <stdio.h> #include <stdlib.h> #include <unistd.h> //for getopt, fork #include <string.h> //for strcat //for struct evkeyvalq #include <sys/queue.h> #include <event.h> //for http #include <evhttp.h> #include <signal.h> #define MYHTTPD_SIGNATURE "myhttpd v 0.0.1" //處理模塊 void httpd_handler(struct evhttp_request *req, void *arg) { char output[2048] = "\0"; char tmp[1024]; //獲取客戶端請求的URI(使用evhttp_request_uri或直接req->uri) const char *uri; uri = evhttp_request_uri(req); sprintf(tmp, "uri=%s\n", uri); strcat(output, tmp); sprintf(tmp, "uri=%s\n", req->uri); strcat(output, tmp); //decoded uri char *decoded_uri; decoded_uri = evhttp_decode_uri(uri); sprintf(tmp, "decoded_uri=%s\n", decoded_uri); strcat(output, tmp); //解析URI的參數(即GET方法的參數) struct evkeyvalq params; evhttp_parse_query(decoded_uri, ¶ms); sprintf(tmp, "q=%s\n", evhttp_find_header(¶ms, "q")); strcat(output, tmp); sprintf(tmp, "s=%s\n", evhttp_find_header(¶ms, "s")); strcat(output, tmp); free(decoded_uri); //獲取POST方法的數據(evhttp沒有直接解析POST的內容結構體的方法) char *post_data = (char *) EVBUFFER_DATA(req->input_buffer); sprintf(tmp, "post_data=%s\n", post_data); strcat(output, tmp); /* 具體的:能夠根據GET/POST的參數執行相應操做,而後將結果輸出 ... */ /* 輸出到客戶端 */ //HTTP header evhttp_add_header(req->output_headers, "Server", MYHTTPD_SIGNATURE); evhttp_add_header(req->output_headers, "Content-Type", "text/plain; charset=UTF-8"); evhttp_add_header(req->output_headers, "Connection", "close"); //輸出的內容 struct evbuffer *buf; buf = evbuffer_new(); evbuffer_add_printf(buf, "It works!\n%s\n", output); evhttp_send_reply(req, HTTP_OK, "OK", buf); evbuffer_free(buf); } void show_help() { char *help = "written by Min (http://54min.com)\n\n" "-l <ip_addr> interface to listen on, default is 0.0.0.0\n" "-p <num> port number to listen on, default is 1984\n" "-d run as a deamon\n" "-t <second> timeout for a http request, default is 120 seconds\n" "-h print this help and exit\n" "\n"; fprintf(stderr, help); } //當向進程發出SIGTERM/SIGHUP/SIGINT/SIGQUIT的時候,終止event的事件偵聽循環 void signal_handler(int sig) { switch (sig) { case SIGTERM: case SIGHUP: case SIGQUIT: case SIGINT: event_loopbreak(); //終止偵聽event_dispatch()的事件偵聽循環,執行以後的代碼 break; } } int main(int argc, char *argv[]) { //自定義信號處理函數 signal(SIGHUP, signal_handler); signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); signal(SIGQUIT, signal_handler); //默認參數 char *httpd_option_listen = "0.0.0.0"; int httpd_option_port = 8080; int httpd_option_daemon = 0; int httpd_option_timeout = 120; //in seconds //獲取參數 int c; while ((c = getopt(argc, argv, "l:p:dt:h")) != -1) { switch (c) { case 'l' : httpd_option_listen = optarg; break; case 'p' : httpd_option_port = atoi(optarg); break; case 'd' : httpd_option_daemon = 1; break; case 't' : httpd_option_timeout = atoi(optarg); break; case 'h' : default : show_help(); exit(EXIT_SUCCESS); } } //判斷是否設置了-d,以daemon運行 if (httpd_option_daemon) { pid_t pid; pid = fork(); if (pid < 0) { perror("fork failed"); exit(EXIT_FAILURE); } if (pid > 0) { //生成子進程成功,退出父進程 exit(EXIT_SUCCESS); } } /* 使用libevent建立HTTP Server */ //初始化event API event_init(); //建立一個http server struct evhttp *httpd; httpd = evhttp_start(httpd_option_listen, httpd_option_port); evhttp_set_timeout(httpd, httpd_option_timeout); //指定generic callback evhttp_set_gencb(httpd, httpd_handler, NULL); //也能夠爲特定的URI指定callback //evhttp_set_cb(httpd, "/", specific_handler, NULL); //循環處理events event_dispatch(); evhttp_free(httpd); return 0; }
在瀏覽器中輸入http://54min.com:8080/index.php?q=test&s=some thing,顯示內容以下:github
It works! uri=/index.php?q=test&s=some%20thing uri=/index.php?q=test&s=some%20thing decoded_uri=/index.php?q=test&s=some thing q=test s=some thing post_data=(null)
並使用Live Http Headers(Firefox addons)查看HTTP headers。數據庫
HTTP/1.1 200 OK Server: myhttpd v 0.0.1 Content-Type: text/plain; charset=UTF-8 Connection: close Date: Tue, 21 Jun 2011 06:30:30 GMT Content-Length: 72
libevent庫使得編寫高併發高性能的HTTP Server變得很簡單。所以實際中,使用libevent能夠爲任何應用(如數據庫)提供一個HTTP based的網絡接口,方便多個clients採用任何支持HTTP protocol的語言與server進行交互。例如:瀏覽器
對不支持HTTP協議的數據庫(RDBMS/NoSQL)封裝HTTP接口網絡
更多的該種應用能夠參考:https://github.com/bitly/simplehttp。數據結構
也能夠爲某些應用封裝HTTP接口,從而實現以client/server方式使用該應用併發
http://blog.csdn.net/sunnydogzhou/article/details/6438122memcached
表示客戶端請求,定義參看:http://monkey.org/~provos/libevent/doxygen-1.4.10/structevhttp__request.html,其中包含的主要域:
struct evkeyvalq *input_headers; //保存客戶端請求的HTTP headers(key-value pairs) struct evkeyvalq *output_headers; //保存將要發送到客戶端的HTTP headers(key-value pairs) //客戶端的ip和port char *remote_host; u_short remote_port; enum evhttp_request_kind kind; //能夠是EVHTTP_REQUEST或EVHTTP_RESPONSE enum evhttp_cmd_type type; //能夠是EVHTTP_REQ_GET, EVHTTP_REQ_POST或EVHTTP_REQ_HEAD char *uri; //客戶端請求的uri char major; //HTTP major number char minor; //HTTP major number int response_code; //HTTP response code char *response_code_line; //readable response struct evbuffer *input_buffer; //客戶端POST的數據 struct evbuffer *output_buffer; //輸出到客戶端的數據
定義參看:http://monkey.org/~provos/libevent/doxygen-1.4.10/event_8h-source.html。struct evkeyvalq
被定義爲TAILQ_HEAD (evkeyvalq, evkeyval);
,即struct evkeyval
類型的tail queue。須要在代碼以前包含
#include <sys/queue.h> #include <event.h>
struct evkeyval爲key-value queue(隊列結構),主要用來保存HTTP headers,也能夠被用來保存parse uri參數的結果。
/* Key-Value pairs. Can be used for HTTP headers but also for query argument parsing. */ struct evkeyval { TAILQ_ENTRY(evkeyval) next; //隊列 char *key; char *value; };
宏TAILQ_ENTRY(evkeyval)被 定義爲:
#define TAILQ_ENTRY(type) struct { struct type *tqe_next; //next element struct type **tqe_prev; //address of previous next element }
定義參看:http://monkey.org/~provos/libevent/doxygen-1.4.10/event_8h-source.html。該結構體用於input和output的buffer。
/* These functions deal with buffering input and output */ struct evbuffer { u_char *buffer; u_char *orig_buffer; size_t misalign; size_t totallen; size_t off; void (*cb)(struct evbuffer *, size_t, size_t, void *); void *cbarg; };
另外定義宏方便獲取evbuffer中保存的內容和大小:
#define EVBUFFER_LENGTH(x) (x)->off #define EVBUFFER_DATA(x) (x)->buffer
例如,獲取客戶端POST數據的內容和大小:
EVBUFFER_DATA(res->input_buffer); EVBUFFER_LENGTH(res->input_buffer);
另外struct evbuffer用以下函數建立添加和釋放:
struct evbuffer *buf; buf = evbuffer_new(); //往buffer中添加內容 evbuffer_add_printf(buf, "It works! you just requested: %s\n", req->uri); //Append a formatted string to the end of an evbuffer. //將內容輸出到客戶端 evhttp_send_reply(req, HTTP_OK, "OK", buf); //釋放掉buf evbuffer_free(buf);
使用req->uri或使用函數const char *evhttp_request_uri(struct evhttp_request *req);
即(evhttp_request_uri(req);
)。
使用函數void evhttp_parse_query(const char *uri, struct evkeyvalq *args);
可對uri的參數進行解析,結果保存在struct evkeyvalq
的key-value pairs中,例如:
char *uri = "http://foo.com/?q=test&s=some+thing"; struct evkeyvalq args; evhttp_parse_query(uri, &args); //而後經過evhttp_find_header等函數獲取各個參數及對應的值 evhttp_find_header(&args, "q"); //獲得test evhttp_find_header(&args, "s"); //獲得some thing
以下兩個函數對URI進行encode和decode:
char *evhttp_encode_uri(const char *uri); char *evhttp_decode_uri(const char *uri);
URI encode的結果是全部非alphanumeric及-_
的字符都被相似於%
和一個2位16進制字符替換(其中空格被+
號替換)。如上兩個函數返回的字符串須要free
掉。
HTTP headers保存在struct evkeyvalq
的結構體中(key-value pairs),使用以下函數可對其進行修改:
const char *evhttp_find_header(const struct evkeyvalq *, const char *); int evhttp_remove_header(struct evkeyvalq *, const char *); int evhttp_add_header(struct evkeyvalq *, const char *, const char *); void evhttp_clear_headers(struct evkeyvalq *);
char *evhttp_htmlescape(const char *html);
特殊字符:&
被替換爲&
;"
被替換爲"
;'
被替換爲'
; <
被替換爲<
;>
被替換爲>
。該函數返回的字符串須要free
掉。