Http服務(wù)器實現(xiàn)文件上傳與下載(一)
一、引言
大家都知道web編程的協(xié)議就是http協(xié)議,稱為超文本傳輸協(xié)議。在J2EE中我們可以很快的實現(xiàn)一個Web工程,但在C++中就不是非常的迅速,原因無非就是底層的socket網(wǎng)絡(luò)編寫需要自己完成,上層的http協(xié)議需要我們自己完成,用戶接口需要我們自己完成,如何高效和設(shè)計一個框架都是非常困難的一件事情。但這些事情Java已經(jīng)在底層為我們封裝好了,而我們僅僅只是在做業(yè)務(wù)層上的事情吧了。
在本Http服務(wù)器實現(xiàn)中,利用C++庫和socket原套接字編程和pthread線程編寫。拒絕使用第三方庫。因為主要是讓大家知道基本的實現(xiàn)方式,除去一些安全、高效等特性,但是不管怎么樣,第三方商業(yè)庫的基本原理還是一致的,只是他們對其進行了優(yōu)化而已。在開始的編寫時,我不會全部的簡介Http的協(xié)議的內(nèi)容,這樣太枯燥了,我僅僅解釋一些下面需要用到的協(xié)議字段。
在寫本文的時候,之前也有些迷惑,C++到底能干啥,到網(wǎng)上一搜,無非就是能開發(fā)游戲,嵌入式編程,寫服務(wù)器等等。接著如果問如何編寫一個服務(wù)器的話,那么這些網(wǎng)絡(luò)水人又會告訴你,你先把基礎(chǔ)學(xué)好,看看什么書,之后你就知道了,我只能呵呵了,在無目的的學(xué)習(xí)中,盡管看了你也不知道如何寫的,因為盡管你知道一些大概,但是沒有一個人領(lǐng)導(dǎo)你入門,我們還是無法編寫一個我們自己想要的東西,我寫這篇博客主要是做一個小小的敲門磚吧,盡管網(wǎng)上有許多博客,關(guān)于如何編寫HTTP服務(wù)器的,但是要不是第三方庫acl,要么就是短短的幾行代碼,要么就是加入了微軟的一些C#內(nèi)容或者MFC,這些在我看來只是一些無關(guān)緊要的東西,加入后或許界面上你很舒服,但是大大增加了我們的學(xué)習(xí)成本,因為這些界面上的代碼改變了我們所知道的程序流程走向,還有一些界面代碼和核心代碼的混合,非常不利于學(xué)習(xí)。
二、HTTP協(xié)議
在大家在瀏覽器的url輸入欄上輸入http://10.1.18.4/doing時。瀏覽器向10.1.18.4服務(wù)器80端口的進程發(fā)送了如下的一個協(xié)議頭,它是一個文本字符串。每行以\r\n結(jié)束。表示回車換行。
1 GET /doing HTTP/1.1 2 Host: 10.1.18.4 3 User-Agent: Mozilla/5.0 (Windows NT 6.2; rv:40.0) Gecko/20100101 Firefox/40.0 4 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 5 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 6 Accept-Encoding: gzip, deflate 7 Referer: http://10.1.18.4/ 8 Connection: keep-alive
所以知道其實我們發(fā)送了一個URL請求,其實被轉(zhuǎn)化為了一個如上的一些字符串。在這里我簡單的解釋一下這個協(xié)議頭表示什么,因為在網(wǎng)上你可以找到非常多的信息來解釋它們。
1)第一行中 GET /doing HTTP1.1 表示請求的方式是GET,URL是/doing ,HTTP協(xié)議的版本是1.1
2)第二行中 Host 就是服務(wù)器的IP
3)第三行中 User-Agent代表著你使用的是什么瀏覽器在什么系統(tǒng)上運行的。從上本可以這條信息顯示是window上火狐瀏覽器發(fā)出的請求頭
4)第四行中Accept代表著該瀏覽器可以接受的信息格式,可以是文本,html,或者應(yīng)用文件(二進制文件)。其中q代表權(quán)重,表示更愿意接受前面的信息。還有一些其他的內(nèi)容,讀者可以自己百度。
5)以下的一些信息中,沒有什么用到,我就不解釋,看文本意義也大概知道一些信息。詳細的請搜索網(wǎng)絡(luò)。
在最重要的是一本請求頭什么時候表示結(jié)束呢,那就是一個空行表示結(jié)束。其實就是"\r\n"結(jié)束。
說了這么多可能大家還是有點迷糊,知道這些那么在程序中又是怎么實現(xiàn)的呢。當(dāng)初我也迷惑,現(xiàn)在我提出一個最簡單的一種實現(xiàn),就是直接連接一個字符串即可。在實際實現(xiàn)中我對其進行了分解,但是現(xiàn)在,我解釋為如下編寫程序:
1 char *str= "GET /doing HTTP/1.1\r\n\ 2 Host: 10.1.18.4\r\n\ 3 User-Agent: Mozilla/5.0 (Windows NT 6.2; rv:40.0) Gecko/20100101 Firefox/40.0\r\n\ 4 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*//*;q=0.8\r\n\ 5 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\r\n\ 6 Accept-Encoding: gzip, deflate\r\n\ 7 Referer: http://10.1.18.4/\r\n\ 8 Connection: keep-alive\r\n\ 9 Range: bytes=14584264-\r\n\r\n" ;
可能上面的協(xié)議內(nèi)容跟之前的有點不一樣,沒關(guān)系,我只是截取了一些內(nèi)容進行輸入。很簡單就是C語言的char*字符串。在沒一行的的結(jié)尾都都有一個'\',表示表示換行輸入,去掉也行,需要把器內(nèi)容寫到一行上,是C語言語法,不懂的讀者可以自己查閱C語言的字符串。我想說的是在每行的結(jié)尾都有一個\r\n。這兩個轉(zhuǎn)義字符就是代表回車換行。并且在第9行有2個\r\n,最后一個代表著空行,意思是說告訴服務(wù)器我的協(xié)議頭到此位置。
為什么需要一個空行呢,這里就有一個網(wǎng)絡(luò)編程的小小信息。在socket TCP流編程中,比如你調(diào)用了write或read函數(shù),內(nèi)部不是一次性接受或者發(fā)送所有的信息。所以當(dāng)我們發(fā)送上述的str的時候,不一定一次全部的發(fā)送,那么服務(wù)端就不知道什么時候結(jié)尾了。所以我們需要HTTP規(guī)定以空行作為結(jié)尾代表著協(xié)議頭的結(jié)束。
接下面了來就是我們編寫的服務(wù)器接受到這個字符串。并且以空行表示接受到整個協(xié)議頭,然后對其進行解析。下面就是解析這段字符串的代碼,在工程中我對其封裝,但是現(xiàn)在我們只要知道實現(xiàn)解析功能即可。
1 #include2 #include 3 #include 4 #include 5 #include 6 using namespace std; 7 8 char *str= "GET /download/JBPM4S.tt HTTP/1.1\r\n\ 9 Host: 10.1.18.4\r\n\ 10 User-Agent: Mozilla/5.0 (Windows NT 6.2; rv:40.0) Gecko/20100101 Firefox/40.0\r\n\ 11 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*//*;q=0.8\r\n\ 12 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\r\n\ 13 Accept-Encoding: gzip, deflate\r\n\ 14 Referer: http:http://10.1.18.4/\r\n\ 15 Connection: keep-alive\r\n\ 16 Range: bytes=14584264-\r\n\r\n" ; 17 18 string& ltrim(string &str) { 19 string::iterator p = find_if(str.begin(), str.end(), not1(ptr_fun 20 str.erase(str.begin(), p); 21 return str; 22 } 23 24 string& rtrim(string &str) { 25 string::reverse_iterator p = find_if(str.rbegin(), str.rend(), not1(ptr_fun (isspace))); 26 str.erase(p.base(), str.end()); 27 return str; 28 } 29 30 string& trim(string &str) { 31 ltrim(rtrim(str)); 32 return str; 33 } 34 string getContent(string& str,int start,char c,int &pos){ 35 int i=start; 36 int len=str.size(); 37 while(i 38 i++; 39 } 40 pos=i; 41 return str.substr(start,i-start); 42 } 43 map 44 int len=strlen(str); 45 vectorvs; 46 int i=0; 47 while(i 48 if(str[i]!='\r'){ 49 int j=i; 50 while(i 51 i++; 52 vs.push_back(string(str+j,str+i)); 53 }else{ 54 i+=2; 55 } 56 } 57 int pos; 58 string method=getContent(vs[0],0,' ',pos); 59 string url=getContent(vs[0],method.size()+1,' ',pos); 60 map 61 mp["Method"]=method; 62 mp["Url"]=url; 63 for(int i=1;i 64 string key=getContent(vs[i],0,':',pos); 65 string value=vs[i].substr(pos+1); 66 mp[key]=trim(value); 67 } 68 return mp; 69 } 70 71 int main(int argc, char **argv) 72 { 73 map 74 for(map 75 cout< 76 } 77 return 0; 78 }
把一些信息解析都放到了一個map里面。這里的解析是先處理每一行,然后再對每一行進行解析??赡苓@樣的處理方式有點慢,但是沒什么關(guān)系,原因是字符串反正比較短,在大并發(fā)下效率不會影響太大,如果大家有什么更好的解析方式,可以回復(fù)我。
在服務(wù)端解析頭信息后,我們可以得到/doing這個url,這樣我們服務(wù)請就可以把客戶端需要的內(nèi)容返回給客戶端了,這里就有瀏覽器請求的內(nèi)容是否合法是否存在這些信息。就要在相應(yīng)的響應(yīng)頭中說明,在《Http服務(wù)器實現(xiàn)文件上傳與下載(二)》中會進行說明。
歡迎大家一起探討這些問題。有什么想法的人給我回復(fù),我們一起學(xué)習(xí),一起進步哦。