LwIP应用开发笔记之四:LwIP无操作系统TFTP服务器
前面我們已經(jīng)實現(xiàn)了UDP的回環(huán)客戶端和回環(huán)服務(wù)器的簡單應(yīng)用,接下來我們實現(xiàn)一個基于UDP的簡單文件傳輸協(xié)議TFTP。
1、TFTP協(xié)議簡介
TFTP是TCP/IP協(xié)議族中的一個用來在客戶機與服務(wù)器之間進行簡單文件傳輸?shù)膮f(xié)議,提供不復(fù)雜、開銷不大的文件傳輸服務(wù)。端口號為69
TFTP是一種簡單的文件傳輸協(xié)議。目標是在UDP之上上建立一個類似于FTP的但僅支持文件上傳和下載功能的傳輸協(xié)議,所以它不包含FTP協(xié)議中的目錄操作和用戶權(quán)限等內(nèi)容。
TFTP報文的頭兩個字節(jié)表示操作碼,共有5中操作碼,如下表:
讀請求和寫請求功能碼的數(shù)據(jù)報文格式是一樣的,所以TFTP報文又可表述為4種形式。對于讀請求或者寫請求,文件名字段說明客戶要讀或?qū)懙奈挥诜?wù)器的上的文件并以0字節(jié)作為結(jié)束,模式字段是一個ASCII碼串,同樣以0字節(jié)結(jié)束。讀請求和寫請求的報文格式:
其次是數(shù)據(jù)包,起包括2個字節(jié)的塊編號以及0-512個字節(jié)的數(shù)據(jù)信息。數(shù)據(jù)包相對比較簡單,其報文格式:
再者為確認包。確認包也有2個字節(jié)的塊編號。其數(shù)據(jù)格式:
最后一種TFTP報文類型是差錯報文,它的操作碼為5.它用于服務(wù)器不能處理讀請求或者寫請求的情況。在文件傳輸?shù)倪^程中的讀和寫也會導(dǎo)致傳送這種報文,接著停止傳輸。錯誤包的報文格式:
TFTP的工作過程很像停止等待協(xié)議,發(fā)送完一個文件塊后就等待對方的確認,確認時應(yīng)指明所確認的塊號。發(fā)送完數(shù)據(jù)后在規(guī)定時間內(nèi)收不到確認就要重發(fā)數(shù)據(jù)PDU,發(fā)送確認PDU的一方若在規(guī)定時間內(nèi)收不到下一個文件塊,也要重發(fā)確認PDU。這樣保證文件的傳送不致因某一個數(shù)據(jù)報的丟失而告失敗。
2、TFTP協(xié)議棧設(shè)計
前面我們簡單的介紹了TFTP協(xié)議,接下來我們看看該如何實現(xiàn)其編程。它有5種操作碼,我們要做的就是實現(xiàn)對這5種操作碼的響應(yīng)。
2.1、讀請求實現(xiàn)
所謂讀請求,就是客戶端請求從服務(wù)器獲取文件,那么服務(wù)器需要做的自然是響應(yīng)客戶端的請求。但我們并沒有文件,所以不管它請求什么文件,我們均給它返回內(nèi)容和大小相同的測試文件。
/* TFTP讀請求處理*/ int TftpReadProcess(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, char* FileName) {tftp_connection_args *args = NULL;/* 這個函數(shù)在回調(diào)函數(shù)中被調(diào)用,因此中斷被禁用,因此我們可以使用常規(guī)的malloc */args = mem_malloc(sizeof(tftp_connection_args));if (!args){/* 內(nèi)存分配失敗 */SendTftpErrorMessage(upcb, to, to_port, TFTP_ERR_NOTDEFINED);CleanTftpConnection(upcb, args);return 0;}/* i初始化連接結(jié)構(gòu)體 */args->op = TFTP_RRQ;args->remote_port = to_port;args->block = 1;/* 塊號從1開始 */args->tot_bytes = 10*1024*1024;/* 注冊回調(diào)函數(shù) */udp_recv(upcb, RrqReceiveCallback, args);/* 通過發(fā)送第一個塊來建立連接,后續(xù)塊在收到ACK后發(fā)送*/SendNextBlock(upcb, args, to, to_port);return 1; }2.2、寫請求實現(xiàn)
寫請求就是客戶端希望向服務(wù)器傳送文件,在這里我們只是實現(xiàn)TFTP服務(wù)器的功能,沒必要將收到的文件真正保存到一個地方,所以只是做接收文件的過程并不將其寫到存儲器,簡單的說就是只在內(nèi)存中而不會寫入Flash等。
/* TFTP寫請求處理 */ int TftpWriteProcess(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, char *FileName) {tftp_connection_args *args = NULL;/* 這個函數(shù)在回調(diào)函數(shù)中被調(diào)用,因此中斷被禁用,因此我們可以使用常規(guī)的malloc */args = mem_malloc(sizeof(tftp_connection_args));if (!args){SendTftpErrorMessage(upcb, to, to_port, TFTP_ERR_NOTDEFINED);CleanTftpConnection(upcb, args);return 0;}args->op = TFTP_WRQ;args->remote_port = to_port;args->block = 0; //WRQ響應(yīng)的塊號為0args->tot_bytes = 0;/* 為控制塊注冊回調(diào)函數(shù) */udp_recv(upcb, WrqReceiveCallback, args);/* 通過發(fā)送第一個ack來發(fā)起寫事務(wù) */SendTftpAckPacket(upcb, to, to_port, args->block); return 0; }2.3、數(shù)據(jù)包操作
無論是讀請求還是寫請求,最終的目的無非是要傳送數(shù)據(jù),所以數(shù)據(jù)包自然也是我們需要構(gòu)造和傳送的。其對應(yīng)的就是數(shù)據(jù)包操作碼,我們設(shè)計程序如下:
/* 構(gòu)造并且傳送數(shù)據(jù)包 */ static int SendTftpDataPacket(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, int block,char *buf, int buflen) {/* 將開始的2個字節(jié)設(shè)置為功能碼 */SetTftpOpCode(buf, TFTP_DATA);/* 將后續(xù)2個字節(jié)設(shè)置為塊號 */SetTftpBlockNumber(buf, block);/* 在后續(xù)設(shè)置n各字節(jié)的數(shù)據(jù) *//* 發(fā)送數(shù)據(jù)包 */return SendTftpMessage(upcb, to, to_port, buf, buflen + 4); }2.4、確認包操作
在傳送數(shù)據(jù)包后,收到?jīng)]收到,發(fā)送方是不知道的,怎么辦呢?這時候接受方接收到后,會給出一個確認包。其對應(yīng)的就是確認操作碼,那么我們還需實現(xiàn)確認包的構(gòu)造和發(fā)送。
/*構(gòu)造并發(fā)送確認包*/ int SendTftpAckPacket(struct udp_pcb *upcb,const ip_addr_t *to, int to_port, int block) {/* 創(chuàng)建一個TFTP ACK包 */char packet[TFTP_ACK_PKT_LEN];/* 將開始的2個字節(jié)設(shè)置為功能碼 */SetTftpOpCode(packet, TFTP_ACK);/* 制定ACK的塊號 */SetTftpBlockNumber(packet, block);return SendTftpMessage(upcb, to, to_port, packet, TFTP_ACK_PKT_LEN); }2.5、錯誤包操作
在包傳送的過程中,有沒有可能出現(xiàn)錯誤呢?當然是有的,這就需要所謂的錯誤包操作碼。在服務(wù)器不能處理讀請求或者寫請求的情況下。在文件傳輸?shù)倪^程中的讀和寫也會導(dǎo)致傳送這種報文,接著停止傳輸。我們也需要開發(fā)構(gòu)造和傳送錯誤包的函數(shù)。
/* 構(gòu)造并向客戶端發(fā)送一條錯誤消息 */ static int SendTftpErrorMessage(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, tftp_errorcode err) {char buf[512];int error_len;error_len = ConstructTftpErrorMessage(buf, err);return SendTftpMessage(upcb, to, to_port, buf, error_len); }3、TFTP服務(wù)器實現(xiàn)
我們已經(jīng)實現(xiàn)了UDP服務(wù)器,而且也實現(xiàn)了簡單的TFTP協(xié)議棧,接下來的工作就是在UDP基礎(chǔ)上實現(xiàn)TFTP服務(wù)器功能。前面我們已經(jīng)提到過,復(fù)雜的服務(wù)器應(yīng)用只是回到函數(shù)的功能不一樣,所以開發(fā)的過程并無區(qū)別。
首先我們來實現(xiàn)初始化部分。創(chuàng)建新的UDP控制塊。綁定到制定的服務(wù)器端口,我們要實現(xiàn)TFTP服務(wù)器,而TFTP協(xié)議的端口號為69,所以我們將其綁定到該端口。最后注冊TFTP服務(wù)器的回調(diào)函數(shù)。
/* 初始化TFTP服務(wù)器 */ void Tftp_Server_Initialization(void) {err_t err;struct udp_pcb *tftp_server_pcb = NULL;/* 生成新的 UDP PCB控制塊 */tftp_server_pcb = udp_new();/* 判斷UDP控制塊是否正確生成 */if (NULL == tftp_server_pcb){return;}/* 綁定PCB控制塊到指定端口 */err = udp_bind(tftp_server_pcb, IP_ADDR_ANY, UDP_TFTP_SERVER_PORT);if (err != ERR_OK){udp_remove(tftp_server_pcb);return;}/* 注冊TFTP服務(wù)器處理函數(shù) */udp_recv(tftp_server_pcb, TftpServerCallback, NULL); }在初始化中注冊了回調(diào)函數(shù),所以我們還要實現(xiàn)TFTP服務(wù)器的回調(diào)函數(shù)。這部分出于結(jié)構(gòu)清晰的考慮,我們分成兩個函數(shù)來寫。
/* TFTP服務(wù)器回調(diào)函數(shù) */ static void TftpServerCallback(void *arg, struct udp_pcb *upcb, struct pbuf *p,const ip_addr_t *addr, u16_t port) {/* 處理新的連接請求 */ProcessTftpRequest(p, addr, port);pbuf_free(p); } /* 從每一個來自addr:port的新請求創(chuàng)建一個新的端口來服務(wù)響應(yīng),并啟動響應(yīng)過程 */ static void ProcessTftpRequest(struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port) {tftp_opcode op = ExtractTftpOpcode(pkt_buf->payload);char FileName[50] = {0};struct udp_pcb *upcb = NULL;err_t err;/* 生成新的UDP PCB控制塊 */upcb = udp_new();if (!upcb){return;}/* 連接 */err = udp_connect(upcb, addr, port);if (err != ERR_OK){return;}ExtractTftpFilename(FileName, pkt_buf->payload);switch (op){case TFTP_RRQ:{TftpReadProcess(upcb, addr, port, FileName);break;}case TFTP_WRQ:{/* 啟動TFTP寫模式 */TftpWriteProcess(upcb, addr, port, FileName);break;}default:{/* 異常,發(fā)送錯誤消息 */SendTftpErrorMessage(upcb, addr, port, TFTP_ERR_ACCESS_VIOLATION);udp_remove(upcb);break;}} }在回調(diào)函數(shù)中,我們實現(xiàn)了對TFTP讀請求和寫請求的響應(yīng),但這足以驗證我們想要實現(xiàn)的TFTP服務(wù)器的功能。
4、結(jié)論
本篇我們基于LwIP的UDP實現(xiàn)了一個簡單的FTP服務(wù)器。這個FTP服務(wù)器只是實現(xiàn)FTP協(xié)議的功能,具體的應(yīng)用可根據(jù)需要添加。我們使用了TFTP客戶端工具對這一服務(wù)器進行了基本測試,最終結(jié)果符合我們的預(yù)期。
歡迎關(guān)注:
總結(jié)
以上是生活随笔為你收集整理的LwIP应用开发笔记之四:LwIP无操作系统TFTP服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go Concurrency Patte
- 下一篇: 探索比特币源码3-熟悉RPC接口