TFTP(Trival File Transfer Protocal),簡單文件傳輸協(xié)議,該協(xié)議在端口69上使用UDP服務(wù)。TFTP協(xié)議常用于無盤工作站或路由器從別的主機(jī)上獲取引導(dǎo)配置文件,由于TFTP報文比較小,能個迅速復(fù)制這些文件
因為作者是做智能家居方向的開發(fā),公司初期實(shí)現(xiàn)硬件的升級是先通過手機(jī)端從服務(wù)器下載固件,然后在通過某種協(xié)議把固件傳輸給硬件設(shè)備,硬件設(shè)備接收完成之后進(jìn)行升級。這里就涉及到一個協(xié)議的選擇,固件本身并不是很大,一般在1M以內(nèi),這時候TFTP協(xié)議無非是最好的選擇之一,輕量級的傳輸不顯得復(fù)雜,對系統(tǒng)的開銷也小。但是網(wǎng)上找了很久都沒發(fā)現(xiàn)有相關(guān)的Demo,所以今天就簡單的基于OC搭建一個TFTP通信的客戶端和服務(wù)器(Demo簡單,暫不支持IPv6和數(shù)據(jù)包超時計時(Demo里面已經(jīng)加上超時處理和錯誤拋出))。
1.TFTP概況
TFTP是一個傳輸文件的簡單協(xié)議,它基于UDP協(xié)議而實(shí)現(xiàn),但是我們也不能確定有些TFTP協(xié)議是基于其它傳輸協(xié)議完成的。此協(xié)議設(shè)計的時候是進(jìn)行小文件傳輸?shù)?。因此它不具備通常的FTP的許多功能,它只能從文件服務(wù)器上獲得或?qū)懭胛募?br> TFTP傳輸起自一個讀取或?qū)懭胛募恼埱?,這個請求也是連接請求。如果服務(wù)器批準(zhǔn)此請求,則服務(wù)器打開連接,數(shù)據(jù)以定長傳輸(一般定在512字節(jié)以內(nèi))。每個數(shù)據(jù)包包括數(shù)據(jù)塊號和一塊數(shù)據(jù),服務(wù)器發(fā)出下一個數(shù)據(jù)包以前必須得到客戶對上一個數(shù)據(jù)包的確認(rèn)。如果一個數(shù)據(jù)包的大小小于規(guī)定長度,則表示傳輸結(jié)束。通信的雙方都是數(shù)據(jù)的發(fā)出者與接收者,一方傳輸數(shù)據(jù)接收應(yīng)答,另一方發(fā)出應(yīng)答接收數(shù)據(jù)。大部分的錯誤會導(dǎo)致連接中斷,錯誤由一個錯誤的數(shù)據(jù)包引起。這個協(xié)議限制很多,這些都是為了實(shí)現(xiàn)起來比較方便而進(jìn)行的。
2.TFTP協(xié)議
既然寫TFTP通信,上來最重要的肯定是協(xié)議

1:對于數(shù)據(jù)長度以字節(jié)來計算和標(biāo)識。
2:對于TFTP數(shù)據(jù)包我這里面只寫了其中五中,對于簡單的開發(fā)已經(jīng)足夠了,操作碼分別對應(yīng)RRQ讀請求、WRQ寫請求、DATA數(shù)據(jù)包、ACK數(shù)據(jù)包確認(rèn)、ERROR差錯包。
3:文件名、模式、差錯信息這些數(shù)據(jù)長度都是不固定的,文件分成數(shù)據(jù)塊傳輸,數(shù)據(jù)塊一般定在0-512個字節(jié)之間,太大可能傳輸?shù)木筒灰欢ò踩?,因為TFTP是基于UDP來進(jìn)行數(shù)據(jù)傳輸,在數(shù)據(jù)鏈路層有MTU的限制每個數(shù)據(jù)包的大?。?500字節(jié))
MTU(1500 byte)-PPP的包頭包尾的開銷(8 byte)-IP頭(20 byte)-UDP頭(8 byte) = 1464(byte)
從上面看出實(shí)際每個UDP包數(shù)據(jù)是在1464字節(jié)以內(nèi),如果超過這個臨界值,系統(tǒng)內(nèi)部會對數(shù)據(jù)包進(jìn)行分片傳輸,由于UDP數(shù)據(jù)包的發(fā)送和接收都是無確認(rèn),讓系統(tǒng)分包去傳送可能存在數(shù)據(jù)丟失,所以業(yè)界一般沒有將數(shù)據(jù)塊定太大,通用512個字節(jié)。
4:文件名和差錯信息最好都用英文字母或者英文字符,一般硬件是低級的單片機(jī),內(nèi)部存儲空間有限,所以一般里面不一定裝有UTF-8數(shù)據(jù)編碼表,一般都是ASCII編碼表,摻雜其他字符可能解析不出來,這個編碼方式具體看硬件
3.TFTP通信流程

1.首先服務(wù)器綁定固定端口號開始監(jiān)聽客戶端的連接
2.一切從客戶端發(fā)送的第一個數(shù)據(jù)包開始,里面包含有讀文件還是寫文件的操作碼,需要操作的文件名
3.服務(wù)器找到對應(yīng)的文件,開始分成塊傳送給設(shè)備
4.設(shè)備收到對應(yīng)的數(shù)據(jù)塊后回應(yīng)個服務(wù)器確認(rèn)包,里面包含確認(rèn)塊號,告訴服務(wù)器這塊我收到了你可以傳下塊了
5.直到最后一個分包傳給設(shè)備,設(shè)備根據(jù)包的大小和規(guī)定分片的大小做對比,如果小于規(guī)定的分片則表示是最后一個數(shù)據(jù)包,向服務(wù)器發(fā)送一個ACK確認(rèn)包,然后關(guān)閉連接,服務(wù)器收到最后一個確認(rèn)的ACK后也關(guān)掉自己的Socket,本次傳輸完成;如果最后一個包正好也是分片的大小,服務(wù)器接下來還得傳輸一個操作碼后面數(shù)據(jù)長度為0的數(shù)據(jù)包過去,這樣客戶端才知道沒有數(shù)據(jù)了
4.代碼實(shí)現(xiàn)部分
話不多說,先看Demo效果(效果圖為GIF動態(tài)圖,動畫只執(zhí)行一次,看不到效果可以刷新下頁面重新播放)

(1)服務(wù)器
① 首先初始化套接字,并綁定到指定端口(我這里用傳進(jìn)來的一個端口號,并沒有寫成固定的69),同時檢查創(chuàng)建的套接字的讀寫能力(下面要用這個套接字監(jiān)聽設(shè)備數(shù)據(jù)返回和向設(shè)備發(fā)送數(shù)據(jù))
//套接字初始化(Create Socket)
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd <= 0) {
[self throwErrorWithCode:errno reason:@"Failed to create socket"];
return;
}
//綁定監(jiān)聽地址
struct sockaddr_in addr_server;
addr_server.sin_len = sizeof(struct sockaddr_in);
addr_server.sin_family = AF_INET;
addr_server.sin_port = htons(bindPort);
addr_server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(_sockfd, (struct sockaddr*)&addr_server, addr_server.sin_len) < 0) {
[self throwErrorWithCode:errno reason:@"Binding socket failed"];
return;
}
②監(jiān)聽客戶端的連接,直到有數(shù)據(jù)請求,同時設(shè)置等待超時時間為30s(30s內(nèi)沒有連接則關(guān)閉套接字) ->如果有連接則進(jìn)入步驟③,數(shù)據(jù)解析
//申明一個接受客戶端連接套接字的地址
struct sockaddr_in addr_clict;
socklen_t addr_clict_len = sizeof(struct sockaddr_in);
addr_clict.sin_len = addr_clict_len;
//設(shè)置接收請求連接超時時間為30s
struct timeval timeout = {30,0};
if (setsockopt(_sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) {
printf("開始設(shè)置Socket服務(wù)器接收連接超時失敗: %s\n",strerror(errno));
}
while (1) {
if (_isOpen == NO) return; //服務(wù)器關(guān)閉直接退出
char recv_buffer[1024]; //接收數(shù)據(jù)緩沖區(qū)
ssize_t result_recv = recvfrom(_sockfd, recv_buffer, sizeof(recv_buffer), 0, (struct sockaddr*)&addr_clict, &addr_clict_len);
if (result_recv < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Read data error"];
return;
}
if (result_recv < 4) continue; //數(shù)據(jù)包長度必須大于或等于4,否則不是我們想要的數(shù)據(jù)
//有接收到數(shù)據(jù),進(jìn)入下面的數(shù)據(jù)解析部分 ->③
}
③有接收到數(shù)據(jù),開始解析數(shù)據(jù)是否是客戶端的連接請求,如果是客戶端的連接請求則‘連接’該套接字,實(shí)際上叫注冊更準(zhǔn)確,這里的connect() 并不創(chuàng)建實(shí)質(zhì)意義上的連接,只是向套接字中注冊目的地址信息,方便后面數(shù)據(jù)的收發(fā)(不用每次發(fā)送和接收數(shù)據(jù)都注冊地址信息),接著解析出請求文件名并將該文件加載到緩存,開始下面的數(shù)據(jù)傳輸
if (recv_buffer[1] == TFTP_RRQ) { //操作碼是讀請求 -> 有客戶端連接
//注冊客戶端地址信息
if (connect(_sockfd, (struct sockaddr*)&addr_clict, sizeof(addr_clict)) != 0) {
[self throwErrorWithCode:errno reason:@"Registration destination address failed"];
return;
}
//1. 解析出文件名
char* cFileName = &recv_buffer[2];
NSLog(@"[TFTPServer] 收到第一個請求包IP: %s, 文件名: %s",inet_ntoa(addr_clict.sin_addr),cFileName);
//2. 拼接路徑
_filepath = [_filepath stringByAppendingPathComponent:[NSString stringWithCString:cFileName encoding:NSUTF8StringEncoding]];
//3. 初始化一些數(shù)據(jù)
_fileTotalLen = self.fileData.length;
NSLog(@"[TFTPServer] 文件長度: %lu",(unsigned long)_fileTotalLen);
if (_fileTotalLen == 0) {
char send_buffer[512];
NSUInteger len = [TFTPServerPacket makeErrorDataWithCode:1000
reason:"Request file name error"
sendBuffer:send_buffer];
sendto(_sockfd, send_buffer, len, 0, (struct sockaddr*)&addr_clict, addr_clict.sin_len);
[self throwErrorWithCode:errno reason:@"Request file name error"];
return;
}
//一切準(zhǔn)備就緒,開始傳輸數(shù)據(jù) ->④
[self beganToTransportData];
return;
}
④開始向客戶端地址傳送數(shù)據(jù),發(fā)送第一個數(shù)據(jù)包,記錄下發(fā)送文件的長度并且判斷是否是最后一個數(shù)據(jù)包(數(shù)據(jù)包拼接部分很簡單,按照上面的協(xié)議來拼接數(shù)據(jù),這里就不貼代碼了,具體的自己可以下載下面的demo看),并且設(shè)置套接字接收數(shù)據(jù)超時時間為6s,防止中間傳輸失敗重傳->進(jìn)入步驟⑤,循環(huán)監(jiān)聽客戶端數(shù)據(jù)返回
//1. 局部變量的聲明
char recv_buffer[1024]; //接收數(shù)據(jù)緩沖區(qū)
char send_buffer[1024]; //發(fā)送數(shù)據(jù)緩沖區(qū)
NSUInteger sendLen = 0; //發(fā)送數(shù)據(jù)的長度
//2. 初始化一些數(shù)據(jù)
_blocknum = 1;
_alreadySendLen = 0;
int retry = 0; //同一個包重傳次數(shù)
BOOL isLastPacket = false; //記錄是否是最后一個數(shù)據(jù)包
//3. 第一個數(shù)據(jù)包的發(fā)送
sendLen = [TFTPServerPacket makeDataWithTotalData:self.fileData
sendBuffer:send_buffer
location:_alreadySendLen
length:TFTP_BlockSize
blocknum:_blocknum];
if (sendLen < (TFTP_BlockSize + 4)) isLastPacket = YES; //記錄下是發(fā)送的最后一個數(shù)據(jù)包
if (send(_sockfd, send_buffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Send data error"];
return;
}
_alreadySendLen = sendLen - 4;
//開始傳輸數(shù)據(jù)時,定個數(shù)據(jù)包接收超時時間段為6s
struct timeval timeout = {6,0};
if (setsockopt(_sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) {
printf("設(shè)置Socket通信過程中,接收客戶端數(shù)據(jù)超時失?。?s\n",strerror(errno));
}
//開始下面的監(jiān)聽ACK返回,發(fā)送接下來的數(shù)據(jù)包 ->⑤
⑤循環(huán)持續(xù)讀取輸入緩沖中的數(shù)據(jù),如果讀取超時則重發(fā)上次的數(shù)據(jù)包,連續(xù)三次超時則拋出錯誤關(guān)閉套接字,若收到客戶端的數(shù)據(jù) -> 進(jìn)入步驟⑥,解析數(shù)據(jù)包
//4. while循環(huán)監(jiān)聽數(shù)據(jù)包的返回
while (1) {
if (_isOpen == NO) return; //服務(wù)器關(guān)閉直接退出監(jiān)聽
ssize_t result_recv = recv(_sockfd, recv_buffer, sizeof(recv_buffer), 0);
if (result_recv < 0 && _isOpen) {
if (errno == EAGAIN) { //接收超時重傳
retry ++;
if (retry >= MAX_RETRY) {
NSLog(@"[TFTPServer] 接收ACK超時,發(fā)送差錯包給客戶端");
sendLen = [TFTPServerPacket makeErrorDataWithCode:1001
reason:"The maximum number of retransmissions"
sendBuffer:send_buffer];
send(_sockfd, send_buffer, sendLen, 0);
[self throwErrorWithCode:1001 reason:@"The maximum number of retransmissions"];
return;
}else {
NSLog(@"[TFTPServer] 接收客戶端確認(rèn)包超時 -> 重傳上次的包(塊號:%u)",_blocknum);
if (send(_sockfd, send_buffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Send data error"];
return;
}
continue;
}
}else {
[self throwErrorWithCode:errno reason:@"Read data error"];
return;
}
}
//數(shù)據(jù)包長度小于4不要
if (result_recv < 4) continue;
//接下來解析客戶端發(fā)送回來的數(shù)據(jù)和對應(yīng)數(shù)據(jù)發(fā)送還有服務(wù)器端操作 ->⑥
}
⑥解析客戶端發(fā)送回來的數(shù)據(jù),首先確認(rèn)操作碼(是否是ACK數(shù)據(jù)包,此時ACK才是我們需要的,錯誤包做下解析和對應(yīng)的操作,其他的數(shù)據(jù)包可以忽略),如果是ACK數(shù)據(jù)包 ->進(jìn)行步驟⑦,解析ACK數(shù)據(jù)包并判斷
//先解析操作碼
char opCode = recv_buffer[1];
if (opCode == TFTP_RRQ || opCode == TFTP_WRQ || opCode == TFTP_DATA) {
NSLog(@"[TFTPServer] 客戶端發(fā)錯了數(shù)據(jù)包(操作碼: %d), 不理",opCode);
}else if (opCode == TFTP_ACK) { //收到ACK數(shù)據(jù)包
//收到設(shè)備端的ACK確認(rèn)包->進(jìn)行ACK塊號確認(rèn),發(fā)送對應(yīng)數(shù)據(jù)包 ->⑦
}else if (opCode == TFTP_ERROR) {
//客戶端那邊發(fā)送過來了錯誤包
NSString *errStr = [[NSString alloc] initWithBytes:&recv_buffer[4] length:result_recv-4 encoding:NSUTF8StringEncoding];
NSLog(@"[TFTPServer] 客戶端傳送過來差錯信息: 錯誤碼 -> %u 錯誤信息 -> %@",(((recv_buffer[2] & 0xff) << 8) | (recv_buffer[3] & 0xff)),errStr);
[self throwErrorWithCode:(((recv_buffer[2] & 0xff) << 8) | (recv_buffer[3] & 0xff)) reason:errStr];
return;
}else {
NSLog(@"[TFTPServer] 客戶端發(fā)錯了數(shù)據(jù)包(操作碼: %d), 不理",opCode);
}
⑦收到客戶端的ACK確認(rèn)包,首先判斷是否是最后一個數(shù)據(jù)包的確認(rèn)塊號,如果是,則關(guān)閉服務(wù)器(斷掉Socket),本次傳輸完成,如果不是則進(jìn)行步驟⑧,解析塊號判斷,并做對應(yīng)的操作
//①. 解析出確認(rèn)塊號
uint clict_sureblocknum = ((recv_buffer[2]&0xff)<<8)|((recv_buffer[3]&0xff));
//NSLog(@"[TFTPServer] 收到客戶端的ACK數(shù)據(jù)包,塊號:%d",clict_sureblocknum);
//②. 判斷是否是最后一個包的確認(rèn)
if (isLastPacket == YES && _blocknum == clict_sureblocknum) { //是最后一個包了
[self sendComplete];
return;
}else {
//不是最后一個數(shù)據(jù)包的確認(rèn)塊號,解析出塊號并判斷 ->⑧
}
⑧解析出塊號,與自己已經(jīng)發(fā)送的塊號做對比,如果是剛發(fā)的塊號,則確認(rèn)進(jìn)行下一個包的發(fā)送,如果塊號是上一個數(shù)據(jù)包的塊號,則進(jìn)入步驟⑨(數(shù)據(jù)包重發(fā)),否則則為塊號錯亂,退出重新發(fā)送
if (_blocknum == clict_sureblocknum) {
_blocknum ++;
retry = 0;
sendLen = [TFTPServerPacket makeDataWithTotalData:self.fileData
sendBuffer:send_buffer
location:_alreadySendLen
length:TFTP_BlockSize
blocknum:_blocknum];
if (sendLen < (TFTP_BlockSize + 4)) isLastPacket = YES; //記錄下是發(fā)送的最后一個數(shù)據(jù)包
if (send(_sockfd, send_buffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Send data error"];
return;
}
_alreadySendLen += (sendLen - 4);
}else if (clict_sureblocknum == (_blocknum - 1)) {
//ACK塊號不對,進(jìn)入重發(fā)機(jī)制
//上一個數(shù)據(jù)包客戶端接收有誤, 重傳 ->⑨
}else {
NSLog(@"[TFTPServer] 客戶端返回的確認(rèn)塊號不對 _blocknum:%u clict_sureblocknum:%u",_blocknum,clict_sureblocknum);
[self throwErrorWithCode:1002 reason:@"Request block number error"];
return;
}
⑨如果收到的是上次發(fā)送包的確認(rèn)塊號(數(shù)據(jù)丟失可能設(shè)備沒有收到),則判斷對上個包的重發(fā)次數(shù)有沒有達(dá)到上限,如果達(dá)到上限,則向客戶端發(fā)送一個差錯包,告訴客戶端此次傳輸有問題,服務(wù)器要斷開連接了,如果沒有達(dá)到上限則將上次發(fā)送的數(shù)據(jù)包重新發(fā)送
retry ++;
if (retry >= MAX_RETRY) {
NSLog(@"[TFTPServer] 接收ACK錯誤次數(shù)達(dá)到上限,發(fā)送差錯包給客戶端");
sendLen = [TFTPServerPacket makeErrorDataWithCode:1001
reason:"The maximum number of retransmissions"
sendBuffer:send_buffer];
send(_sockfd, send_buffer, sendLen, 0);
[self throwErrorWithCode:1001 reason:@"The maximum number of retransmissions"];
return;
}else {
NSLog(@"[TFTPServer] 客戶端發(fā)送ACK塊號有誤(塊號:%u), 重傳上次的包(塊號:%u)",_blocknum,clict_sureblocknum);
if (send(_sockfd, send_buffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Send data error"];
return;
}
}
(2)客戶端
①創(chuàng)建Socket套接字,綁定固定端口并檢查套接字的讀寫情況,用來接收服務(wù)器數(shù)據(jù)返回
//初始化套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd <= 0) {
[self throwErrorWithCode:errno reason:@"Failed to create socket"];
return ;
}
struct sockaddr_in addr_bind;
addr_bind.sin_len = sizeof(struct sockaddr_in);
addr_bind.sin_family = AF_INET;
addr_bind.sin_port = htons(port);
addr_bind.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(_sockfd, (struct sockaddr*)&addr_bind, addr_bind.sin_len) < 0) {
[self throwErrorWithCode:errno reason:@"Binding socket failed"];
return;
}
②初始化服務(wù)器地址信息,并注冊服務(wù)器地址信息,方便后面直接接收數(shù)據(jù)和發(fā)送數(shù)據(jù),并且設(shè)置接讀取輸入緩沖超時時間為6s,防止傳輸過程中出現(xiàn)異常
//注冊套接字目的地址
struct sockaddr_in addr_server;
addr_server.sin_len = sizeof(struct sockaddr_in);
addr_server.sin_family = AF_INET;
addr_server.sin_port = htons(port);
inet_pton(AF_INET, host.UTF8String, &addr_server.sin_addr);
if (connect(_sockfd, (struct sockaddr*)&addr_server, addr_server.sin_len) < 0) {
[self throwErrorWithCode:errno reason:@"Registration destination address failed"];
return;
}
//設(shè)置讀取數(shù)據(jù)超時
struct timeval timeout = {6, 0};
if (setsockopt(_sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) {
printf("[TFTPClient] 設(shè)置接收數(shù)據(jù)超時失?。?s",strerror(errno));
}
③開始向服務(wù)器發(fā)送第一個數(shù)據(jù)包,文件請求數(shù)據(jù)包
//1. 初始化一些變量
char sendBuffer[1024]; //發(fā)送數(shù)據(jù)緩存區(qū)
NSUInteger sendLen; //發(fā)送數(shù)據(jù)長度
char recvBuffer[1024]; //接收數(shù)據(jù)緩存區(qū)
_blocknum = 0; //接收塊號記錄
self.fileData.length = 0; //接收文件緩存區(qū)
int retry = 0; //記錄同一個包的請求次數(shù)
BOOL isLastPacket = false; //記錄是否是最后一個數(shù)據(jù)包
//2. 發(fā)送文件請求包
sendLen = [TFTPClientPacket makeRRQWithFileName:filename
sendBuffer:sendBuffer];
if (send(_sockfd, sendBuffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Read data error"];
return;
}
//開始監(jiān)聽服務(wù)器數(shù)據(jù)發(fā)送了 -> ④
④循環(huán)監(jiān)聽服務(wù)器發(fā)送數(shù)據(jù),首先判斷是否讀取超時,如果超時則重發(fā)上一次的確認(rèn)塊,連續(xù)三次超時則拋出錯誤關(guān)閉套接字。如果收到數(shù)據(jù)則判斷數(shù)據(jù)長度是否滿足自己的需求,都滿足則進(jìn)行接下來的解析->步驟⑤,不滿足跳過本地循環(huán)讀取,進(jìn)行下次讀取
//3. 開始監(jiān)聽數(shù)據(jù)返回
while (1) {
ssize_t result_recv = recv(_sockfd, recvBuffer, sizeof(recvBuffer), 0);
if (result_recv < 0 && _isOpen) {
if (errno == EAGAIN) { //讀取數(shù)據(jù)超時
retry++;
if (retry >= MAX_RETRY) {
NSLog(@"[TFTPClient] 請求超時,發(fā)送差錯包給服務(wù)器");
sendLen = [TFTPClientPacket makeErrorDataWithCode:1001
reason:"The maximum number of retransmissions"
sendBuffer:sendBuffer];
send(_sockfd, sendBuffer, sendLen, 0);
[self throwErrorWithCode:1001 reason:@"The maximum number of retransmissions"];
return;
}else {
//重發(fā)上一個ACK確認(rèn)包
NSLog(@"[TFTPClient] 客戶端請求數(shù)據(jù)塊超時,重發(fā)上個ACK(塊號:%u)",_blocknum);
if (send(_sockfd, sendBuffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Send data error"];
return;
}
continue;
}
}else {
[self throwErrorWithCode:errno reason:@"Read data error"];
return;
}
}
//數(shù)據(jù)長度過短或不是我們需要服務(wù)器地址發(fā)送過來的數(shù)據(jù)都不是我們想要的數(shù)據(jù), 直接丟掉
if (result_recv < 4) continue;
//是自己需要的數(shù)據(jù),解析出操作碼,進(jìn)行相應(yīng)的操作 ->⑤
}
⑤首先解析出接收到數(shù)據(jù)包的操作碼,判斷是否是DATA對應(yīng)的操作碼,如果不是拋出對應(yīng)的信息和進(jìn)行對應(yīng)的操作,如果是則進(jìn)行接下來的操作->步驟⑥,塊號的確認(rèn)
//解析操作碼
char opCode = recvBuffer[1];
if (opCode == TFTP_RRQ || opCode == TFTP_WRQ || opCode == TFTP_ACK) {
NSLog(@"[TFTPClient] 服務(wù)器發(fā)送了錯誤數(shù)據(jù)包(操作碼: %d),不理",opCode);
}else if (opCode == TFTP_DATA) {
/* 服務(wù)器發(fā)送過來數(shù)據(jù)包 */
//進(jìn)行接下來的數(shù)據(jù)解析和拼接,發(fā)送給確認(rèn)包 ->⑥
}else if (opCode == TFTP_ERROR) {
NSString *errStr = [[NSString alloc] initWithBytes:&recvBuffer[4] length:result_recv-4 encoding:NSUTF8StringEncoding];
NSLog(@"[TFTPClient] 服務(wù)器傳送過來差錯信息: 錯誤碼 -> %u 錯誤信息 -> %@",(((recvBuffer[2] & 0xff) << 8) | (recvBuffer[3] & 0xff)),errStr);
[self throwErrorWithCode:(((recvBuffer[2] & 0xff) << 8) | (recvBuffer[3] & 0xff)) reason:errStr];
return;
}else {
NSLog(@"[TFTPClient] 服務(wù)器傳過來不知名的數(shù)據(jù)包(操作碼: %d)",opCode);
}
⑥解析服務(wù)器發(fā)送過來的數(shù)據(jù)包,拿到塊號和自己這邊記錄的塊號做對比,如果塊號正確則向服務(wù)器發(fā)送本次的塊號確認(rèn)ACK包,并把數(shù)據(jù)拼接到緩存,如果收到數(shù)據(jù)包塊號不正確,則進(jìn)行步驟⑦重確認(rèn)操作
//解析出塊號, 與自己的塊號作比較, 看看服務(wù)器有沒有發(fā)錯
uint blocknum = (recvBuffer[2]&0xff)<<8 | (recvBuffer[3]&0xff);
//NSLog(@"[TFTPClient] 服務(wù)器發(fā)送過來數(shù)據(jù)包,塊號:%u",blocknum);
if (blocknum == (_blocknum + 1)) {
retry = 0;
_blocknum = blocknum;
//解析數(shù)據(jù)包, 并且判斷是否是最后一個數(shù)據(jù)包
NSData *data = [NSData dataWithBytes:&recvBuffer[4] length:result_recv-4];
[self.fileData appendData:data];
if (data.length < TFTP_BlockSize) isLastPacket = YES;
//發(fā)送ACK確認(rèn)包
sendLen = [TFTPClientPacket makeACKWithBlockNum:_blocknum sendBuffer:sendBuffer];
if (send(_sockfd, sendBuffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Send data error"];
return;
}
}else {
//塊號不對,進(jìn)入重發(fā)機(jī)制
//傳送數(shù)據(jù)塊號不對,進(jìn)行重新確認(rèn) -> ⑦
}
⑦服務(wù)器發(fā)送的數(shù)據(jù)塊號和自己將要接收的塊號對不上,拿到自己最后一個接收到的數(shù)據(jù)包塊號,先判斷對該數(shù)據(jù)包的確認(rèn)次數(shù)有沒有達(dá)到上限,達(dá)到上限則向服務(wù)器發(fā)送差錯包,表示本次傳輸出錯,并關(guān)掉套接字,如果沒有達(dá)到上限,那么重發(fā)這個確認(rèn)塊號,向服務(wù)器確認(rèn)得到正確的數(shù)據(jù)塊
retry++;
if (retry >= MAX_RETRY) {
NSLog(@"[TFTPClient] 接收數(shù)據(jù)包錯誤次數(shù)達(dá)到上限,發(fā)送差錯包給客戶端");
sendLen = [TFTPClientPacket makeErrorDataWithCode:1001
reason:"The maximum number of retransmissions"
sendBuffer:sendBuffer];
send(_sockfd, sendBuffer, sendLen, 0);
[self throwErrorWithCode:1001 reason:@"The maximum number of retransmissions"];
return;
}else {
NSLog(@"[TFTPClient] 服務(wù)器發(fā)送塊號不對(塊號:%u), 重發(fā)送上個ACK確認(rèn)包(塊號:%u)",blocknum,_blocknum);
if (send(_sockfd, sendBuffer, sendLen, 0) < 0 && _isOpen) {
[self throwErrorWithCode:errno reason:@"Send data error"];
return;
}
}
⑧確認(rèn)包發(fā)送完或者確認(rèn)包重發(fā)完,判斷是否是最后一個數(shù)據(jù)包的確認(rèn),如果是則關(guān)閉Socket,本次傳輸完成,不是則跳過接著監(jiān)聽
if (isLastPacket) { //就收完成
[self recevComplete];
return;
}
5.結(jié)語
上面的代碼部分只是一個大概的思路講解,主要為了方便理解demo里面的代碼邏輯,具體的還是要看demo。