您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何構建LwIP raw api 中的http server,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
下面為構建LwIP RAW API下HTTP SERVER DEMO的學習記錄,內容僅為個人設計與理解,所貼代碼均為簡化版,不保證適用性與準確性。
本人使用的LwIP版本為2.1.2。LwIP應用層直接接口為ALTCP,是TCP之上包了一個抽象層,也可以通過宏開關LWIP_ALTCP使接口直接對應TCP函數。
一、服務初始化
void http_init(void) { struct altcp_pcb *pcb; //分配協議控制塊 pcb = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY); //掛載接口 altcp_bind(pcb, IP_ANY_TYPE, 80); //開啟監聽 pcb = altcp_listen(pcb); //注冊接收回調 altcp_accept(pcb, http_accept); }
初始化的內容就是分配協議控制塊,綁定到http端口號80上,然后等待客戶端連接。
altcp_tcp_new_ip_type函數內調用TCP接口tcp_new_ip_type創建了控制塊pcb,然后定義altcp_pcb控制塊ret,將pcb裁剪后將幾個LwIP關心的部分對接到ret,上層應用只需關心閹割后的ret而無需再看完整的TCP控制塊結構體,應用層的callback也只需跟altcp_pcb對接即可。若空間不足,TCP會釋放某些非活躍控制塊,而altcp只是根據MEMP剩余空間大小不足回復失敗
pcb->callback_arg = altcp_pcb;
pcb->recv = altcp_tcp_recv;
pcb->sent = altcp_tcp_sent;
pcb->errf = altcp_tcp_err;
struct altcp_pcb * altcp_tcp_new_ip_type(u8_t ip_type) { /* Allocate the tcp pcb first to invoke the priority handling code if we're out of pcbs */ struct tcp_pcb *tpcb = tcp_new_ip_type(ip_type); if (tpcb != NULL) { struct altcp_pcb *ret = altcp_alloc(); if (ret != NULL) { altcp_tcp_setup(ret, tpcb); return ret; } else { /* altcp_pcb allocation failed -> free the tcp_pcb too */ tcp_close(tpcb); } } return NULL; }
其上還有一個altcp_new_ip_type的分配方式,可以用戶自定義alloc函數和參數保存在其調用的altcp_allocator_t結構體變量中。
altcp_bind直接操作altcp_tcp_bind,實際使用tcp_bind,將altcp_pcb對應的pcb和http端口號80綁定。TCP遍歷已存在的控制塊鏈表,如沒有相同的IP和端口號則插入鏈表。
altcp_listen(conn)啟動服務器監聽狀態,其實際功能函數altcp_tcp_listen調用的也是tcp_listen_with_backlog_and_err。由于TCP監聽時會用節省資源的tcp_pcb_listen結構體變量替換tcp_pcb,所以需要更新state元素。再將altcp_tcp_accept與tcp_accept對接,作為客戶端連接的回調,其作用是當TCP_EVENT_ACCEPT發生時,開辟一個新的altcp_pcb去對接客戶端連接的new_tpcb,并執行arg中預設的accept函數(pcb->callback_arg->accept,即altcp_pcb->accept)
static struct altcp_pcb * altcp_tcp_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err) { struct tcp_pcb *pcb; struct tcp_pcb *lpcb; if (conn == NULL) { return NULL; } ALTCP_TCP_ASSERT_CONN(conn); pcb = (struct tcp_pcb *)conn->state; lpcb = tcp_listen_with_backlog_and_err(pcb, backlog, err); if (lpcb != NULL) { conn->state = lpcb; tcp_accept(lpcb, altcp_tcp_accept); return conn; } return NULL; }
altcp_accept是將上一步中accept階段TCP傳遞到LwIP的回調注冊到應用層的http_accept,在此函數內處理客戶端的連接請求,完成完整的連接操作。在用戶層的http_accept中開辟一個變量放置應用的狀態以及下層回調的參數,并將其與對應的pcb聯系起來,之后就是將已經與TCP連接好的LwIP各個altcp回調與應用層具體處理函數對應起來。
#define HTTP_PRIO TCP_PRIO_MIN static err_t http_accept(void *arg, struct altcp_pcb *pcb, err_t err) { struct http_st *st = http_state_alloc(); st->pcb = pcb; altcp_arg(pcb, hs); altcp_setprio(pcb, HTTP_PRIO); altcp_recv(pcb, http_recv); altcp_poll(pcb, http_poll, HTTP_POLL_INT); altcp_sent(pcb, http_sent); altcp_err(pcb, http_err); return ERR_OK; }
至此一個服務的初始化過程基本完成,接下來就是應用層的函數編寫。
二、應用層函數功能
應用層在http_recv處理接收數據的回調,其中結構體pbuf是協議棧分解出的數據包。在raw api的數據處理中,需要用戶手動執行altcp_recved通知協議棧數據正確接收,并且pbuf_free釋放數據包緩存
static err_t http_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) { struct http_st *st = (struct http_st *)arg; if((p == NULL) || (err != ERR_OK) || (st == NULL)) { if(p != NULL) { altcp_recved(pcb, p->tot_len); pbuf_free(p); } http_close_conn(pcb, st); return ERR_OK; } altcp_recved(pcb, p->tot_len); if(st->handle == NULL) { err_t parsed = http_parse_request(p, st, pcb); pbuf_free(p); if(parsed == ERR_OK) { http_send(pcb, st); } else if(parsed == ERR_ARG) { http_close_conn(pcb, hs); } } else { pbuf_free(p); } return ERR_OK; }
http_parse_request函數主要功能為解析接收的HTTP協議指令,對正確的“GET”進行響應。http server預先儲存了file_handle列表,當client發對應的uri時傳輸相應的文件
#define CRLF "\r\n" static err_t http_parse_request(struct pbuf *p, struct http_st *st, struct altcp_pcb *pcb) { char *data; u16_t data_len; if((st->handle != NULL) || (st->file != NULL)) { //already started sending return ERR_USE; } //actual data in pbuf data = (char *)p->payload; //length of current buffer data_len = p->len; /* find first \r\n */ if(lwip_strnstr(data, CRLF, data_len) != NULL) { char *sp1, *sp2; int is_09 = 0; u16_t left_len, uri_len; if(strncmp(data, "GET ", 4)) { //unsupported http_find_error_file(st, 501); } sp1 = data + 3; //check URI left_len = (u16_t)(data_len - ((sp1 + 1) - data)); sp2 = lwip_strnstr(sp1 + 1, " ", left_len); if(sp2 == NULL) { sp2 = lwip_strnstr(sp1 + 1, CRLF, left_len); is_09 = 1; } uri_len = (u16_t)(sp2 - (sp1 + 1)); if((sp2 != 0) && (sp2 > sp1)) { //find the end of HTTP headers if(lwip_strnstr(data, CRLF CRLF, data_len) != NULL) { char *uri = sp1 + 1; *sp1 = 0; uri[uri_len] = 0; return http_find_file(st, uri, is_09); } } } return http_find_error_file(st, 400); }
static err_t http_find_error_file(struct http_st *st, u16_t error_nr) { const char *uri, *uri1, *uri2, *uri3; if (error_nr == 501) { uri1 = "/501.html"; uri2 = "/501.htm"; uri3 = "/501.shtml"; } else { /* 400 (bad request is the default) */ uri1 = "/400.html"; uri2 = "/400.htm"; uri3 = "/400.shtml"; } if (fs_open(&st->file_handle, uri1) == ERR_OK) { uri = uri1; } else if (fs_open(&st->file_handle, uri2) == ERR_OK) { uri = uri2; } else if (fs_open(&st->file_handle, uri3) == ERR_OK) { uri = uri3; } else { return ERR_ARG; } return http_init_file(st, &st->file_handle, 0, uri, 0, NULL); }
根據解析的uri,在本地文件中尋找對應name的文件
#define LWIP_HTTP_MAX_REQUEST_URI_LEN 63 static char http_uri_buf[LWIP_HTTP_MAX_REQUEST_URI_LEN + 1]; static err_t http_find_file(struct http_st *st, const char *uri, int is_09) { size_t loop; struct fs_file *file = NULL; char *params = NULL; err_t err; int i; u8_t tag_check = 0; //check uri size_t uri_len = strlen(uri); if((uri_len > 0) && (uri[uri_len - 1] == '/') && ((uri != http_uri_buf) || (uri_len == 1))) { size_t copy_len = LWIP_MIN(sizeof(http_uri_buf) - 1, uri_len - 1); if(copy_len > 0) { MEMCPY(http_uri_buf, uri, copy_len); http_uri_buf[copy_len] = 0; } for(loop = 0; loop < NUM_DEFAULT_FILENAMES; loop++) { const char *file_name; if(copy_len > 0) { size_t len_left = sizeof(http_uri_buf) - copy_len - 1; if(len_left > 0) { size_t name_len = strlen(httpd_default_filenames[loop].name); size_t name_copy_len = LWIP_MIN(len_left, name_len); MEMCPY(&http_uri_buf[copy_len], httpd_default_filenames[loop].name, name_copy_len); http_uri_buf[copy_len + name_copy_len] = 0; } file_name = http_uri_buf; } else { file_name = httpd_default_filenames[loop].name; } err = fs_open(&st->file_handle, file_name); if(err == ERR_OK) { uri = file_name; file = &st->file_handle; tag_check = httpd_default_filenames[loop].shtml; } } } if(file == NULL) { params = (char *)strchr(uri, '?'); if(params != NULL) { *params = '\0'; params++; } http_cgi_paramcount = -1; if(httpd_num_cgis && httpd_cgis) { for(i = 0; i < httpd_num_cgis; i++) { if(strcmp(uri, httpd_cgis[i].pcCGIName) == 0) { http_cgi_paramcount = extract_uri_parameters(hs, params); uri = httpd_cgis[i].pfnCGIHandler(i, http_cgi_paramcount, st->params, st->param_vals); break; } } } err = fs_open(&st->file_handle, uri); if(err == ERR_OK) { file = &st->file_handle; } else { file = http_get_404_file(st, &uri); } if(file != NULL) { if(file->flags & FS_FILE_FLAGS_SSI) { tag_check = 1; } else { tag_check = http_uri_is_ssi(file, uri); } } } if(file == NULL) { /* None of the default filenames exist so send back a 404 page */ file = http_get_404_file(st, &uri); } return http_init_file(st, file, is_09, uri, tag_check, params); }
將找到的文件掛載到狀態量st
static err_t http_init_file(struct http_st *st, struct fs_file *file, int is_09, const char *uri, u8_t tag_check, char *params) { if(file != NULL) { if(tag_check) { struct http_ssi_state *ssi = http_ssi_state_alloc(); if(ssi != NULL) { ssi->tag_index = 0; ssi->tag_state = TAG_NONE; ssi->parsed = file->data; ssi->parse_left = file->len; ssi->tag_end = file->data; st->ssi = ssi; } } st->handle = file; st->file = file->data; st->left = (u32_t)file->len; st->retries = 0; if(is_09 && ((st->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0)) { /* HTTP/0.9 responses are sent without HTTP header, search for the end of the header. */ char *file_start = lwip_strnstr(st->file, CRLF CRLF, st->left); if(file_start != NULL) { int diff = file_start + 4 - st->file; st->file += diff; st->left -= (u32_t)diff; } } } else { st->handle = NULL; st->file = NULL; st->left = 0; st->retries = 0; } if((st->handle == NULL) || ((st->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) == 0)) { get_http_headers(st, uri); } return ERR_OK; }
關于如何構建LwIP raw api 中的http server就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。