【反思】一個價值兩天的BUG,不管工做仍是學習C語言的朋友都看看吧!

 

博文原創,轉載請聯繫博主!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,也要緊緊記住每一個優先級和結合性!

相關文章
相關標籤/搜索