




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、第一部分tcp事務協議第1章t/tcp概述1.1概述本窣首*夕己介紹容廣-刀艮務君需事務核念。 我彳門qu史 用udpi'勺客戶-月艮務器応用幵女臺,這是最簡單的情形。接著我 們編寫使用tcp的客戶和服務器程序,并由此考察兩臺主機間交互的tcp/ip分組。然后我 們使用t/tcp,證明利用t/tcp uj以減少分組數,并給出為利用t/tcp需要對兩端的源代碼所 做的最少改動。接下來介紹了運行書中示例程序的測試網絡,并對分別使用udp、tcp和t/tcp的客戶 服務器應用程序進行了簡單的時間耗費比較。我們考察了一些使用tcp的典型internets用程 序,看看如杲兩端都支持t/tcp,
2、將需要做哪些修改。緊接著,簡要介紹了 internet協議族屮 事務協議的發展歷史,概略敘述了現有的t/tcp實現。本書全文以及有關t/tcp的文獻中,事務一詞的含義都是指客戶向服務器發出一個請求, 然后服務器對該請求作出應答。internet中最常見的一個例子是,客戶向域名服務器(dns)發 出請求,查詢域名對應的1p地址,然后域名服務器給出響應。本書中的事務這個術語并沒有 數據庫屮的事務那樣的含義:加鎖、兩步提交、冋退,等等。1.2 udp上的客戶-服務器我們先來看一個簡單的udp海丿亠-月艮務2罟応用禾呈序門勺彳列于,jx容:廣程序測h弋石馬女口|冬1-1所示。在這個例子屮,客戶向服務器
3、發出一個請求,服知fc!她該請求1讖岳燦一個應答。udpcli.c3 main(int argc# char *argv()4 /* simple udp client /5 struct sockaddr_in serv;6 charrequestrequest. replyreply;7 intsockfd, n;8 if (argc != 2)9 err_quit(usage: udpcli <ip address of server>):10 if (sockfd = socket(pf_inetr sock_dgram, 0) < 0)11 err_sys(sock
4、et error);12 memset(&serv 0, sizeof(serv);13 serv. sin_f amily = af_inet;14 serv.sin_addr.s_addr = inet_addr (argvl);15 serv.sinport = htons(udp_serv_port);下載16 /* form request( /17 if (sendto(sockfd, requestr request, 0,18 (sa) &servr sizeof(serv) != request)19 err_sys csendto error");
5、20 if (n = recvfrom(sockfd# reply, reply, 0.21 (sa) null, (int ) null) < 0)22 err_sys("recvfrom error");23 /* process "n* bytes of reply( /24 exit(0);25 udpcli.c圖1-1 (續)本書中所有源代碼的格式都是這樣。每一非空行前面都標有行號。正文中敘述 某段源代碼時,這段源代碼的起始和結束行號標記于正文段落的左邊,如下而的正 文所示。有時這些段落前而會有一小段說明,對所描述的源代碼進行概要說明。源 代碼段開
6、頭和結尾處的水平線標明源代碼段所在的文件名。這些文件名通常都是指 我們在1.9節中將介紹的4.4版bsd-lite屮發布的文件。我們來討論這個程序的一些有關特性,但不詳細描述插口函數,因為我們假設讀者対這 些函數有-些基本的認識。關于插口函數的細節在參考書stevens 1990的第6章屮可以找到。 圖12給出了頭文件cliserv.ho1. 創建udp插廠i10-11 socket函數用于創建一個udp插口,并將一個非負的插口描述符返回給調用進程。 出錯處理函數err_sys參見參考|5 stevens 1992的附錄b.2。這個函數可以接受任意數口的 參數,但要用vsprintf函數對它們
7、格式化,然后這個函數會打印出系統調用所返回的 errno值所對應的unix出錯信息,然后終止進程。2. 填寫服務器地址12-15首先用memset函數將internet插口地址結構清零,然后填入服務器的ip地址和端口號。 為簡明起見,我們要求用戶在程序運行中通過命令行輸入一個點分十進制數形式的ip地址 (argvl )。服務器端口號(udp_serv_port)在頭文件 cliserv . h 中用 # de fine 定義,在 本章的所有程序首部中都包含了該頭文件。這樣做是為了使程序簡潔,并避免使調用 get ho st by name和gets er vby name歯數的源彳弋碼復雜化。
8、3. 構造并向服務器發送請求16-19客戶程序構造一個請求(只用一行注釋來表示),并fflsendto函數將其發出,這樣就 有一個udp數據報發往服務器。同樣是為了簡明起見,我們假設請求(request)和應答 (reply)的報文長度為固定值。實用的程序應當按照請求和應答的最大長度來分配緩存空間, 但實際的請求和應答報文長度是變化的,而且一般都比較小。4. 讀取和處理服務器的應答20-23調用reevfrom函數將使進程阻塞(即置為睡眠狀態),直至收到一個數據報。接著客 戶進程處理應答(用一行注釋來表示),然后進程終止。由于recvf rom函數屮沒有超時機制,請求報文或應答報文屮任何一個丟
9、失都將 造成該進程永久掛起。事實上,udp客戶-服務器應用的一個基木問題就是對現實卅 界中的此類錯誤缺少健壯性。在本節的末尾將對這個問題做更詳細的討論。在頭文件c 1 i se rv h中,我們將sa定義為struct sockaddr t即指向般 的插口地址結構的指針。每當有一個插口歯數需要一個指向插口地址結構的指針時, 該指針必須被置為指向一個一般性插口地址結構的指針。這是由于插口函數先于 ans1c標準出現,在80年代早期開發插口函數的吋候,void*(空類型)指針類型尚 不可用。問題是,“struct sockaddr總共有17個字符,這經常使這一行源代 碼超出屏幕(或書本頁面)的右邊
10、界,因此我們將其縮寫成為sao這個縮寫是從bsd 內核源代碼中借用過來的。圖12給出了在本章所有程序中都包含的頭文件cliserv.hocliserv.h1 / common includes and defines for udp, tcp. and t/tcp11 define request 40012 «define reply 4001314idefine udp_serv_port define tcp.serv.port #define ttcp_serv_port777788889999/* max size of request. in bytes */ / max
11、 size of reply, in bytes */ udp server's well-known port / /* tcp servers well-known port */ /* t/tcp server's well-known port */2 clients and servers */3#include<sys/types.h>4tinclude<sys/socketh>5tfinclude<netinet/inh>6include<arpa/inet.h>7include<stdio.h>8inc
12、lude<stdlib.h>9tinclude<string.h>10includevunistdh>16 / following shortens all the type casts of pointer arguments /17 #define sa struct sockaddr *18 voiderr_quit(const char );19 voiderr.sys(const char ,);20 intread_stream(intt char * int);cliseru.圖1-2木章各程序中均包含的頭文件cliserv.h圖1給岀了相應的udp
13、服務器程序。1 "includecliserv.h"udpserv.2 int3 main()4 /* simple udp server /5 struct sockaddr_in serv, cli;6 charrequestrequest)r replyreply;7 int8ockfdf n, clilen;8 if (aockfd socket(pf_inetf sock_dgram. 0) < 0)9 err_sy8 (11 socket error );10 memset(&serv, 0. sizeof(serv);11 serv. sin_f
14、 ami ly = af_inet;12 serv.sin_addr.s.addr » htonl(tnaddr一any);13 serv.sin_port htons(udp.serv.port);圖13與圖11的udp客戶程序對應的udp服務器程序 載14 if (bind(sockfdr (sa) &serv# sizeof (serv) < 0)15 err_sys("bind error*);16 for (;) 17 clilen = sizeof(cli);18 if (n = recvfrom(sockfd# request, request,
15、 0.19 (sa) &clif &clilen) < 0)20 err_sys(recvfrom error");21 /* process "n" bytes of request【and create reply( */22 if (sendto(sockfd> reply, replyf 0,23 (sa) &cli, sizeof(cli) 2 reply)24 errsys(sendto error);25 26 udpserv.i圖1-3 (續)5. 創建udp插口和綁定本機地址8-1 5 調用socket函數創建
16、一個udp插口,并在英internet插口地址結構中填入服務器的木 機地址。這里本機地址設置為通配符(inaddr_any),這意味著服務器可以從任何一個木機 接口接收數據報(假設服務器是多宿主的,即口j以有多個網絡接口)。端口號設為服務器的知名 端口 (udp_serv_port),該常量也在前面講過的頭文件cliserv.h中定義。木機ip地址和 知名端口用b i nd函數綁定到插口上。6. 處理客戶請求16-25接下來,服務器程序就進入一個無限循環:等待客戶程序的請求到達(recvfrom), 處理該請求(我們只用一行注釋來表示處理動作),然后發出應答(sendto)o這只是最簡單的ud
17、p春戶-刀艮務忌罟應用o 頭際中常見白勺侈ij 于足域;名丿j艮務一系統 (dns)o dns客戶(稱作解析器)通常是一般客戶應用程序 (例如,telnet客戶、ftp客戶或www瀏覽器)的一個部分。解析器向dns服務器發岀一個 udp數據報,查詢某一域名對應的ip地址。服務器發冋的應答通常也是一個udp數據報。如果觀察客戶向服務器發送請求時雙方交換的分組,我們就會得到圖1-4這樣的時間系列, 頁面上時間口上而下遞增。服務器程序先啟動,英行為過程給在圖14的右半部,客戶程序稍 后啟動。我們分別來看客戶和服務器程序中調用的函數及其相應內核執行的動作。在對socket函 數的兩次調用中,上下緊挨著
18、的兩個箭頭表示內核執行請求的動作并立即返回。在調用 sendto函數時,盡管內核也立即返冋,但實際上已經發出了一個udp數據報。為簡明起見, 我們假設客戶程序的請求和服務器程序的應答所牛:成的ip數據報的長度都小于網絡的最大傳 輸單元(mtu), ip數據報不必分段。在這個圖屮,有兩次調用recvfrom函數使進程睡眠,直到有數據報到達才被喚醒。我j把內核中相應的例程記為s丄eep和wakeupo最后,我們述在圖屮標出了事務所耗費的時間。圖1-4的左側標示的是客戶端測得的事務 時間:從客戶發出請求到收到服務器的應答所經歷的時間。組成這段事務時間的數值標在圖 的右側:rtt+spt,其中rtt是
19、網絡往返時間,spt是服務器處理客戶請求的時間。udp客載戶月艮務器爭務門勺最矢厲日寸可京尤足rtt+ spt。tw客戶端懇陽焦網絡服幷器端socketsleepbind 一 recvfromsocket 二二sendto trtrecvfrom sleep返回wakeupwakeup返冋進程諸求sendto>vrttj sptmrtt圖1-4 udp春嚴-丿股務器事務i'kjh寸丿予|冬盡管沒有明確說明,但我們已經假設從客八到服務器的路徑需耍臨rtt時間,返 回的路徑乂需1/2 rtt時間。但實際悄況并非總是如此。據對大約600條internet路徑的 研究paxson 199
20、5b發現:30%的路徑呈現明顯的不對稱牲,說明兩個方向上的路市 經過了不同的站點。我們的udp各廣-月艮務苕卑春題來耳忙'活冏婁遷(每個程序只有大約3()行 有關網絡的源代碼),但在實際環境中應用還不夠健壯。由于udp是不保證可靠的協議,數 據報町能會丟失、失序或重復,因此實用的應用程序必須處理這些問題。這通常是在客戶程 序調用recvfrom時設置一個超時泄時器,用以檢測數據報的丟失,并重傳請求。如杲耍 使用超時定時器,客戶程序就要測量rtt并動態更新,這是因為互連網上的rtt會在很大范 圍內變化,并且變化很快。但如果是服務器的應答丟失,而不是請求,那么服務器就要再次 處理同一個請求
21、,這可能會給某些服務帶來問題。解決這個問題的辦法之一是讓服務器將 毎個客戶最近一次請求的響應暫存起來,必要時覓傳這個應答即可,而不需要再次處理這 個請求。最后,典型的情況是,客戶向服務器發送的每個請求中都有一個不同的標識,服 務器把這個標識在響應中傳回來,使客戶能把請求和響應匹配起來。在參考書stevens 1990 的&4節中給出了 udp二|'心 客戶-月及務 器處理這些問題的源代碼細節,但這將在程 序中增加大約5()0行源代碼。一方面,許多udp應用程序都通過執行所有這些額外步驟(超時機制、rtt值測量、請求 標識,等等)來增加口j靠性;另一方面,隨著新的udp應用程序不
22、斷出現,這些步驟也在不斷 地推陳出新。參考書patridge 1990b中指出,“為了開發,可靠的udp應用程序;你要有狀態 信息(序列號、重傳計數器和往返時間佔計器),原則上你要用到當前tcp連接塊中的全部信息。因此,構筑一個,可靠的udp;本質上和開發tcp樣難:冇些應用程序并不實現上面 所述的所有步驟:例如在接收時使用超時機制,但并不測量rtt值,當然更不會動態地更新rtt值。這樣,當應用程序從一個環境(比如局域網)移植到另一 個環境(比如廣域網)屮應用時,就可能會引發一些問題。比較好的解決辦法是用tcp而不是用 udp,這樣就町以利用tcp提供的所有可靠傳輸特性。但是這種辦法會使客戶端
23、測得的事務時 間tlrtt + spt增加到2xrtt + spt(見下一節),而且還會人大增加兩個系統之間交換的分組數 mo對付這些新的問題也有一個辦法,即用t/tcp取代tcp,我們將在1.4節中對此進行討論。1.3 tcp±的客戶-服務器下一個例子是tcp二門勺各廣-月艮務君岸事務丿血e o 罔1-5給出了客 戶程序。1 #include"cliserv.h"tcpcli.c2int3main(int argc. char *argv)4/* simple tcp client */5struct sockaddr_in serv;6charrequestr
24、equestf reply(reply】;7intsockfd, n;8if (argc != 2)9err_quit(usage: tcpcli <ip address of server>);10if (sockfd = socket(pf_inetr sock_stream. 0) < 0)11err_sys(socket error");12memset(&serv. 0. sizeof(serv);13serv.sin_family = af.inet;14serv.sinaddrs_addr = inet_addr(argv1);15serv.s
25、in_port » htons(tcp_serv_port);16i£ (connect(sockfd, (sa) &serv, sizeof(serv) < 0)17err_sys("connect error");18/ form request /19if (write(sockfd, request, request) != request)20err.sys("write error");21if (shutdown(sockfdr 1) < 0)22err_sys(shutdown error"
26、;);23if (n = read_stream(sockfd, reply, reply) < 0)24err_sys("read error*);25/* processbytes of reply */26exit(0);27 圖15 tcp事務的客戶1. 創建tcp插口和連接到服務器1 0-1 7調用socket函數創建一個tcp插口,然后在internet插口地址結構屮填入服務器的ii 地址和端口號。對connect函數的調用啟動tcp的三次握手過程,在客八和服務器之間建衛 起連接。卷1的第18章給出了tcp連接建立和釋放過程中交換分組的詳細情況。2. 發送請求和半關
27、閉連接19-22客八的請求是用write函數發給服務器的。之后客八調用shutdown函數(函數的第2個參數為1)關閉連接的一半,即數據流從客戶向服務器的方向。這就告知服務器客戶的數據 已經發完了:從客戶端向服務器傳遞了一個文件結束的通知。這時有一個設置了 fin標志的tcp報文段發給服務器。客戶此時仍然能夠從連接屮讀取數據只關閉了一個方向的數拯流。這就叫做tcp的半關w(half-close)o卷1的第18.5節給出了有關細節。3. 讀取應答23-24讀取應答是由函數read_st ream完成的,如圖1-6所示。由于tcp是一個而向字節 流的協議,沒有任何形式的記錄定界符,因而從服務器端t
28、cp傳回的應答可能會包含在多個 tcp報文段中。這也就可能會需要多次調用read函數才能傳遞給客戶進程。而且我們知道, 當服務器發送完應答后就會關閉連接,使得tcp向客八端發送一個帶fin的報文段,在rwd 函數中返冋一個文件結束標志(返冋值為()。為了處理這些細節問題,在read_stream函數幽毅斷週陽他就(碩颯至耐倔極狂滿或者read函數返冋一個文件結束標志。read_stream1 tinclude "cliserv.h"2 int3 main(int argc. char *argv()4 /* simple tcp client /5 struct socka
29、ddr_in serv;6 charrequestrequestf reply(reply);7 intsockfd, n;8 if (argc != 2)9 err_quit("usage: tcpcli <ip address of server>);10 if (sockfd = socket(pf.inet, sockstream, 0) < 0)11 err_sys(socket error");12 memset(&serv. 0, sizeof(serv);13 serv.sin_family = af.inet;14 serv.si
30、n_addrs_addr = inet_addr(argv1);15 serv.sin_port = htons(tcp_serv_port);還有一些別的方法可以在類似tcp這樣的流協議中用來給記錄圧界。許多 internet用程序(ftp、smtp、http和nntp)使用回車和換行符來標記記錄的結朿。 其他一些應用程序(dns, rpc)則在每個記錄的前面加上一個定長的記錄長度字段。 在我們的例子中,利用了 tcp的文件結束標志(fin),因為在每次事務中客戶只向服 務器發送-個請求,而服務器也只發回一個應答。ftp也在其數據連接中采用了這項 技術,用以告知對方文件已經結束。圖17給出的
31、是tcp的服務器程序。1#include"cliserv.h"tcpserv.c2int3main()4/* simple tcp server */5structsockaddr_in serv, cli;6charrequestrequest. replyreply;7 int listenfd, sockfd, nf clilen;8 if (listenfd = socket(pf_inet, sock_streamf 0) < 0)9 err.sys(socket error);10 memset(&serv. 0, sizeof(serv);11
32、serv sin_family = af_inet;12 serv. sin_addr s_addr= htonl (inaddr_any);13 serv.sin_port = htons(tcp_serv_port);14 if (bind(listenfdr (sa) &serv, sizeof(serv) < 0)15 err_sys("bind error");16 if (listendistenfd,somaxconn) < 0)17 err_sys(listen error");18 for (;)(19 clilen = si
33、zeof(cli);20 if (sockfd = accept(listenfdr (sa) &cli# &clilen) < 0)21 err_sys(accept error);22 if (n « read_stream(sockfd, requestf request) < 0)23 err_8y8(*read error");24 /* process "n" bytes of request( and create reply) */25 if (write(sockfd, reply# reply) !
34、7; reply)26 err_sys("write error");27 close(sockfd);28 29 xtcpserv.cffil-7 (續)4. 創建監聽用tcp插口8-17用于創建一個tcp插口,并將服務器的知名端口綁定到該插口上。與udp服務器一樣, tcp服務器也將通配符作為其ip地址。調用listen函數將新創建的插口作為監聽插口,用于 等待客戶端發起的連接。listen函數的笫二個參數觀定了允許的最大掛起連接數,內核要為 該插口將這些連接進行排隊處理。somaxconn在頭文件<sys/socket. h>屮圧義。jt數值過去一直都取5
35、,但現在 有一些比較新的系統將其定為10。對于一些很繁忙的服務器(例如:web服務器),已經 發現需要取更大的值,比如256或1024o在14.5節中我們還將對此問題進行更多的討論。5. 接受連接和處理請求18-28 服務器進程調用accept函數后就進入阻塞狀態,直到有客戶進程調用connect函 數而建立起一個連接。函數accept返回一個新的插口描述符sockfd,代表與客戶和服務器 之間所建立的連接。服務器調用函數read_stream讀取客戶的請求(圖16),再調用write 函數向客戶發送應答。這是一個反復循環的服務器:把當前的客戶請求處理完畢后才乂調用accept去 接受另一個客
36、戶的連接。并發服務器可以并行地處理多個客戶請求(即:同時處理)。 在unix的主機上實現并發服務器的常用技術是:在accept函數返回后,調用unix的 fork函數創建一個子進程,由子進程處理客戶的請求,父進程則緊接著乂調用 accept去接受別的客戶連接。實現并發服務器的另一項技術是為每個新建立的連接創建一個線程(叫做輕量進程)。為了避免那些為網絡無關的進程控制函數把我們的例 子搞復雜,我們只給出了反復循環的服務器。參考書stevens 1992的第4章討論比較 了循環服務器和并發服務器。還冇第三個選擇是釆用預分支服務器。即服務器啟動時連續調用work函數數次, 并讓每個子進程都在同一個監
37、聽插口描述符上調用accept函數。這種辦法節省了為 每個客戶的連接請求臨時創建子進程的時間開銷,這對于繁忙的服務器來說,是很 人的節省。有些http服務器就采用了這項技術。圖給出了 tcp一匕客戶-月爻務苕詮事務門勺11寸im 系歹u o 我彳門x,7士 空iil冬i 1_4 山i価客戶端服務器端socketbindlistensleep 一 acceptsocketconnectsleep返回 write shutdown read一 wakeupsleep返冋(數抑;)一 wakeupread (eof)二二wakeup返回read (數據)read (eof)進程請求rttwrite
38、closesv6rtt圖18 tcp上客廣-月及務器爭務口勺口寸序事務相比,網絡上交換的分組數增加了: tcp上事務的分組數是9,而udp上的則是2。采用 tcp后,客戶端測量的事務時間是不少于2 x rtt + spto通常,中間三個從客戶到服務器的 報文段(對服務器syn的ack、請求以及客戶的fin)是緊密相連的;后而兩個從服務器到客戶 的報文段(服務器的應答和fin)也是緊密相連的。這使實際事務時間比從圖18屮看到的更接近 2xrtt + spto木例中多出來的一個rtt源于tcp連接建立的時間開銷:圖中前兩個報文段所花的時 間。如果tcp可以把建連和發送客戶數據以及客戶fin(圖中客
39、戶端發出的前四個報文段)合起 來,再把服務器的應答和fin合起來,事務時間就乂可以回到rtt + spt了,這與udp的一樣。 事實上,這就是t/tcp中采用的基本技巧。6. tcp的time_wait狀態tcp要求,首先發出fin的一端(我們的例子中是公戶),在通信雙方都完全關閉連接之后, 仍然要保持在time.wait狀態直至兩倍的報文段最大生存時間(msl)。msl的建議值是120 秒,也即處于t1me_wate狀態耍達到4分鐘。當連接處j * t1me_wa1t狀態時,同一連接(即 客戶ip地址和端口號,以及服務器ip地址和端口號這4個值相同)不能重復打開(我們在第4章中 還要更多地討
40、論time.wait狀態)o許多基于伯克利代碼的tcp實現,在time_wait狀態的保持時間僅僅為60秒, 而不是rfc 1122 braden 1989中指定的240秒。在本書的所有計算中,我們還是假 定正確的等待周期為240秒。在我們的例子中,客戶端訂先發出fin,這稱為主動關閉,因而time_wait狀態出現在 客八端。在這個狀態延續期內,tcp耍為這個己經關閉的連接保留一定的狀態信息,以便能 止確處理那些在網絡中延遲一段時間、在連接關閉z后到達的報文段。同樣,如果最后一個 ack丟失了,服務器將重傳fin,使客戶端重傳最后的ack。其他一些應用程序,特別是www中的http,要求客戶
41、程序發送一個專門的命令來指示 已經將請求發送完畢(而不是像我們的客戶程序那樣采用半關閉連接的辦法);接著服務器就發 冋應答,緊接著就是服務器的fin。然后客戶程序再發出fin。這樣做與前而所述的不同之處 在于,現在的time_wait狀態出現在服務器端而不是客戶端。對許多客戶訪問的繁忙服務器 來說,需耍保留的狀態信息會占用服務器的大量內存。因此,當設計一個事務性客戶服務器 應用程序時,讓連接的哪一端關閉后進入time.wait狀態值得仔細斟酌。我們還將看到, t/tcp可以讓time_wait狀態的延續時間從240秒減少到大約12秒。7. 減少tcp中的報文段數像圖1-9所示的那樣,把數據和控
42、制報文段合并起來可以減少圖1-8中所示的tcp報文段數。 請注意,這里的第一個報文段中包含有syn、數據和fin,而不像圖1-8中那樣僅僅是syno 類似地,服務器的應答和服務器的f1n也可以合并。雖然這樣的分組序列也符合tcp的規沱, 但是作者無法在應用程序中利用現有的插口 api使tcp產生這樣的報文段序列(因此才在圖1-9 屮客戶端產生第一個報文段時和服務器端產生最后一個報文段時標上了問號);而且據作者所 知,也沒有哪一個應用程序確實生成了這樣的報文段序列。值得一提的是,盡管我們把報文段的數h山9減少到了 5,但客戶端觀測的事務依然是2x rtt + spto這是因為tcp中規定,服務器
43、端的tcp在三次握手結束z前不能向服務器進程提 交數據(卷2的笫27.9節說明了 tcp是如何在連接建立之前將到達的數據進行排隊緩存的)。加客戶端r t函數, 內核socketread 十 sleep返回(數據)一 wakeup read (eof).二sleep將數據加入隊列wakeup圖19最少tcp事務的時序bindlistenaccept返回二“ad (數據)x read (eof)進程請求rtt% rttsptv6rtt上這種限制的原因是服務器必須確信來自客戶的syn是“新的;即不是以前某次連接的syn在 網絡中延遲一段時間后到達服務器端的。確認過程是這樣的:服務器對客戶發送的syn
44、發送 確認,再發出口己的syn,然片等待客戶對該syn的確認。當三次握手完成之后,通信雙方 就都知道對方的syn是新的。由于在三次握手結朿之前服務器無法開始處理客戶的請求, 故分組數的減少并沒有縮短客戶端測得的事務時間。下面這段話引自 rfc 1185 jacobson, braden, and zhang 1990的附錄:"注意:使連接能夠盡快重復利用是盡期tcp開發的重要冃標。z所以有這樣的要求是因為當時人們希望tcp既是應用層事務協議的基礎,同時也是面向連接協議的基礎。半時討 論中英至把既包含有syn和fin比特,同時乂包含數據的報文段叫做'圣誕樹'報文 段和k
45、amikaze(敢死隊)'報文段。但這種熱情很快被潑了冷水,因為人們發現,三 次syn握手和fin握手意味著一次數據交換至少需要5個分組。而且,time_wait狀 態的延續說明同一個連接不可能馬上再次打開。于是,再沒有人在這個領域做進一 步的研究,盡管現在的某些應用程序(比如,簡單郵件傳送協議,smtp)經常會產生 很短的會話。人們-般都可以采用為每個連接選用不同的端口對的辦法來避開重用 問題”。rfc 1379 braden 1992b中寫到:“這些'kamikaze(敢死隊)'報文段不是作為-種支持的服務來提供,而主要用來搞垮其他實驗性的tcp! ”作為一個實驗,
46、作者編寫了一個測試程序,這個程序把syn-與數據和fin在一個報文段中 發出去,即圖19中的第一個報文段。該報文段發給8個不同版木unix的標準echo服務器(卷1的 笫1.12節),再用tcpdump觀察所交換的數據。其中的7個(4.4bsd、aix 322、bsd/os 2.0> hp-ux 9.01、irix system v.3、sunos 4.3和system v release 4.0)都能正確處理該報文段, 另外一個(solaris 2.4)則把隨syn起傳送的數據扔掉,迫使客戶程序重傳數據。那7個系統中的報文段序列與圖1-9所描繪的不盡相同。當三次握手結朿厲,服務器立刻
47、就對客戶的數據和fin發出確認。另外,中于echo服務器無法把數據和fin捆綁在一起(圖19 中的第四個報文段)發送,結果是發了兩個報文段而不只是一個:應答和緊接其后的fin。因 此 報文段的總數是7而不是圖19中所示的5。我們在3.7節中會進一步討論與非t/tcp實現的 兼容性問題,并給出一些tcpdump的輸出結果。許多從伯克利演變而來的系統屮,服務器無法處理接收到的報文段中只有syn、 fin,而沒有數據、ack的情況。這個bug使得新創建的插口保持在close_wait狀 態直到主機重新啟動。但這卻是一個合法的t/tcp報文段:客戶建立起了一個連接, 沒冇發送任何數據,然后就關閉連接。
48、1.4 t/tcph的客戶-服務器我們的t/tcp容戶-jjk 務帑 白勺測he石馬牙口上沖f施 tcp容戶ttcpcli.c-月艮 務君聞i勺洌h弋冇馬田各徇-不i亙j , lu 便能夠利用t/tcp的優勢。圖1-1() 給出丁卩僦血舐客戶趙herv. h2 int3 main(int argc. char *argv)4 / t/tcp client */5 struct sockaddr_in serv;6 charrequest(request reply(reply:7 intsockfd, n;8 if (argc ! 2)9 err_quit(usage: ttcpcli <
49、;ip address of server>");10 if (sockfd = socket(pf_inetr sock_streamr 0) < 0)11 err.sys(socket error);圖l10 t/tcp上的事務客戶程序12 memset(&serv. 0, sizeof(serv);13 serv.sin_family = af.inet;14 serv.sin_addr.s_addr = inet_addr(argv1);15 serv.sinjport = htons(tcp_serv_port);16 /* form requestj
50、*/17 if (sendto(sockfdr request, request, msg_eofr18 (sa) &serv, sizeof(serv) != request)19 err_sys("sendto error");20 if (n = read_stream(sockfdr reply, reply) < 0)21 err_sys("read error);22 /* process "n" bytes of reply /23 exit(0);24 ttcpcli.i圖1-10 (續)1. 創建tcp插口10-
51、15對socket函數的調用與tcp上的客八程序一樣,在internet口地址結構屮同樣也填 入服務器的ip地址和端口號。2. 向服務器發送請求17-19 t/tcp上的客戶程序不調用connect函數。而是直接調用標準的sendto函數,該函 數向服務器發送請求,同時與服務器建立起連接。此外,我們還用sendt。函數的第4個參數 指定了一個新的標志msg_eof,用以告訴系統內核數據已經發送完畢。這樣做就相當于圖15 中調用shutdown函數,向服務器發送一個fin。msg_eof標志是t/tcp實現屮新加入的,不 耍把它一與msg_eor標志混淆,后者是基于記錄的協議(比如osi的運輸層
52、協議)屮用來標志記錄 結束的。我們將在圖112中看到,調用sendto函數的結果是客戶端的syn、客戶的請求以及 fin都包含在一個報文段中發送出去。換言z,調用一個sendto函數就實現了 connect> write和shut down三個函數的功能。3. 讀服務器的應答20-21 讀服務器的應答還是用read_stream函數,與前文討論過的tcp上的客戶程序一 樣。圖111所示的是t/tcp±的服務器程序。1 *includecliserv.h*ttcpseru.c2 int 3 main()5 struct6 char7 int/* t/tcp server */so
53、ckaddr_in serv, cli; request(request)r replyreply; listenfd, sockfd n. clilen;8 if (listenfd « socket(pf_inetr sock.stream, 0) < 0)9 err_sys(socket error);10 memset(&serv. 0r sizeof(serv);11 serv.sin_family = af_inet;12 serv. sin_addr s_addr = htonl (inaddrany);13 serv.sin_port = htons(t
54、cp_serv_port);14 if (bind(listenfdf (sa) &serv. sizeof(serv) < 0)15 err_sys("bind error*);16 if (listen(listenfd, somaxconn) < 0)17 err.sys("listen error*);18 for (;) 19 clilen = sizeof(cli);2°if (sockfd = accept(listenfd, (sa)&clilen) < 0)21 err_sys(waccept error);22
55、 if (n = read_stream(sockfdf request, request) < 0)23 err_sys("read error*);24 /* process "n* bytes of request! and create reply( /25 if (send(sockfd, reply, reply, msg_eof) 2 reply)2 6err_sys(send error);27 close(sockfd);28 29 ttcpserv.c圖111 (續)這個程序與圖1-7中tcp上的服務器程序兒乎完全一樣:對socket函數、bind
56、函數、 listen函數、accept函數和read_st ream函數的調用都一模一樣。唯一的不同在于 t/tcp上的服務器發送應答時調用的是send函數,而不是write函數。這樣就可以設置 msg_eof標志,從而可以將服務器的應答和服務器的fin合并在一起發送。圖1-12所示的是t/tcp上容廣_刀艮務召需事務門勺i寸序醫1 ot/tcp上的客戶測量到的爭務時間和udp上的幾乎一樣(圖1-4): rtt + spt。我們估計 t/tcp上的時間會比udp上的時間稍長一點,這是兇為tcp協議需要處理的事情比udp要多一 些,而且通信雙方都要執行兩次read操作分別讀數據和文件結束標志(而udp環境下雙方都 只要調用一次recvfrom函數即可)。但是雙方主機上這一段額外的處理時間比一次網絡往返 時間rtt要小得多(我們在1.6節中給出了一些測試數據,用來比較udp、tcp和t/tcp上的客 戶服務器申務的差別)。由此我們可以得出結論:t/tcp上的申務時間要比tcp上的竊務小大 約一次網絡往返時間rtto t/tcp屮省下來的這個rtt來自于tao,即tcp加速打開(tcp accelerated open)o這種方式跳過
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 糖尿病護理匯報
- 機關新聞寫作培訓
- 竣工圖繪制培訓
- 安福大型慶典活動方案
- 憲法活動五個一活動方案
- 室外活動策劃方案
- 寵物公益展覽活動方案
- 宣教基地活動方案
- 安方公司年會活動方案
- 室內新年活動方案
- 小餐飲經營許可告知書承諾書范文
- JBT 3300-2024 平衡重式叉車 整機試驗方法(正式版)
- 鄭州經貿學院輔導員考試題庫
- 城軌行車組織實訓總結報告
- (正式版)HGT 6263-2024 電石渣脫硫劑
- 農村村民土地轉讓協議書
- GB/T 6346.1-2024電子設備用固定電容器第1部分:總規范
- TDT1056-2019縣級國土調查生產成本定額
- CSR法律法規及其他要求清單(RBA)2024.3
- 二年級100以內加減法混合運算題庫
- 國家開放大學《鋼結構(本)》期末復習指導參考答案
評論
0/150
提交評論