知識點1【TFTP的概述】
學習通信的基本:通信協議(具體發送上面樣的報文)、通信流程(按照什么步驟發送)
1、TFTP的概述
tftp:簡單文件傳輸協議,**基于UDP,**不進行用戶有效性驗證
基于UDP:編程架構是UDP的
必須創建UDP套接字
套接字分類:UDP套接字,TCP套接字,原始套接字
數據傳輸格式(模式選擇):
octet:二進制模式
netascii:文本模式
2、TFTP的通信過程
決定了 編程流程
我們主要寫客戶端
注意:
服務器中,在69號端口后,是臨時端口,臨時端口是只要socket,不bind。因此需要創建一個新的線程,線程中執行socket()
TFTP通信過程總結
3、TFTP協議分析
讀寫請求是用戶自己發送的,我們現在不考慮選項部分。
數據包516個字節,當小于516個字節的時候,表示下載 結束(最后一個報文),塊編號中存儲的是該數據包的編號。這里注意一定要對操作碼進行判斷,是3才可以確認是數據包。
收到數據包 我們需要回復一個ACK,ACK中的塊編號需要和數據包的ACK相同
這里有一個技巧,客戶端直接把收到的內容的前4個字節發送給服務器,只需要修改操作碼的第二個字節即可
錯誤碼操作碼是5,差錯碼是不同的錯誤有不同的編號,差錯信息說明錯誤。
OACK是比如我們設置了選項部分,需要設置的,相對復雜,我們下面介紹。
想一想
傳輸的數據的大小一定是 512Byte 嗎?
由于網絡的原因,一方收不到另一方的數據怎么辦?
1、不一定需要512,選項修改
2、這里服務器發送數據包后,如果沒有收到ACK,它會等待,并從發送數據包處開始計時,超出 超時時間后,才會發出錯誤碼,提示重發。
而這里的數據包長度修改,超時時間的修改,都需要借助選項來完成
4、TFTP帶選項的讀寫報文
選項和值成對出現。
OACK是在修改了選項后,服務器向客戶端發送的一個報文,是起一個確認作用的。
我們結合上圖,客戶端一旦修改了選項部分,客戶端不再是立即發送數據,而是發送OACK,讓客戶端確認是否要這樣修改(通過ACK,塊編號中:0表示同意,這也是為什么數據編號是從1開始的),客戶端給予回應后,服務器才會執行發送數據的操作。
選項的種類
tsize 選項
當讀操作時,tsize 選項的參數必須為“0”,服務器會返回待讀取的文件的大小
當寫操作時,tsize 選項參數應為待寫入文件的大小,服務器會回顯該選項
blksize 選項
修改傳輸文件時使用的數據塊的大小(范圍:8~65464)
timeout 選項
修改默認的數據傳輸超時時間(單位:秒)
注意:
1、選項沒有順序
2、在使用tsize 讀操作的時候,tsize的默認參數是0(因為讀時不知道文件的大小),收到OACK的時候,會返回實際文件的大小(nB說明是字符串需要轉換)
總結
1、可以通過發送帶選項的讀/寫請求發送給 server 2、如果 server 允許修改選項則發送選項修改確認包 3、server 發送的數據、選項修改確認包都是臨時 port 4、server 通過 timeout 來對丟失數據包的重新發送
知識點2【TFTP案例】
需求:編寫TFTP客戶端(不帶選項),從客戶端下載文件
流程:
1、創建UDP套接字
2、構建讀請求報文,并發送到服務器69號端口
3、while 循環接收,buf是516個字節,recvform的返回值是獲取到數據的長度
這里需要創建一個文件描述符取接收
4、關閉文件描述符 和 套接字
構建 讀寫請求報文 的方式
使用sprintf組包,當sendto時,有一個內容長度,使用sprintf的返回值:組包的總長度,目的端口寫69
代碼演示
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char const *argv[])
{if(argc != 3){printf("error\\n");printf("modo:./a.out 196.168.9.28 a.txt\\n");_exit(-1);}//創建套接字,目的:向服務器發送數據int fd_sock = socket(AF_INET,SOCK_DGRAM,0);if(fd_sock < 0){perror("socket");return 0;}//sprintf組包,返回值使用len_sprintf接收。格式: 0 1 文件名 0 模式 0 char arr_sprintf[64] = "";int len_sprintf = sprintf(arr_sprintf,"%c%c%s%c%s%c",0,1,argv[2],0,"octet",0);//發送sprintf組包(讀寫報文)struct sockaddr_in addr_rw;memset(&addr_rw,0,sizeof(addr_rw));addr_rw.sin_family = AF_INET;addr_rw.sin_port = htons(69);addr_rw.sin_addr.s_addr = inet_addr(argv[1]);sendto(fd_sock,arr_sprintf,len_sprintf,0,(struct sockaddr *)&addr_rw,sizeof(addr_rw));/* 接收服務器發出的 數據報文,報文的前四個字節:操作碼(2),端口碼(2),數據(512),存儲在arr_recv中數據段操作碼判斷,下面是3的操作重復性判斷,定義一個unsigned short num = 0,num+1與端口碼比較數據寫入文件,并將端口碼賦值給numACK 回應服務器 arr_recv前四個字節進行處理即可*///操作碼為5操作//關閉套接字 及 文件描述符 //輸出錯誤原因unsigned short num = 0;int fd_w = open(argv[2],O_WRONLY | O_CREAT,0664);if(fd_w < 0){close(fd_sock);perror("open");return 0;}int len = 0;unsigned char arr_recv[516] = "";struct sockaddr_in addr_recv;int len_recv = sizeof(addr_recv);do{len = recvfrom(fd_sock,arr_recv,sizeof(arr_recv),0,(struct sockaddr *)&addr_recv,&len_recv);if(arr_recv[1] == 3){if((unsigned short)(num + 1) == ntohs(*(unsigned short *)(arr_recv +2))){write(fd_w,arr_recv + 4,len - 4);//ack應答num = ntohs(*(unsigned short *)(arr_recv +2));printf("%d\\n",num);}arr_recv[1] = 4;sendto(fd_sock,arr_recv,4,0,(struct sockaddr *)&addr_recv,sizeof(addr_recv));}else if(arr_recv[1] == 5){close(fd_sock);close(fd_w);unlink(argv[2]);printf("%s\\n",arr_recv+4);_exit(-1);}} while (len == 516);//關閉文件描述符,套接字,關閉文件close(fd_w);close(fd_sock);return 0;
}
代碼運行結果
結束
代碼重在練習!
代碼重在練習!
代碼重在練習!
今天的分享就到此結束了,希望對你有所幫助,如果你喜歡我的分享,請點贊收藏夾關注,謝謝大家!!!