網(wǎng)絡(luò)應(yīng)用集成了我們已經(jīng)學(xué)到的很多概念:進(jìn)程板壮、信號(hào)、字節(jié)順序合住、存儲(chǔ)器映射绰精、動(dòng)態(tài)分配等撒璧,同時(shí)客服端-服務(wù)器模型是一個(gè)新的知識(shí),我們將所有的這些結(jié)合起來(lái)笨使,創(chuàng)建一個(gè)微小的Web服務(wù)器卿樱,提供瀏覽器靜態(tài)和動(dòng)態(tài)的訪問(wèn)。
1.1 客戶(hù)端--服務(wù)器模型
每一個(gè)網(wǎng)絡(luò)應(yīng)用都是基于一個(gè)客戶(hù)端進(jìn)程和一個(gè)服務(wù)器進(jìn)程建立起來(lái)的硫椰,服務(wù)器管理資源繁调,客服端請(qǐng)求某種服務(wù),客戶(hù)端和服務(wù)器都是一個(gè)運(yùn)行中的程序最爬。典型的示意圖如下:
我們以一個(gè)郵件客服端訪問(wèn)文件為例:
① 客戶(hù)端發(fā)送一個(gè)瀏覽文件的請(qǐng)求給Web服務(wù)器涉馁;
② 服務(wù)器接收請(qǐng)求以后门岔,解釋它爱致,從資源中取出相應(yīng)的文件;
③ 服務(wù)器將文件發(fā)送到請(qǐng)求的客戶(hù)端寒随;
④ 客戶(hù)端接收并顯示在屏幕上糠悯。
1.2 網(wǎng)絡(luò)
網(wǎng)絡(luò)是連接客戶(hù)端與服務(wù)器之間的通路,對(duì)于單個(gè)的主機(jī)而言妻往,網(wǎng)絡(luò)只是插在IO總線上的一種互艾,從網(wǎng)絡(luò)上接收的數(shù)據(jù)經(jīng)過(guò)適配器-IO總線-橋-存儲(chǔ)器總線-主存:
上圖只是一個(gè)簡(jiǎn)圖,我們來(lái)簡(jiǎn)單的討論一下具體的實(shí)現(xiàn)
在網(wǎng)絡(luò)建立的初期讯泣,是通過(guò)一個(gè)集線器纫普,實(shí)現(xiàn)無(wú)差別的所有主機(jī)之間的數(shù)據(jù)交換。同交換機(jī)不同好渠,集線器不是點(diǎn)到點(diǎn)昨稼,它的每次轉(zhuǎn)發(fā)都是針對(duì)所有的主機(jī),這就形成了一個(gè)局域網(wǎng)拳锚。后來(lái)又加入了橋假栓,實(shí)現(xiàn)局域網(wǎng)直接的互相連接,總體的架構(gòu)圖如下:
新加入的橋霍掺,可以實(shí)現(xiàn)自動(dòng)學(xué)習(xí)匾荆,哪兒主機(jī)可以通過(guò)哪個(gè)端口方便到達(dá),如果將A發(fā)送到B杆烁,當(dāng)?shù)竭_(dá)橋X的時(shí)候牙丽,橋發(fā)現(xiàn)A到B就是本局域網(wǎng)的事情,就會(huì)丟棄到這個(gè)幀兔魂。而當(dāng)A發(fā)送到C的時(shí)候烤芦,橋X只會(huì)將幀發(fā)送到橋Y再由橋Y分發(fā)到C,而節(jié)省了帶寬入热。
路由器有兩個(gè)不同的端口拍棕,WAN(廣域網(wǎng))連接更大的局域網(wǎng)晓铆,LAN(本地網(wǎng))連接本地主機(jī),結(jié)構(gòu)如下:
網(wǎng)絡(luò)上的硬件已經(jīng)全部連接好了绰播,但是我們的主機(jī)各有不同骄噪,有不同的主機(jī)、不同的系統(tǒng)各種千差萬(wàn)別蠢箩,是如何通過(guò)這個(gè)網(wǎng)絡(luò)發(fā)送數(shù)據(jù)實(shí)現(xiàn)求同存異的链蕊。
通過(guò)協(xié)議發(fā)送,我們?nèi)祟?lèi)社會(huì)也是一樣的谬泌,為了達(dá)成某種一致性滔韵,不至于以后扯皮,我們會(huì)實(shí)現(xiàn)簽訂一個(gè)協(xié)議掌实,將需要調(diào)解的問(wèn)題和責(zé)任全部列出陪蜻,而且竟可能傾其所有,在以后的操作中不會(huì)有含糊不清或者界定不明的問(wèn)題贱鼻,也就能更好的合作宴卖,計(jì)算機(jī)網(wǎng)絡(luò)也是一樣通過(guò)建立一套TCP/IP協(xié)議族,實(shí)現(xiàn)不同主機(jī)之間的數(shù)據(jù)傳輸:
下圖是客戶(hù)端A發(fā)送數(shù)據(jù)到服務(wù)端B的過(guò)程:
① 客服端將需要發(fā)送的數(shù)據(jù)拷貝到緩沖區(qū)邻悬;
② 主機(jī)A上運(yùn)行的協(xié)議軟件(TCP|IP)把實(shí)際要發(fā)送的數(shù)據(jù)加入PH(互聯(lián)網(wǎng)包頭)和FH1(LAN1幀頭)症昏,創(chuàng)建一個(gè)LAN1的幀發(fā)送到網(wǎng)絡(luò)適配器中;
③ 主機(jī)上的網(wǎng)絡(luò)適配器(連接在IO總線)收到了這個(gè)LAN1幀以后通過(guò)網(wǎng)線發(fā)送到路由器的LAN1端口父丰;
④ 收到了這個(gè)LAN1幀的時(shí)候肝谭,將其發(fā)送到協(xié)議軟件上;
⑤ 路由器的協(xié)議軟件將LAN1幀的舊的LAN1幀頭剝落蛾扇,加入到實(shí)際發(fā)送的主機(jī)LAN2幀頭攘烛,并將其發(fā)送到路由器LAN2端口上;
⑥ 路由器LAN2將數(shù)據(jù)拷貝到網(wǎng)線上屁桑;
⑦ 主機(jī)B的適配器從網(wǎng)線上收到了這個(gè)幀的時(shí)候医寿,將其傳送到協(xié)議軟件;
⑧ 協(xié)議軟件幀頭和包頭吧實(shí)際的數(shù)據(jù)拷貝出來(lái)蘑斧;
1.3 全球IP因特網(wǎng)
每臺(tái)主機(jī)都支持TCP\IP協(xié)議靖秩,并運(yùn)行著這個(gè)軟件,IP協(xié)議提供基本的命名方法和傳送機(jī)制竖瘾,我們來(lái)看看windows上運(yùn)行的這個(gè)協(xié)議:
① IP地址
IP地址是一個(gè)無(wú)符號(hào)的32位整數(shù)沟突,存儲(chǔ)在如下的結(jié)構(gòu)中:
統(tǒng)一的網(wǎng)絡(luò)字節(jié)順序是以大端的字節(jié)順序,IP地址也是使用大端法存放的捕传,如果不同的話需要使用一些函數(shù)進(jìn)行轉(zhuǎn)換:
IP地址有時(shí)候是使用點(diǎn)分的十進(jìn)制表示的如:192.168.1.1惠拭,而下面的函數(shù)可以實(shí)現(xiàn)將點(diǎn)分十進(jìn)制的IP地址轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)順序的IP地址:
② 域名
域名是一種將IP地址映射為一組人性化的字符串的機(jī)制,而域名的集合是一個(gè)層次結(jié)構(gòu):
第一層域名是一個(gè)非營(yíng)利性組織定義的,常見(jiàn)的有.com职辅、edu棒呛、gov等
第二層cmu.edu是按照先后順序獲得的,一旦獲得了cmu域携,就可以向下生成cs\ece等
每一組由字符串映射到IP地址的數(shù)據(jù)結(jié)構(gòu)如下圖:
而負(fù)責(zé)解析這種映射的稱(chēng)為DNS數(shù)據(jù)庫(kù)簇秒,保存著成千上萬(wàn)的上圖中的條目結(jié)構(gòu)。應(yīng)用程序可以通過(guò)gethostbyname和gethostbyaddr函數(shù)顯示的從DNS數(shù)據(jù)庫(kù)中檢索條目:
我們來(lái)看一個(gè)程序:
從給定的域名或者IP地址中打印出相應(yīng)的主機(jī)名秀鞭、別名和地址列表:
可以看出趋观,多個(gè)域名可以映射多個(gè)IP地址
③ 因特網(wǎng)的連接
我們通常使用的是一個(gè)套接字完成一個(gè)客服端與服務(wù)端之間的雙向連接的
每個(gè)端是由該端的地址:端口組成,其中服務(wù)端的端口通常是固定的锋边,而客服端的端口是臨時(shí)分配的皱坛,當(dāng)形成了一個(gè)套接字對(duì)的時(shí)候,就可以開(kāi)始雙向通信了豆巨。
1.4 套接字函數(shù)
當(dāng)我們進(jìn)行客戶(hù)端與服務(wù)端之間的雙向通信的時(shí)候剩辟,我們使用的是一組套接字函數(shù),來(lái)看一張總的圖:
客戶(hù)端:
① socket函數(shù):創(chuàng)建一個(gè)套接字描述符
我們通常使用clientfd = socket(AF_INET, SOCK_STREAM, 0)搀矫;來(lái)描述抹沪,其中AF_INET表示使用互聯(lián)網(wǎng),SOCK_STREAM表示使用套接字連接的一個(gè)端點(diǎn)瓤球;
② connect函數(shù):客服端建立同服務(wù)器的連接
③ 客戶(hù)端封裝socket和connect函數(shù):open_clientfd函數(shù)
我們首先創(chuàng)建了套接字的描述(行7),然后檢索DNS主機(jī)條目敏弃,并拷貝第一個(gè)IP地址到serveraddr中卦羡,發(fā)起一個(gè)connect(行16)請(qǐng)求,成功時(shí)返回clientfd給調(diào)用函數(shù)麦到。
服務(wù)端:
① bind函數(shù):高數(shù)內(nèi)核將my_addr中的服務(wù)器套接字地址和套接字描述符sockfd聯(lián)系起來(lái):
原型是:int bind(int sockfd, struct scokaddr *my_addr, int addrlen);
② listen函數(shù):被動(dòng)的監(jiān)聽(tīng)绿饵,告訴內(nèi)核被服務(wù)端使用
原型是:int listen(int sockfd, int backlog);將sockfd轉(zhuǎn)化為監(jiān)聽(tīng)套接字
③ 封裝:bind和listen成為open_listenfd函數(shù)
④ accept函數(shù):等待客戶(hù)端的連接,創(chuàng)建已連接描述符
監(jiān)聽(tīng)描述符創(chuàng)建一次存在于整個(gè)生命周期瓶颠,已連接描述符只存在于一個(gè)客戶(hù)端拟赊,可以創(chuàng)建并發(fā)服務(wù)器。
第一步:服務(wù)器調(diào)用accept粹淋,等待連接到達(dá)監(jiān)聽(tīng)描述符吸祟;
第二步:客戶(hù)端調(diào)用connect函數(shù),發(fā)送一個(gè)連接請(qǐng)求到監(jiān)聽(tīng)描述符桃移;
第三步:打開(kāi)一個(gè)已連接描述符connfd(4)通過(guò)connfd和clientfd交換數(shù)據(jù)屋匕。
實(shí)例:回聲(echo)客服端與服務(wù)端
客戶(hù)端:echoclient.c
服務(wù)端:echoserveri.c
運(yùn)行效果:
第一步:?jiǎn)?dòng)服務(wù)器2017端口
第二步:客服端開(kāi)始連接本機(jī)127.0.0.1 2017端口,并發(fā)送字符串
1.5 Web服務(wù)器相關(guān)知識(shí)
① 基礎(chǔ):
Web客戶(hù)端(瀏覽器)與服務(wù)器之間的交互是基于一個(gè)HTTP協(xié)議借杰,同常規(guī)的FTP協(xié)議不同过吻,傳輸?shù)氖浅谋緲?biāo)記語(yǔ)言(HTML)
② Web服務(wù)器發(fā)送的內(nèi)容
是一個(gè)MIME類(lèi)型的序列
以?xún)煞N不同的方式發(fā)送到瀏覽器:
1> 取一個(gè)磁盤(pán)文件,返回給瀏覽器(靜態(tài)內(nèi)容)蔗衡;
2> 取一個(gè)可執(zhí)行文件纤虽,返回給瀏覽器(動(dòng)態(tài)內(nèi)容)乳绕。
URL:通用資源定位符(Universal Resource Locator)
URL是為每個(gè)文件定位使用的,其實(shí)也就是每個(gè)文件的名字逼纸,如:
訪問(wèn):http://www.google.com:80/index.html請(qǐng)求/index.html文件靜態(tài)內(nèi)容
訪問(wèn):http://www.google.com:8000/cgi-bin/adder?15000&213請(qǐng)求可執(zhí)行文件cgi-bin/adder
注:其中1500&213是執(zhí)行文件的兩個(gè)傳入?yún)?shù)
注:后綴中的“/”不代表根目錄刷袍,請(qǐng)求的時(shí)候所有服務(wù)器默認(rèn)為主頁(yè),解釋為/index.html
③ HTTP事務(wù)
1> 請(qǐng)求
我們使用telnet 對(duì)www.aol.com 80端口進(jìn)行連接樊展,然后輸入我們的請(qǐng)求:由兩部分組成
請(qǐng)求行:GET / HTTP/1.1要求獲取/index.html文件的內(nèi)容;
請(qǐng)求頭:host:www.aol.com附加額外信息呻纹;
最后我們以一個(gè)換行符號(hào)將我們的請(qǐng)求發(fā)送給服務(wù)器。
2> 響應(yīng)
響應(yīng)由三部分組成
響應(yīng)行:HTTP/1.1 301 Moved Permanently ;
多個(gè)響應(yīng)頭和響應(yīng)主體構(gòu)成
其中狀態(tài)碼有以下幾種:
④ 服務(wù)動(dòng)態(tài)內(nèi)容
1> 瀏覽器如何將參數(shù)傳遞給服務(wù)器
使用 专缠?符號(hào)分割文件名和參數(shù)雷酪,使用&符號(hào)分割不同的參數(shù);
2> 服務(wù)器如何將參數(shù)傳遞給子進(jìn)程
如果一個(gè)服務(wù)器收到一個(gè)瀏覽器(客戶(hù)端)發(fā)送的一個(gè)類(lèi)似請(qǐng)求:
GET /cgi-bin/adder?15000&213 HTTP/1.1
我們的adder程序遵照CGI標(biāo)準(zhǔn)編寫(xiě)涝婉,這樣子進(jìn)程將傳入CGI的環(huán)境變量QUERY_STRING設(shè)置為15000&213哥力,這樣在adder程序運(yùn)行的時(shí)候就可以調(diào)用getenv(獲取環(huán)境變量)來(lái)獲得參數(shù)
3> 服務(wù)器如何將其他信息傳遞給子進(jìn)程
依照CGI標(biāo)準(zhǔn)定義的函數(shù)還可以設(shè)置環(huán)境變量,有下面這些可以使用
4> 子進(jìn)程將輸出發(fā)送到哪里
子進(jìn)程加載并運(yùn)行CGI程序以前墩弯,將CGI程序的標(biāo)準(zhǔn)輸出從定位到了客服端已關(guān)聯(lián)描述符(使用dup2函數(shù))吩跋,這樣一來(lái)任何標(biāo)準(zhǔn)輸出都會(huì)發(fā)送到客服端去了。同時(shí)子進(jìn)程還要負(fù)責(zé)生成conten-type和content-length兩個(gè)響應(yīng)頭渔工,解釋所發(fā)送的內(nèi)容锌钮,以及終止的空行。
一個(gè)標(biāo)準(zhǔn)的CGI程序adder引矩,只是簡(jiǎn)單的將傳入的參數(shù)相加梁丘,并返回給客戶(hù)端
1.6 實(shí)例:創(chuàng)建一個(gè)微小的Web服務(wù)器
我們匯總我們已經(jīng)學(xué)到的所有內(nèi)容,創(chuàng)建一個(gè)只有200多行代碼的小型Web服務(wù)器旺韭,不過(guò)麻雀雖小氛谜,東西還是滿多的,可以實(shí)現(xiàn)對(duì)靜態(tài)和動(dòng)態(tài)內(nèi)容的訪問(wèn)区端,我們直接上完整的代碼值漫。
① 主程序mian部分:
首先通過(guò)特定的端口創(chuàng)建一個(gè)監(jiān)聽(tīng)描述符,然后就進(jìn)入了無(wú)限循環(huán)中织盼,通過(guò)accept函數(shù)創(chuàng)建已連接的描述符connfd杨何,執(zhí)行doit事務(wù)。
② doti事務(wù)
③ 解析URI函數(shù):parse_uri
首先悔政,我們解析請(qǐng)求行晚吞,并且只處理GET方法,然后對(duì)URI進(jìn)行解析谋国,返回一個(gè)標(biāo)識(shí)確定到底是靜態(tài)頁(yè)面還是動(dòng)態(tài)頁(yè)面槽地,分別調(diào)用相應(yīng)的函數(shù)處理。
首先是確定到底是靜態(tài)內(nèi)容還是動(dòng)態(tài)內(nèi)容,如果是靜態(tài)內(nèi)容捌蚊,首先清除CGI參數(shù)串集畅,然后將URI轉(zhuǎn)化為一個(gè)相對(duì)路徑名,如果沒(méi)有指定缅糟,默認(rèn)的轉(zhuǎn)向到home.html頁(yè)面挺智,返回1;如果是動(dòng)態(tài)的內(nèi)容窗宦,定位到赦颇?后面,抽取相應(yīng)的參數(shù)赴涵,將URI轉(zhuǎn)化為一個(gè)Unix文件名媒怯,返回0;
④ 忽略請(qǐng)求頭:read_requesthdrs函數(shù)
只是簡(jiǎn)單的已報(bào)頭結(jié)束的地方循環(huán)讀取整個(gè)報(bào)頭髓窜,然后不做任何處理扇苞,忽略它;
⑤ 處理靜態(tài)頁(yè)面:serve_static 和get_filetype
⑥ 處理動(dòng)態(tài)內(nèi)容:serve_dynamic
⑦ 錯(cuò)誤處理函數(shù):clienterror
我們來(lái)看看運(yùn)行的效果,首先是解析動(dòng)態(tài)內(nèi)容寄纵,我們創(chuàng)建了一個(gè)主頁(yè):home.html文件鳖敷,并且在服務(wù)端啟用了
顯示動(dòng)態(tài)內(nèi)容,我們使用之前的adder程序程拭,將1243和12相加定踱,并顯示
2017年05月07日 完