這兩天,師弟在兩臺電腦上搭建了lwipwin32通訊平臺,目的是可以不斷髮送一幅幅圖片圖像大小爲1280*720大小的圖片。若是不考慮壓縮狀況且是256色即1字節,這樣大小的一幅圖片須要發送大約發送1M字節。具備李逵性格的山東師弟目標是:客服端發送一次請求,服務器端可以經過TCP方式一次發送大約1M的數據;我震驚了, 我說你可知道MTU(1500)的上限,以及內存的考慮。固然讓每次發送一次數據量儘量大,這種想法是好的;看着他天天哀聲嘆氣,臨近畢業的我深表同情,另外我對lwip也蠻幹感興趣的,一直都想玩玩這個技術,但一直沒有機會,言歸正傳,稍微介紹下lwip。編程
一、概述:lwip是輕量型的TCP/IP實現,只需10幾KB的RAM和40幾KB的ROM就可以跑起來,適合應用於嵌入式設備的網絡通訊。有牛人Adam Dunkels發明,提供給用戶上那種接口服務器
RAW TCP/IP、Squential API, BSD API(也就是常說的socket編程),前者編程稍微複雜點,協議和應用程序在一個進程裏面,可是效率高。中者首先要操做系統的支持,可是一旦實現了操做系統模擬層的實現,編程妥妥的。後者是爲了符合人們使用socket編程的習慣而模擬的socket實現。因此王者乃RAW TCP/IP。網上提供了源碼的下載,如今已經更新至1.4.x(doc 開發與移植指導文檔, src 源碼 test 測試例子),doc裏面羅列了RAW TCP/IP的開發接口函數。網絡
二、demo搭建:socket
服務器端:
0 open_tap()裏面選擇適合的網卡tcp
1 開闢新進程 sys_thread_new("http thread",server_init, NULL, 0, 0);函數
2 設定網址和默認路由器 netif_add(&netif, &ip, &mask, &gw, NULL, ethernetif_init, tcpip_input);測試
3 在server_init裏面 分別調用pcb = tcp_new(); tcp_bind(pcb, IP_ADDR_ANY, 8000);pcb = tcp_listen(pcb);google
tcp_accept(pcb, server_accept); tcp_poll(pcb, tcp_poll, 10);// 每5s執行一次tcp_poll;spa
4 當有新的鏈接到來時,即有新的數據接收時, server_accept 就好調用。在server_accept裏面註冊調用函數tcp_recv(pcb, server_recv);操作系統
此後每次新的數據過來時,就會調用server_recv函數。
5 在函數 static err_t server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)裏面對pbuf進行分析,要注意p->next的值。
而且借鑑其餘資料知道當即要調用tcp_recevd;很神祕的告訴你要調用不然很差使。後面這裏有重要發現!!!。
6 調用pbuf_free函數,來釋放pbuf;
客服端:
0~2步基本相同
3在client_init裏面 分別調用pcb = tcp_new(); ret_val = tcp_connect(pcb, &dest, 8000, client_connected);
4 在client_connected裏面發送鏈接請求。同時調用tcp_accept(pcb, client_recv)來註冊數據接收處理回調函數。
5 在static err_t client_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);對接收到的數據進行處理
相似server_recv。
搭建完成以後:師弟發現,在server_recv 函數裏面只可以tcp_write 128個字節的數據。當發送大於128個數據時,就會只可以接收128個數據。
找了半天發現[2]是由opt.h裏面的參數MSS 128決定的, 另外還要注意一個參數即TCP_SND_WND 256。
改變這兩個後,發送比TCP_SND_WND小且比MSS大的數據時, 客戶端能夠接受到一次等於MSS的數據。
試了各類策略後,客戶端只接收一次。咱們甚至懷疑是回調函數註冊一次只能使用一次的問題,都無效。
後面查詢各類資料[3], 後面發現英文資料lwip-user給出了不少啓示:
包括分析TCP segment(底層自行封裝成不一樣packet,不帶ACK) 和 IP segment(>MTU時,會帶ACK)
瞭解了opt.h中:
MSS (the smaller, the better) 128
TCP_SND_BUF 256
MEM_SIZE (1600) HEAP SIZE 若是tcp_write 用COPY的方式時是須要設置較大的值;
TCP_SND_QUEUELEN 4*(TCP_SND_BUF/tcp_mss)(最小爲除式的兩倍);
MEMP_NUM_TCP_SEG: 至少跟上面同樣大
MEMP_NUM_PBUF(16) --->32
TCP_WND 2048 接收窗口,接收多少個數據包的問題。
今天經過wireshark抓包發現,1.15 端發送給了 1.111端後面多於MSS的數據報文,並且發送端一直髮送MSS長度的報文,
能夠得知應該是接收端的沒有應答正確。前面提到的tcp_recevd調用的是tcp_ack函數。其實就是對接收到的數據進行應答
可是同時發現有一個好用的函數 tcp_ack_now(pcb),是直接發送應答,由於後面跟了tcp_output(pcb)函數。
而且要了解flags 設置 pcb->flags |= TF_NODELAY | TF_ACK_NOW; len = tcp_sndbuf(pcb);會告知剩餘的buf的長度
而且屢次發送的時候能夠設置這個值,從而手動告知系統還有多少buf能夠發送,從而實現了屢次發送的操做。
改了這兩處以後,實驗能夠發送32000個字節,不錯。
參考文檔:
[1] http://lwip.wikia.com/wiki/Category:LwIP_Application_Developers_Manual
[2] https://groups.google.com/forum/#!topic/osdeve_mirror_tcpip_lwip/4GAbtXFwlvk
[3] 百度:關於LWIP協議棧連續屢次tcp_write後失敗的解決過程
[4] lwip design and implementation