博文原創,轉載請聯繫博主!linux
使用C語言也有兩個年頭了,BUG寫出來過很多,也改過很多BUG。可是恰恰就是有這麼一個BUG讓我手頭的項目停工了兩天,緣由從百度找到谷歌,資料從MAN手冊找到RFC也沒有找到問題的緣由,可是真正發現BUG緣由以後實在是讓本身汗顏。git
無論如何,決定把這個BUG寫進博文,也是給學習C語言的朋友們提個醒,查看BUG的眼光不要過高,思考問題要自底向上思考。github
具體項目在個人github裏: https://github.com/yue9944882/HttpAccelerater編程
正文:vim
問題大體發現是這樣的,在這個HTTP下載器中,實質上編程邏輯都是在傳輸層TCP套接字上完成的,具體細節就很少提,在通信過程當中是經過一對send和recv函數完成的,recv函數原型以下所示:(linux環境<sys/socket.h>)服務器
int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);socket
sockfd: 接收端套接字描述符函數
buff: 用來存放recv函數接收到的數據的緩衝區學習
nbytes: 指明buff的長度測試
flags: 通常置爲0
返回值: recv函數返回其實際copy的字節數
flags | 說明 | recv | send |
MSG_DONTROUTE | 繞過路由表查找 | • | |
MSG_DONTWAIT | 僅本操做非阻塞 | • | • |
MSG_OOB | 發送或接收帶外數據 | • | • |
MSG_PEEK | 窺看外來消息 | • | |
MSG_WAITALL | 等待全部數據 | • |
問題就是出在recv函數的返回值這裏,由於和HTTP服務器的通信過程當中,服務器端所返回的內容不只僅是包括一個所請求的文件數據,還有http的報頭,並且在recv獲得的數據中HTTP首部和實體是混合在一塊兒的,因此就須要咱們用\r\n\r\n四個字符做爲標誌來檢測HTTP首部的結束,並且又由於recv獲得的數據並非完整地填充進接收數據的緩衝區中的,因此咱們計算接收到的文件的第一段數據的偏移是這樣的:
[ HTTP首部結束的偏移,實際讀進緩衝區的偏移 ]
由於HTTP首部長度遠遠小於緩衝區長度就忽略首部填滿緩衝區的狀況。
recv函數是獲得實際讀進緩衝區偏移的關鍵,但是實際調試過程當中每次recv返回的值都是0!
但是緩衝區裏面卻讀進了請求的數據,裏面也有完整的HTTP首部,這到底是爲何呢?
那麼咱們查看一下recv函數返回0的具體緣由:
recv()
returns 0 only when you request a 0-byte buffer or the other peer has gracefully disconnected.
首先咱們的緩衝區確確實實寫進了數據,就談不上0-byte buffer,另外一種狀況就是TCP鏈接的正常關閉,即服務器端發送FIN包,可是以下所示,咱們的代碼中有後續的while循環仍然接受到了服務器端傳送的數據,代碼以下所示:
while(curPos<gURLinfo.llContentLen){ dr=recv(sockdesc,recvBuf,4096,0); if(dr+curPos>gURLinfo.llContentLen){ dw=pwrite(file,recvBuf,gURLinfo.llContentLen-headlength-curPos,curPos); curPos+=dw; //printf("offset:\t%d\ndw:\t%d\n",curPos,dw); break; }else{ dw=pwrite(file,recvBuf,dr,curPos); curPos+=dw; } //printf("offset:\t%d\ndw:\t%d\n",curPos,dw); }
在這裏recv返回的dr值居然是正常的非零正值--從內核讀取進緩衝區的字節數! 那麼咱們天然就會開始認爲是recv函數的第一次使用纔會返回0,以後的使用不會再出現問題。因而我就用了一個「弄巧成拙的辦法」:首先使用bzero函數將緩衝區填充滿0,再在緩衝區中尋找以‘\0’爲結束標誌進行掃描,掃描結束的時候獲得緩衝區內實際字節的長度。可是實際測試的時候發現,這個辦法用於下載純粹的txt格式的文件是沒有問題的,然而當下載二進制文件例如圖片,壓縮後文件的時候,就會出現問題!ps:這樣下載下來的圖片居然仍是偏紅的,害得我去圖片編碼區RBG值尋找BUG真相,浪費了不少時間。
既然老是返回0,咱們來查看一下是不是有錯誤發生吧,因而加入了<errno.h>,結果輸出出來了errno仍是0,也就是無錯誤發生!
最終這個問題的解決過程是這樣的:
1.使用wget下載完整的圖片。
2.使用 vim -b 圖片 和正常的圖片進行對比(:%!xxd 查看),發現和正常圖片不一樣之處,在於一些空白0字段的填充
3.進而發現仍是第一次recv函數致使的文件內容不對
4.最終問題鎖定在了這樣一段代碼,也是讓我最汗顏的:
if(dr=recv(sockdesc,recvBuf,4096,0)==-1){
fprintf(stderr,"Header Recving Failure!\n");
exit(-1);
}
這是recv函數第一次接收讀取字節數的代碼,也就是這段代碼致使了recv讀取字節數的不可知,返回值永遠爲0。相信C語言的老手已經看出來了,這段代碼的問題:
關係運算符優先級大於賦值運算符!!!
真正的正確的代碼應該是這樣寫的:
if((dr=recv(sockdesc,recvBuf,4096,0))==-1){
fprintf(stderr,"Header Recving Failure!\n");
exit(-1);
}
C語言寫多了,有些代碼會越寫越簡練,好比聲明和運算混寫,函數參數局部全局混寫,可是對於關係運算符的優先級是最不能忽略的,不管是哪一個語言,哪怕是運算符關係符最混亂的perl,也要緊緊記住每一個優先級和結合性!