• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            loop_in_codes

            低調做技術__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

            tcp要點學習-建立連接

            Author : Kevin Lynx

            準備:

            在這里本文將遵循上一篇文章的風格,只提TCP協議中的要點,這樣我覺得可以更容易地掌握TCP。或者
            根本談不上掌握,對于這種純理論的東西,即使你現在掌握了再多的細節,一段時間后也會淡忘。

            在以后各種細節中,因為我們會涉及到分析一些TCP中的數據報,因此一個協議包截獲工具必不可少。在
            <TCP/IP詳解>中一直使用tcpdump。這里因為我的系統是windows,所以只好使用windows平臺的tcpdump,
            也就是WinDump。在使用WinDump之前,你需要安裝該程序使用的庫WinpCap

            關于WinDump的具體用法你可以從網上其他地方獲取,這里我只稍微提一下。要讓WinDump開始監聽數據,
            首先需要確定讓其監聽哪一個網絡設備(或者說是網絡接口)。你可以:

             

            windump -D

             

            獲取當前機器上的網絡接口。然后使用:

             

            windump -i 2 

             

            開始對網絡接口2的數據監聽。windump如同tcpdump(其實就是tcpdump)一樣支持過濾表達式,windump
            將會根據你提供的過濾表達式過濾不需要的網絡數據包,例如:

             

            windump -i 2 port 4000 

             

            那么windump只會顯示端口號為4000的網絡數據。

            序號和確認號:

            要講解TCP的建立過程,也就是那個所謂的三次握手,就會涉及到序號和確認號這兩個東西。翻書到TCP
            的報文頭,有兩個很重要的域(都是32位)就是序號域和確認號域。可能有些同學會對TCP那個報文頭有所
            疑惑(能看懂我在講什么的會產生這樣的疑惑么?),這里我可以告訴你,你可以假想TCP的報文頭就是個
            C語言結構體(假想而已,去翻翻bsd對TCP的實現,肯定沒這么簡單),那么大致上,所謂的TCP報文頭就是:

            typedef struct _tcp_header
            {
               
            /// 16位源端口號
                unsigned short src_port;
               
            /// 16位目的端口號
                unsigned short dst_port;
               
            /// 32位序號
                unsigned long seq_num;
               
            /// 32位確認號
                unsigned long ack_num;
               
            /// 16位標志位[4位首部長度,保留6位,ACK、SYN之類的標志位]
                unsigned short flag;
               
            /// 16位窗口大小
                unsigned short win_size;
               
            /// 16位校驗和
                short crc_sum;
               
            /// 16位緊急指針
                short ptr;
               
            /// 可選選項
               
            /// how to implement this ?   

            }
            tcp_header;


            那么,這個序號和確認號是什么?TCP報文為每一個字節都設置一個序號,覺得很奇怪?這里并不是為每一
            字節附加一個序號(那會是多么可笑的編程手法?),而是為一個TCP報文附加一個序號,這個序號表示報文
            中數據的第一個字節的序號,而其他數據則是根據離第一個數據的偏移來決定序號的,例如,現在有數據:
            abcd。如果這段數據的序號為1200,那么a的序號就是1200,b的序號就是1201。而TCP發送的下一個數據包
            的序號就會是上一個數據包最后一個字節的序號加一。例如efghi是abcd的下一個數據包,那么它的序號就
            是1204。通過這種看似簡單的方法,TCP就實現了為每一個字節設置序號的功能(終于明白為什么書上要告訴
            我們‘為每一個字節設置一個序號’了吧?)。注意,設置序號是一種可以讓TCP成為’可靠協議‘的手段。
            TCP中各種亂七八糟的東西都是有目的的,大部分目的還是為了’可靠‘兩個字。別把TCP看高深了,如果
            讓你來設計一個網絡協議,目的需要告訴你是’可靠的‘,你就會明白為什么會產生那些亂七八糟的東西了。

            接著看,確認號是什么?因為TCP會對接收到的數據包進行確認,發送確認數據包時,就會設置這個確認號,
            確認號通常表示接收方希望接收到的下一段報文的序號。例如某一次接收方收到序號為1200的4字節數舉報,
            那么它發送確認報文給發送方時,就會設置確認號為1204。

            大部分書上在講確認號和序號時,都會說確認號是序號加一。這其實有點誤解人,所以我才在這里廢話了
            半天(高手寬容下:D)。

            開始三次握手:

            如果你還不會簡單的tcp socket編程,我建議你先去學學,這就好比你不會C++基本語法,就別去研究vtable
            之類。

            三次握手開始于客戶端試圖連接服務器端。當你調用諸如connect的函數時,正常情況下就會開始三次握手。
            隨便在網上找張三次握手的圖:

            connection

            如前文所述,三次握手也就是產生了三個數據包。客戶端主動連接,發送SYN被設置了的報文(注意序號和
            確認號,因為這里不包含用戶數據,所以序號和確認號就是加一減一的關系)。服務器端收到該報文時,正
            常情況下就發送SYN和ACK被設置了的報文作為確認,以及告訴客戶端:我想打開我這邊的連接(雙工)。客戶
            端于是再對服務器端的SYN進行確認,于是再發送ACK報文。然后連接建立完畢。對于阻塞式socket而言,你
            的connect可能就返回成功給你。

            在進行了鋪天蓋地的羅利巴索的基礎概念的講解后,看看這個連接建立的過程,是不是簡單得幾近無聊?

            我們來實際點,寫個最簡單的客戶端代碼:

               sockaddr_in addr;
                memset(
            &addr, 0, sizeof( addr ) );
                addr.sin_family
            = AF_INET;
                addr.sin_port
            = htons( 80 );
               
            /// 220.181.37.55
                addr.sin_addr.s_addr = inet_addr( "220.181.37.55" );
                printf(
            "%s : connecting to server.\n", _str_time() );
               
            int err = connect( s, (sockaddr*) &addr, sizeof( addr ) );

             
            主要就是connect。運行程序前我們運行windump:

             

            windump -i 2 host 220.181.37.55 

             

            00:38:22.979229 IP noname.domain.4397 > 220.181.37.55.80: S 2523219966:2523219966(0) win 65535 <mss 1460,nop,nop,sackOK>
            00:38:23.024254 IP 220.181.37.55.80 > noname.domain.4397: S 1277008647:1277008647(0) ack 2523219967 win 2920 <mss 1440,nop,nop,sackOK>
            00:38:23.024338 IP noname.domain.4397 > 220.181.37.55.80: . ack 1 win 65535 

             

            如何分析windump的結果,建議參看<tcp/ip詳解>中對于tcpdump的描述。

            建立連接的附加信息:

            雖然SYN、ACK之類的報文沒有用戶數據,但是TCP還是附加了其他信息。最為重要的就是附加的MSS值。這個
            可以被協商的MSS值基本上就只在建立連接時協商。如以上數據表示,MSS為1460字節。

            連接的意外:

            連接的意外我大致分為兩種情況(也許還有更多情況):目的主機不可達、目的主機并沒有在指定端口監聽。
            當目的主機不可達時,也就是說,SYN報文段根本無法到達對方(如果你的機器根本沒插網線,你就不可達),
            那么TCP收不到任何回復報文。這個時候,你會看到TCP中的定時器機制出現了。TCP對發出的SYN報文進行
            計時,當在指定時間內沒有得到回復報文時,TCP就會重傳剛才的SYN報文。通常,各種不同的TCP實現對于
            這個超時值都不同,但是據我觀察,重傳次數基本上都是3次。例如,我連接一個不可達的主機:

             

            12:39:50.560690 IP cd-zhangmin.1573 > 220.181.37.55.1024: S 3117975575:3117975575(0) win 65535 <mss 1460,nop,nop,sackOK>
            12:39:53.538734 IP cd-zhangmin.1573 > 220.181.37.55.1024: S 3117975575:3117975575(0) win 65535 <mss 1460,nop,nop,sackOK>
            12:39:59.663726 IP cd-zhangmin.1573 > 220.181.37.55.1024: S 3117975575:3117975575(0) win 65535 <mss 1460,nop,nop,sackOK>

             

            發出了三個序號一樣的SYN報文,但是沒有得到一個回復報文(廢話)。每一個SYN報文之間的間隔時間都是
            有規律的,在windows上是3秒6秒9秒12秒。上面的數據你看不到12秒這個數據,因為這是第三個報文發出的
            時間和connect返回錯誤信息時的時間之差。另一方面,如果連接同一個網絡,這個間隔時間又不同。例如
            直接連局域網,間隔時間就差不多為500ms。

            (我強烈建議你能運行windump去試驗這里提到的每一個現象,如果你在ubuntu下使用tcpdump,記住sudo :D)

            出現意外的第二種情況是如果主機數據包可達,但是試圖連接的端口根本沒有監聽,那么發送SYN報文的這
            方會收到RST被設置的報文(connect也會返回相應的信息給你),例如:

             

            13:37:22.202532 IP cd-zhangmin.1658 > 7AURORA-CCTEST.7100: S 2417354281:2417354281(0) win 65535 <mss 1460,nop,nop,sackOK>
            13:37:22.202627 IP 7AURORA-CCTEST.7100 > cd-zhangmin.1658: R 0:0(0) ack 2417354282 win 0
            13:37:22.711415 IP cd-zhangmin.1658 > 7AURORA-CCTEST.7100: S 2417354281:2417354281(0) win 65535 <mss 1460,nop,nop,sackOK>
            13:37:22.711498 IP 7AURORA-CCTEST.7100 > cd-zhangmin.1658: R 0:0(0) ack 1 win 0
            13:37:23.367733 IP cd-zhangmin.1658 > 7AURORA-CCTEST.7100: S 2417354281:2417354281(0) win 65535 <mss 1460,nop,nop,sackOK>
            13:37:23.367826 IP 7AURORA-CCTEST.7100 > cd-zhangmin.1658: R 0:0(0) ack 1 win 0 

             

            可以看出,7AURORA-CCTEST.7100返回了RST報文給我,但是我這邊根本不在乎這個報文,繼續發送SYN報文。
            三次過后connect就返回了。(數據反映的事實是這樣)

            關于listen:

            TCP服務器端會維護一個新連接的隊列。當新連接上的客戶端三次握手完成時,就會將其放入這個隊列。這個隊

            列的大小是通過listen設置的。當這個隊列滿時,如果有新的客戶端試圖連接(發送SYN),服務器端丟棄報文,

            同時不做任何回復。

            總結:
            TCP連接的建立的相關要點就是這些(or more?)。正常情況下就是三次握手,非正常情況下就是SYN三次超時,
            以及收到RST報文卻被忽略。

            posted on 2008-05-11 01:03 Kevin Lynx 閱讀(3694) 評論(10)  編輯 收藏 引用 所屬分類: game developnetwork

            評論

            # re: tcp要點學習-建立連接 2008-05-11 02:40 Fox

            看來,這一塊的東西,我又可以偷偷懶,直接請教你了;)  回復  更多評論   

            # re: tcp要點學習-建立連接 2008-05-15 17:34 買書網

            TCP協議中的要點寫的很好  回復  更多評論   

            # re: tcp要點學習-建立連接 2008-05-16 09:25

            寫的很通俗易懂。
              回復  更多評論   

            # re: tcp要點學習-建立連接[未登錄] 2008-09-03 22:21 thinkinnight

            文章寫得很好,看了之后我就自己想試了一下。結果發現自己編寫的服務器、客戶端在三次握手后,由客戶端又向服務器發送了一個RST ACK,不知道是為什么。

            服務器端并沒有使用while循環,而只是一次的accept。我想原因可能在這里,但是具體的還是不清楚如何出的問題。能交流一下嗎?謝謝  回復  更多評論   

            # re: tcp要點學習-建立連接 2008-09-04 13:54 Kevin Lynx

            @thinkinnight
            發送RST通常都是因為異常退出導致的。可能你沒有正常關閉。  回復  更多評論   

            # re: tcp要點學習-建立連接[未登錄] 2008-09-04 16:01 thinkinnight

            Kevin Lynx你好, 關鍵是我不知道問題是出在哪里.程序是最簡單的client和server, 為winsock, 代碼的主要部分如下(去掉wsastartup和wsacleanup,其余socket相關代碼均在下方,沒有寫close):

            [server]

            sockaddr_in server;
            SOCKET s = socket(AF_INET, SOCK_STREAM, NULL);

            server.sin_family = AF_INET;
            server.sin_port = htons(5001);
            server.sin_addr.S_un.S_addr = INADDR_ANY;

            int _err = bind(s,(sockaddr *)&server, sizeof(server));
            _err = listen(s,5);
            SOCKET forclient = accept(s, (sockaddr *)NULL, NULL);


            [client]

            sockaddr_in client;
            memset(&client,0,sizeof(client));

            client.sin_family = AF_INET;
            client.sin_port = htons(5001);
            client.sin_addr.S_un.S_addr = inet_addr("192.168.0.2");

            SOCKET s = socket(AF_INET,SOCK_STREAM,NULL);
            int _err=connect(s, (sockaddr *)&client, sizeof(sockaddr));

            windump結果(處理了一下,將ip隱藏了)
            windump: listening on \Device\xxx_{xxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxx}
            15:53:20.162579 IP client.36519 > server.5001: S 36061147:360611
            47(0) win 65535 <mss 1460,nop,nop,sackOK>
            15:53:20.163065 IP server.5001 > client.36519: S 808783039:80878
            3039(0) ack 36061148 win 65535 <mss 1460,nop,nop,sackOK>
            15:53:20.163110 IP client.36519 > server.5001: . ack 1 win 65535

            15:53:20.163243 IP client.36519 > server.5001: R 1:1(0) ack 1 wi
            n 0

            4 packets captured
            26 packets received by filter
            0 packets dropped by kernel

            就是最后會有一個RST, ACK, 對這個不明白是為什么.
            也不應該是半開端口吧.  回復  更多評論   

            # re: tcp要點學習-建立連接[未登錄] 2008-09-04 16:41 thinkinnight

            windump是只監聽到server 5001端口的通信,其他都被過濾掉了,有用的信息就這四條.  回復  更多評論   

            # re: tcp要點學習-建立連接 2008-09-05 09:30 Kevin Lynx

            加上正常關閉closesocket之類,在程序未退出前不要ctrl+c強制退出。你試下這些。我做實驗也是在WIN平臺下。  回復  更多評論   

            # re: tcp要點學習-建立連接[未登錄] 2008-09-06 21:34 thinkinnight

            我試了一下,加上closesocket還是一樣的結果,而且關鍵我覺得不是得到正確的序列,而是內部到底發生了什么造成這種結果。
            剛看了TCP/IP卷一中對RST的描述,一共有三種情況:
            1. 到不存在的端口的連接請求
            2. 異常終止一個連接
            3. 半打開連接

            第一種情況下,應該是服務器向客戶端發送RST。告知該端口不存在。
            第二種情況,按照書上面的說法,需要置SO_LINGER,這樣使得連接關閉時進行復位而不是正常的FIN,但是程序中并沒有這樣。可是現象倒是很符合。
            第三種情況,書上也是接受方以RST作為應答,那現在也沒有數據交互,而是直接出現RST,也不像。

            所以只是想研究一下這種情況的原理、過程。
            至于正常的情況,能做出來肯定是很好,不然反正書上那些內容也知道了,關鍵還是使用嘛,所以就是想對真實的現象有一些了解,我都懷疑是不是漏包了。。。但是看來看去,也只有這些
              回復  更多評論   

            # re: tcp要點學習-建立連接 2010-02-11 10:03 tcpcoder

            so good  回復  更多評論   

            久久人人爽人爽人人爽av| 乱亲女H秽乱长久久久| 国产精品免费久久久久影院| 久久九九亚洲精品| 色成年激情久久综合| 一极黄色视频久久网站| 久久天天躁狠狠躁夜夜96流白浆| 久久99精品久久久久久久不卡 | 久久e热在这里只有国产中文精品99 | 少妇人妻综合久久中文字幕| 亚洲va中文字幕无码久久不卡| 91久久精品国产91性色也| 亚洲综合久久夜AV | 欧美综合天天夜夜久久| 久久久久久国产精品美女| 国产呻吟久久久久久久92| 欧美亚洲色综久久精品国产| 欧美性大战久久久久久| 国产精品久久久久久久久鸭| 精品国产乱码久久久久软件| 久久精品国产欧美日韩| 91精品国产综合久久婷婷 | 伊人久久大香线蕉综合影院首页| 国产精品久久久久久福利漫画| 久久精品国产男包| 日韩电影久久久被窝网| 成人精品一区二区久久| 久久久青草久久久青草| 久久丫精品国产亚洲av不卡| 久久精品国产清自在天天线| 麻豆久久| 亚洲欧洲久久av| 国产69精品久久久久观看软件| 99久久久久| www亚洲欲色成人久久精品| 久久久九九有精品国产| 蜜桃麻豆www久久| 国产精品成人无码久久久久久| 久久福利青草精品资源站免费 | 久久精品国产乱子伦| 亚洲欧洲久久久精品|