前面我們已經(jīng)實(shí)現(xiàn)了UDP的回環(huán)客戶端和回環(huán)服務(wù)器的簡(jiǎn)單應(yīng)用,接下來(lái)我們實(shí)現(xiàn)一個(gè)基于UDP的簡(jiǎn)單文件傳輸協(xié)議TFTP。
1 、 TFTP****協(xié)議簡(jiǎn)介
TFTP是TCP/IP協(xié)議族中的一個(gè)用來(lái)在客戶機(jī)與服務(wù)器之間進(jìn)行簡(jiǎn)單文件傳輸?shù)膮f(xié)議,提供不復(fù)雜、開(kāi)銷(xiāo)不大的文件傳輸服務(wù)。端口號(hào)為69
TFTP是一種簡(jiǎn)單的文件傳輸協(xié)議。目標(biāo)是在UDP之上上建立一個(gè)類(lèi)似于FTP的但僅支持文件上傳和下載功能的傳輸協(xié)議,所以它不包含F(xiàn)TP協(xié)議中的目錄操作和用戶權(quán)限等內(nèi)容。
TFTP報(bào)文的頭兩個(gè)字節(jié)表示操作碼,共有5中操作碼,如下表:
讀請(qǐng)求和寫(xiě)請(qǐng)求功能碼的數(shù)據(jù)報(bào)文格式是一樣的,所以TFTP報(bào)文又可表述為4種形式。對(duì)于讀請(qǐng)求或者寫(xiě)請(qǐng)求,文件名字段說(shuō)明客戶要讀或?qū)懙奈挥诜?wù)器的上的文件并以0字節(jié)作為結(jié)束,模式字段是一個(gè)ASCII碼串,同樣以0字節(jié)結(jié)束。讀請(qǐng)求和寫(xiě)請(qǐng)求的報(bào)文格式:
其次是數(shù)據(jù)包,起包括2個(gè)字節(jié)的塊編號(hào)以及0-512個(gè)字節(jié)的數(shù)據(jù)信息。數(shù)據(jù)包相對(duì)比較簡(jiǎn)單,其報(bào)文格式:
再者為確認(rèn)包。確認(rèn)包也有2個(gè)字節(jié)的塊編號(hào)。其數(shù)據(jù)格式:
最后一種TFTP報(bào)文類(lèi)型是差錯(cuò)報(bào)文,它的操作碼為5.它用于服務(wù)器不能處理讀請(qǐng)求或者寫(xiě)請(qǐng)求的情況。在文件傳輸?shù)倪^(guò)程中的讀和寫(xiě)也會(huì)導(dǎo)致傳送這種報(bào)文,接著停止傳輸。錯(cuò)誤包的報(bào)文格式:
TFTP的工作過(guò)程很像停止等待協(xié)議,發(fā)送完一個(gè)文件塊后就等待對(duì)方的確認(rèn),確認(rèn)時(shí)應(yīng)指明所確認(rèn)的塊號(hào)。發(fā)送完數(shù)據(jù)后在規(guī)定時(shí)間內(nèi)收不到確認(rèn)就要重發(fā)數(shù)據(jù)PDU,發(fā)送確認(rèn)PDU的一方若在規(guī)定時(shí)間內(nèi)收不到下一個(gè)文件塊,也要重發(fā)確認(rèn)PDU。這樣保證文件的傳送不致因某一個(gè)數(shù)據(jù)報(bào)的丟失而告失敗。
2 、 TFTP****協(xié)議棧設(shè)計(jì)
前面我們簡(jiǎn)單的介紹了TFTP協(xié)議,接下來(lái)我們看看該如何實(shí)現(xiàn)其編程。它有5種操作碼,我們要做的就是實(shí)現(xiàn)對(duì)這5種操作碼的響應(yīng)。
2.1 、讀請(qǐng)求實(shí)現(xiàn)
所謂讀請(qǐng)求,就是客戶端請(qǐng)求從服務(wù)器獲取文件,那么服務(wù)器需要做的自然是響應(yīng)客戶端的請(qǐng)求。但我們并沒(méi)有文件,所以不管它請(qǐng)求什么文件,我們均給它返回內(nèi)容和大小相同的測(cè)試文件。
1 /* TFTP讀請(qǐng)求處理*/
2 int TftpReadProcess(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, char* FileName)
3 {
4 tftp_connection_args *args = NULL;
5
6 /* 這個(gè)函數(shù)在回調(diào)函數(shù)中被調(diào)用,因此中斷被禁用,因此我們可以使用常規(guī)的malloc */
7 args = mem_malloc(sizeof(tftp_connection_args));
8
9 if (!args)
10 {
11 /* 內(nèi)存分配失敗 */
12 SendTftpErrorMessage(upcb, to, to_port, TFTP_ERR_NOTDEFINED);
13
14 CleanTftpConnection(upcb, args);
15
16 return 0;
17 }
18
19 /* i初始化連接結(jié)構(gòu)體 */
20 args->op = TFTP_RRQ;
21 args->remote_port = to_port;
22 args->block = 1; /* 塊號(hào)從1開(kāi)始 */
23 args->tot_bytes = 10*1024*1024;
24
25 /* 注冊(cè)回調(diào)函數(shù) */
26 udp_recv(upcb, RrqReceiveCallback, args);
27
28 /* 通過(guò)發(fā)送第一個(gè)塊來(lái)建立連接,后續(xù)塊在收到ACK后發(fā)送*/
29 SendNextBlock(upcb, args, to, to_port);
30
31 return 1;
32 }
2.2 、寫(xiě)請(qǐng)求實(shí)現(xiàn)
寫(xiě)請(qǐng)求就是客戶端希望向服務(wù)器傳送文件,在這里我們只是實(shí)現(xiàn)TFTP服務(wù)器的功能,沒(méi)必要將收到的文件真正保存到一個(gè)地方,所以只是做接收文件的過(guò)程并不將其寫(xiě)到存儲(chǔ)器,簡(jiǎn)單的說(shuō)就是只在內(nèi)存中而不會(huì)寫(xiě)入Flash等。
1 /* TFTP寫(xiě)請(qǐng)求處理 */
2 int TftpWriteProcess(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, char *FileName)
3 {
4 tftp_connection_args *args = NULL;
5
6 /* 這個(gè)函數(shù)在回調(diào)函數(shù)中被調(diào)用,因此中斷被禁用,因此我們可以使用常規(guī)的malloc */
7 args = mem_malloc(sizeof(tftp_connection_args));
8
9 if (!args)
10 {
11 SendTftpErrorMessage(upcb, to, to_port, TFTP_ERR_NOTDEFINED);
12
13 CleanTftpConnection(upcb, args);
14
15 return 0;
16 }
17
18 args->op = TFTP_WRQ;
19 args->remote_port = to_port;
20 args->block = 0; //WRQ響應(yīng)的塊號(hào)為0
21 args->tot_bytes = 0;
22
23 /* 為控制塊注冊(cè)回調(diào)函數(shù) */
24 udp_recv(upcb, WrqReceiveCallback, args);
25
26 /* 通過(guò)發(fā)送第一個(gè)ack來(lái)發(fā)起寫(xiě)事務(wù) */
27 SendTftpAckPacket(upcb, to, to_port, args->block);
28
29 return 0;
30 }
2.3 、數(shù)據(jù)包操作
無(wú)論是讀請(qǐng)求還是寫(xiě)請(qǐng)求,最終的目的無(wú)非是要傳送數(shù)據(jù),所以數(shù)據(jù)包自然也是我們需要構(gòu)造和傳送的。其對(duì)應(yīng)的就是數(shù)據(jù)包操作碼,我們?cè)O(shè)計(jì)程序如下:
1 /* 構(gòu)造并且傳送數(shù)據(jù)包 */
2 static int SendTftpDataPacket(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, int block,char *buf, int buflen)
3 {
4 /* 將開(kāi)始的2個(gè)字節(jié)設(shè)置為功能碼 */
5 SetTftpOpCode(buf, TFTP_DATA);
6
7 /* 將后續(xù)2個(gè)字節(jié)設(shè)置為塊號(hào) */
8 SetTftpBlockNumber(buf, block);
9
10 /* 在后續(xù)設(shè)置n個(gè)字節(jié)的數(shù)據(jù) */
11
12 /* 發(fā)送數(shù)據(jù)包 */
13 return SendTftpMessage(upcb, to, to_port, buf, buflen + 4);
14 }
2.4 、確認(rèn)包操作
在傳送數(shù)據(jù)包后,收到?jīng)]收到,發(fā)送方是不知道的,怎么辦呢?這時(shí)候接受方接收到后,會(huì)給出一個(gè)確認(rèn)包。其對(duì)應(yīng)的就是確認(rèn)操作碼,那么我們還需實(shí)現(xiàn)確認(rèn)包的構(gòu)造和發(fā)送。
1 /*構(gòu)造并發(fā)送確認(rèn)包*/
2 int SendTftpAckPacket(struct udp_pcb *upcb,const ip_addr_t *to, int to_port, int block)
3 {
4 /* 創(chuàng)建一個(gè)TFTP ACK包 */
5 char packet[TFTP_ACK_PKT_LEN];
6
7 /* 將開(kāi)始的2個(gè)字節(jié)設(shè)置為功能碼 */
8 SetTftpOpCode(packet, TFTP_ACK);
9
10 /* 制定ACK的塊號(hào) */
11 SetTftpBlockNumber(packet, block);
12
13 return SendTftpMessage(upcb, to, to_port, packet, TFTP_ACK_PKT_LEN);
14 }
2.5 、錯(cuò)誤包操作
在包傳送的過(guò)程中,有沒(méi)有可能出現(xiàn)錯(cuò)誤呢?當(dāng)然是有的,這就需要所謂的錯(cuò)誤包操作碼。在服務(wù)器不能處理讀請(qǐng)求或者寫(xiě)請(qǐng)求的情況下。在文件傳輸?shù)倪^(guò)程中的讀和寫(xiě)也會(huì)導(dǎo)致傳送這種報(bào)文,接著停止傳輸。我們也需要開(kāi)發(fā)構(gòu)造和傳送錯(cuò)誤包的函數(shù)。
1 /* 構(gòu)造并向客戶端發(fā)送一條錯(cuò)誤消息 */
2 static int SendTftpErrorMessage(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, tftp_errorcode err)
3 {
4 char buf[512];
5 int error_len;
6
7 error_len = ConstructTftpErrorMessage(buf, err);
8
9 return SendTftpMessage(upcb, to, to_port, buf, error_len);
10 }
3 、 TFTP****服務(wù)器實(shí)現(xiàn)
我們已經(jīng)實(shí)現(xiàn)了UDP服務(wù)器,而且也實(shí)現(xiàn)了簡(jiǎn)單的TFTP協(xié)議棧,接下來(lái)的工作就是在UDP基礎(chǔ)上實(shí)現(xiàn)TFTP服務(wù)器功能。前面我們已經(jīng)提到過(guò),復(fù)雜的服務(wù)器應(yīng)用只是回到函數(shù)的功能不一樣,所以開(kāi)發(fā)的過(guò)程并無(wú)區(qū)別。
首先我們來(lái)實(shí)現(xiàn)初始化部分。創(chuàng)建新的UDP控制塊。綁定到制定的服務(wù)器端口,我們要實(shí)現(xiàn)TFTP服務(wù)器,而TFTP協(xié)議的端口號(hào)為69,所以我們將其綁定到該端口。最后注冊(cè)TFTP服務(wù)器的回調(diào)函數(shù)。
1 /* 初始化TFTP服務(wù)器 */
2 void Tftp_Server_Initialization(void)
3 {
4 err_t err;
5 struct udp_pcb *tftp_server_pcb = NULL;
6
7 /* 生成新的 UDP PCB控制塊 */
8 tftp_server_pcb = udp_new();
9
10 /* 判斷UDP控制塊是否正確生成 */
11 if (NULL == tftp_server_pcb)
12 {
13 return;
14 }
15
16 /* 綁定PCB控制塊到指定端口 */
17 err = udp_bind(tftp_server_pcb, IP_ADDR_ANY, UDP_TFTP_SERVER_PORT);
18
19 if (err != ERR_OK)
20 {
21 udp_remove(tftp_server_pcb);
22 return;
23 }
24
25 /* 注冊(cè)TFTP服務(wù)器處理函數(shù) */
26 udp_recv(tftp_server_pcb, TftpServerCallback, NULL);
27 }
在初始化中注冊(cè)了回調(diào)函數(shù),所以我們還要實(shí)現(xiàn)TFTP服務(wù)器的回調(diào)函數(shù)。這部分出于結(jié)構(gòu)清晰的考慮,我們分成兩個(gè)函數(shù)來(lái)寫(xiě)。
1 /* TFTP服務(wù)器回調(diào)函數(shù) */
2 static void TftpServerCallback(void *arg, struct udp_pcb *upcb, struct pbuf *p,const ip_addr_t *addr, u16_t port)
3 {
4 /* 處理新的連接請(qǐng)求 */
5 ProcessTftpRequest(p, addr, port);
6
7 pbuf_free(p);
8 }
9 /* 從每一個(gè)來(lái)自addr:port的新請(qǐng)求創(chuàng)建一個(gè)新的端口來(lái)服務(wù)響應(yīng),并啟動(dòng)響應(yīng)過(guò)程 */
10 static void ProcessTftpRequest(struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port)
11 {
12 tftp_opcode op = ExtractTftpOpcode(pkt_buf->payload);
13 char FileName[50] = {0};
14 struct udp_pcb *upcb = NULL;
15 err_t err;
16
17 /* 生成新的UDP PCB控制塊 */
18 upcb = udp_new();
19 if (!upcb)
20 {
21 return;
22 }
23
24 /* 連接 */
25 err = udp_connect(upcb, addr, port);
26 if (err != ERR_OK)
27 {
28 return;
29 }
30
31 ExtractTftpFilename(FileName, pkt_buf->payload);
32
33 switch (op)
34 {
35 case TFTP_RRQ:
36 {
37
38 TftpReadProcess(upcb, addr, port, FileName);
39 break;
40 }
41 case TFTP_WRQ:
42 {
43 /* 啟動(dòng)TFTP寫(xiě)模式 */
44 TftpWriteProcess(upcb, addr, port, FileName);
45 break;
46 }
47 default:
48 {
49 /* 異常,發(fā)送錯(cuò)誤消息 */
50 SendTftpErrorMessage(upcb, addr, port, TFTP_ERR_ACCESS_VIOLATION);
51
52 udp_remove(upcb);
53
54 break;
55 }
56 }
57 }
在回調(diào)函數(shù)中,我們實(shí)現(xiàn)了對(duì)TFTP讀請(qǐng)求和寫(xiě)請(qǐng)求的響應(yīng),但這足以驗(yàn)證我們想要實(shí)現(xiàn)的TFTP服務(wù)器的功能。
4 、結(jié)論
本篇我們基于LwIP的UDP實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的FTP服務(wù)器。這個(gè)FTP服務(wù)器只是實(shí)現(xiàn)FTP協(xié)議的功能,具體的應(yīng)用可根據(jù)需要添加。我們使用了TFTP客戶端工具對(duì)這一服務(wù)器進(jìn)行了基本測(cè)試,最終結(jié)果符合我們的預(yù)期。
-
服務(wù)器
+關(guān)注
關(guān)注
12文章
9160瀏覽量
85415 -
TFTP
+關(guān)注
關(guān)注
0文章
20瀏覽量
14341 -
UDP
+關(guān)注
關(guān)注
0文章
325瀏覽量
33937 -
文件傳輸協(xié)議
+關(guān)注
關(guān)注
0文章
3瀏覽量
913
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論