• <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

            低調(diào)做技術(shù)__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

            #

            半同步半異步模式以及Leader_Follwer模式

            這里提到的兩個設(shè)計模式都是用于高并發(fā)系統(tǒng)(例如一個高性能的網(wǎng)絡(luò)服務(wù)器)的。這里我只是簡單地提一下:

            1.半同步/半異步(half-sync/half-async):

            在網(wǎng)上一份資料中引用了一本貌似很經(jīng)典的書里的比喻:

            許多餐廳使用 半同步/半異步 模式的變體。例如,餐廳常常雇傭一個領(lǐng)班負(fù)責(zé)迎接顧客,并在餐廳繁忙時留意給顧客安排桌位,
            為等待就餐的顧客按序排隊是必要的。領(lǐng)班由所有顧客“共享”,不能被任何特定顧客占用太多時間。當(dāng)顧客在一張桌子入坐后,
            有一個侍應(yīng)生專門為這張桌子服務(wù)。

            按照另一份似乎比較權(quán)威的文檔的描述,要實現(xiàn)半同步/半異步模式,需要實現(xiàn)三層:異步層、同步層、隊列層。因為很多操作
            采用異步方式會比較有效率(例如高效率的網(wǎng)絡(luò)模型似乎都采用異步IO),但是異步操作的復(fù)雜度比較高,不利于編程。而同步
            操作相對之下編程要簡單點。為了結(jié)合兩者的優(yōu)點,就提出了這個模式。而為了讓異步層和同步層互相通信(模塊間的通信),系
            統(tǒng)需要加入一個通信隊列。異步層將操作結(jié)果放入隊列,同步層從隊列里獲取操作結(jié)果。

            回過頭來看看我之前寫的那個select網(wǎng)絡(luò)模型代碼,個人認(rèn)為基本上算是一個半同步半異步模式的簡單例子:Buffer相當(dāng)于通信
            隊列,網(wǎng)絡(luò)底層將數(shù)據(jù)寫入Buffer,上層再同步地從該隊列里獲取出數(shù)據(jù)。這樣看來似乎也沒什么難度。 = =

            關(guān)于例子代碼,直接引用iunknown給的:

            //這就是一個典型的循環(huán)隊列的定義,iget 是隊列頭,iput 是隊列尾</STRONG>  
            int clifd[MAXNCLI], iget, iput;   
            int main( int argc, char * argv[] )  
            {  
              ......  
              int listenfd = Tcp_listen( NULL, argv[ 1 ], &addrlen );  
              ......  
              iget = iput = 0;  
              for( int i = 0; i < nthreads; i++ ) {  
                pthread_create( &tptr[i].thread_tid, NULL, &thread_main, (void*)i );  
              for( ; ; ) {  
                connfd = accept( listenfd, cliaddr,, &clilen );  
                clifd[ iput ] = connfd;     // 接受到的連接句柄放入隊列</STRONG>  
                if( ++iput == MAXNCLI ) iput = 0;    
              }  
            }  
            void * thread_main( void * arg )  
            {  
              for( ; ; ) {  
                while( iget == iput ) pthread_cond_wait( ...... );  
                connfd = clifd[ iget ];     // 從隊列中獲得連接句柄</STRONG>  
                if( ++iget == MAXNCLI ) iget = 0;  
                ......  
                web_child( connfd );  
                close( connfd );  
              }  

            2.領(lǐng)導(dǎo)者/追隨者(Leader/Followers):

            同樣,給出別人引用的比喻:

            在日常生活中,領(lǐng)導(dǎo)者/追隨者模式用于管理許多飛機場出租車候車臺。在該用例中,出租車扮演“線程”角色,排在第一輛的出
            租車成為領(lǐng)導(dǎo)者,剩下的出租車成為追隨者。同樣,到達出租車候車臺的乘客構(gòu)成了必須被多路分解給出租車的事件,一般以先進
            先出排序。一般來說,如果任何出租車可以為任何顧客服務(wù),該場景就主要相當(dāng)于非綁定句柄/線程關(guān)聯(lián)。然而,如果僅僅是某些
            出租車可以為某些乘客服務(wù),該場景就相當(dāng)于綁定句柄/線程關(guān)聯(lián)。

            其實這個更簡單,我記得<unix網(wǎng)絡(luò)編程>中似乎提到過這個。總之有一種網(wǎng)絡(luò)模型(connection-per-thread?)里,一個線程用于
            accept連接。當(dāng)接收到一個新的連接時,這個線程就轉(zhuǎn)為connection thread,而這個線程后面的線程則上升為accept線程。這里,
            accept線程就相當(dāng)于領(lǐng)導(dǎo)者線程,而其他線程則屬于追隨者線程。

            iunknown 的例子代碼:

            int listenfd;  
            int main( int argc, char * argv[] )  
            {  
              ......  
              listenfd = Tcp_listen( NULL, argv[ 1 ], &addrlen );  
              ......  
              for( int i = 0; i < nthreads; i++ ){  
                pthread_create( &tptr[i].thread_tid, NULL, &thread_main, (void*)i );  
              }  
              ......  
            }  
            void * thread_main( void * arg )  
            {  
              for( ; ; ){  
                ......  
                // 多個線程同時阻塞在這個 accept 調(diào)用上,依靠操作系統(tǒng)的隊列</STRONG>  
                connfd = accept( listenfd, cliaddr, &clilen );  
                ......  
                web_child( connfd );  
                close( connfd );  
                ......  
              }  

            posted @ 2008-06-04 10:40 Kevin Lynx 閱讀(8860) | 評論 (5)編輯 收藏

            寫了個簡單的聊天服務(wù)器

            沒什么技術(shù)含量,將select模型做簡單的封裝,同時提供服務(wù)器端和客戶端所用的接口。功能實現(xiàn)上對數(shù)據(jù)的發(fā)送和接收
            都做了緩存,搞得跟異步IO一樣 = =#。

            這個例子聊天服務(wù)器可以使用telnet登錄,服務(wù)器直接將telnet發(fā)來的字符串轉(zhuǎn)發(fā)給所有客戶端。我稍微寫了一個小的網(wǎng)絡(luò)
            模塊,可以用于以后寫網(wǎng)絡(luò)程序的例子代碼,也算是練習(xí)下網(wǎng)絡(luò)庫的設(shè)計。

            系統(tǒng)總體類圖如下:

            classdiagram

            Address用于包裝sockaddr_in結(jié)構(gòu)體,目的就是讓系統(tǒng)用起來更方便。

            Buffer用于封裝原始內(nèi)存,主要目的是拿來做發(fā)送、接收數(shù)據(jù)緩沖。

            Fdset差不多和FD_SET一樣,只是這里自己寫一個FD_SET,可以讓連接數(shù)不受FD_SETSIZE的限制。

            Socket封裝了基本的SOCKET操作,包括創(chuàng)建、銷毀套接字。

            Session比較有意思,按我的意思,就是代表一個網(wǎng)絡(luò)連接。對于服務(wù)器端,可能會有很多連接,每一個連接可以用一個
            Session對象表示。而對于客戶端,只有一個連接,那么就是一個Session對象。對于Session對象來說,可以進行數(shù)據(jù)的
            發(fā)送和接收,因此這里Session有recv、send之類的接口。為了緩沖數(shù)據(jù),所以Session對于讀寫分別有一個Buffer對象。

            Server代表一個服務(wù)器,直接提供創(chuàng)建服務(wù)器的接口。同時使用一個unsigned long作為每一個連接的ID號。

            Client代表一個客戶端,可以直接用于連接服務(wù)器。

            下載文件提供網(wǎng)絡(luò)模塊代碼,以及三個例子程序。點擊下載

            posted @ 2008-05-28 17:13 Kevin Lynx 閱讀(4329) | 評論 (4)編輯 收藏

            tcp要點學(xué)習(xí)-數(shù)據(jù)發(fā)送一

            Author : Kevin Lynx

            1. 什么是delayed ack algorithm
               delayed ack algorithm也就是<TCP/IP詳解>中所謂的"經(jīng)受時延的確認(rèn)"(翻譯得真饒舌 = =||)。在RFC1122中提到delayed ack
               的概念:
              

              "
                  A host that is receiving a stream of TCP data segments can
                  increase efficiency 
            in both the Internet and the hosts by
                  sending fewer than one ACK (acknowledgment) segment per data
                  segment received; 
            this is known as a "delayed ACK" [TCP:5].
                
            "


               我在之前提到過,TCP在收到每一個數(shù)據(jù)包時,都會發(fā)送一個ACK報文給對方,用以告訴對方"我接收到你剛才發(fā)送的數(shù)據(jù)了"。并
               且會在報文的確認(rèn)號字段中標(biāo)志希望接收到的數(shù)據(jù)包。

               但是,如你所想,如果為每一個接收到的報文都發(fā)送一個ACK報文,那將會增加網(wǎng)絡(luò)的負(fù)擔(dān)。于是,為了解決這個問題,delayed
               ack被提出。也就是說,實現(xiàn)了delayed ack的TCP,并不見得會對每一個接收到的數(shù)據(jù)包發(fā)送ACK確認(rèn)報文。

               實際情況是,TCP延遲發(fā)送這個ACK。延遲多久?<TCP/IP詳解>中說的是200ms,在RFC1122中說的則是500ms。delayed ack有時候
               還會附加到數(shù)據(jù)報文段一起發(fā)送,如果在延遲時間內(nèi)有報文段要發(fā)送的話,如果沒有,那么當(dāng)延遲時間到時,就單獨發(fā)送ACK。

               在另一份文檔中,作者講到delayed ack的好處:
               a) to avoid the silly window syndrome;
               b) to allow ACKs to piggyback on a reply frame if one is ready to go when the stack decides to do the ACK;
               c) to allow the stack to send one ACK for several frames, if those frames arrive within the delay period.

               a) 所謂的糊涂窗口綜合癥(別人都這樣翻譯的,似乎有點搞笑:D)
               b) 將ACK與將要發(fā)送的數(shù)據(jù)報文一起發(fā)送
               c) 一個ack確認(rèn)多個報文段,如果這幾個報文段在延遲時間內(nèi)到達

            2. 什么是Nagle algoritm ?
               簡而言之,nagle算法主要目的是減少網(wǎng)絡(luò)流量,當(dāng)你發(fā)送的數(shù)據(jù)包太小時,TCP并不立即發(fā)送該數(shù)據(jù)包,而是緩存起來直到數(shù)據(jù)包
               到達一定大小后才發(fā)送。(improving the efficiency of TCP/IP networks by reducing the number of packets that need to
               be sent over the network.)

               關(guān)于這個算法,我覺得wikipedia上講的比較好。具體點說,當(dāng)上層提交數(shù)據(jù)給TCP時,TCP覺得你的數(shù)據(jù)太小了(套用一般的例子,
               如果你要發(fā)送1一個字節(jié)的數(shù)據(jù),當(dāng)附加上TCP和IP頭后,數(shù)據(jù)包通常就會增加到41字節(jié),那么這顯然是低效的),就緩存你的數(shù)據(jù),
               當(dāng)數(shù)據(jù)緩存到一定長度后,如果之前發(fā)送的數(shù)據(jù)得到了ACK確認(rèn)且接收方有足夠空間容納數(shù)據(jù),就發(fā)送這些數(shù)據(jù),否則繼續(xù)等待。

               wikipedia上給了一段nagle的偽代碼:

            if there is new data to send
                 
            if the window size >= MSS and available data is >= MSS
                   send complete MSS segment now
                 
            else
                   
            if there is unconfirmed data still in the pipe
                     enqueue data 
            in the buffer until an acknowledge is received
                   
            else
                     send data immediately
                   end 
            if
                 end 
            if
               end 
            if 

               
               TCP socket提供了關(guān)閉nagle算法的接口,你可以通過TCP_NODELAY選項決定是否開啟該算法。不過MSDN上建議不要關(guān)閉此算法。如果
               你發(fā)送的數(shù)據(jù)不至于很小的話(<40byte),我也不建議你關(guān)閉。

            posted @ 2008-05-22 15:42 Kevin Lynx 閱讀(3141) | 評論 (1)編輯 收藏

            剖析Etwork網(wǎng)絡(luò)庫

            Author : Kevin Lynx

            從開始接觸網(wǎng)絡(luò)編程這個東西開始,我就不間斷地閱讀一些網(wǎng)絡(luò)庫(模塊)的源代碼,主要目的是為了獲取別
            人在這方面的經(jīng)驗,編程這東西,還是要多實踐啊。

            基本上,Etwork是一個很小巧的網(wǎng)絡(luò)庫。Etwork基于select模型,采用我之前說的技巧,理論上可以處理很
            多連接(先不說效率)。

            先看看下這個庫的結(jié)構(gòu):

            classgraph

            如同很多網(wǎng)絡(luò)庫一樣,總會有一個類似于ISocketManager的類,用于管理所有網(wǎng)絡(luò)連接(當(dāng)用戶服務(wù)器時)。
            而ISocket則用于代表一個網(wǎng)絡(luò)連接。在其他庫中,ISocketManager對應(yīng)的可能就是Server,而ISocket對應(yīng)
            的則是Session。

            在接口設(shè)計上,盡管Etwork寫了很多接口類(看看那些IClass),但是事實上它抽象得并不徹底。只是暴露給
            客戶端的代碼很簡潔,而庫本身依然臃腫。不知道為什么,現(xiàn)在我比較喜歡純C這種簡潔的東西,對于OO以及
            template,漸漸地有點心累。

            在功能實現(xiàn)上,我以TCP服務(wù)器為例,CreateEtwork根據(jù)傳來的參數(shù)建立服務(wù)器,在SocketManager::open中
            是很常規(guī)的socket, bind, listen。當(dāng)建立了服務(wù)器之后,需要在程序主循環(huán)里不斷地輪詢狀態(tài),這里主要
            調(diào)用poll函數(shù)完成。

            poll函數(shù)主體就是調(diào)用select。當(dāng)select成功返回活動的套接字?jǐn)?shù)量后,Etwork依次輪詢讀、寫、錯誤fdset,
            將保存的所有網(wǎng)絡(luò)連接(就是那些ISocket對象)對應(yīng)的套接字與fdset中當(dāng)前的套接字做比較。大致邏輯為:

            fd_count = select( 0, readset, writeset, exceptset, &timeout ); 

            for( each fd in readset )
                
            if( fd is listening fd ) 
                    accept 
            new connection
                
            else
                    
            for( each socket in all connections )
                        
            if( fd == socket )
                            can read data on 
            this socket 

            for( each fd in writeset )
             

            for( each fd in exceptset )
             


            沒什么特別讓人注意的地方(別覺得別人垃圾,耐心讀別人的代碼不是什么壞事)。每一次,當(dāng)Etwork檢測到
            新的連接時,會創(chuàng)建新的ISocket對象,并關(guān)聯(lián)對應(yīng)的套接字,然后保存此對象到一個列表中。當(dāng)poll結(jié)束
            后,客戶端程序通常會調(diào)用accept函數(shù)(Etwork中提供的接口),該函數(shù)主要是將poll中保存的新的ISocket
            對象全部拷貝出去。

            在接收、發(fā)送網(wǎng)絡(luò)數(shù)據(jù)上,Etwork如同幾乎所有的網(wǎng)絡(luò)庫(模塊)一樣,采用了緩沖機制。這里所說的緩沖機
            制是,網(wǎng)絡(luò)模塊接收到網(wǎng)絡(luò)數(shù)據(jù)時,將數(shù)據(jù)保存起來,客戶端程序想獲取數(shù)據(jù)時,實際上就是從這個緩沖中
            直接取,而不是從網(wǎng)絡(luò)上獲取;同理,發(fā)送數(shù)據(jù)時,客戶端程序?qū)?shù)據(jù)提供給網(wǎng)絡(luò)模塊,網(wǎng)絡(luò)模塊將數(shù)據(jù)保
            存起來,網(wǎng)絡(luò)模塊會在另一個時候發(fā)送這個緩沖中的數(shù)據(jù)(對于異步IO的處理畢竟不一樣)。

            Etwork關(guān)于這個緩沖機制的相關(guān)代碼,主要集中在Buffer這個類。與Buffer相關(guān)的是一個Message機制。Buffer
            維護了一個Message的隊列(deque)。一個Message實際上是一個非常簡單的結(jié)構(gòu)體:

             

            struct Message 
            {
                unsigned 
            short offset_;
                unsigned 
            short size_;
            }

             

            這其實是消息頭,在消息頭后全部是數(shù)據(jù)。在創(chuàng)建消息時(new_message),Etwork根據(jù)客戶端提供的數(shù)據(jù)創(chuàng)建
            足夠大的緩存保存:

             

            Message * m = (Message *)::operator new( size + sizeof( Message ) ); 

             

            這其實是一個很危險的做法,但是從Etwokr的源碼可以看出來,作者很喜歡玩弄這個技巧。與Buffer具體相
            關(guān)的接口包括:get_data, put_data, get_message, put_message。Buffer內(nèi)部維護的數(shù)據(jù)都是以Message
            的形式組織。但是,對于外部而言,卻依然是raw data,也就是諸如char*之類的數(shù)據(jù)。幾個相關(guān)函數(shù)大致
            上的操作為:獲取指定尺寸的消息(可能包含多個消息),將一段數(shù)據(jù)加入Buffer并以消息的形式組織(可能會
            創(chuàng)建多個消息),將一個消息以raw data的形式輸出,將raw data以一個消息的形式加入到Buffer。

            一般情況下,Etwork的poll操作,會將套接字上的數(shù)據(jù)接收并put_data到緩沖中;發(fā)送數(shù)據(jù)時則get_data。
            客戶端要從緩沖中獲取數(shù)據(jù)時,就調(diào)用get_message;發(fā)送數(shù)據(jù)時就put_message。

            Etwork中還有一個比較有趣的東西:marshaller。這個東西主要就是提供將C++中各種數(shù)據(jù)類型的變量進行字
            節(jié)編碼,也就是將int long struct之類的東西轉(zhuǎn)換為unsigned char,從而方便直接往網(wǎng)絡(luò)上發(fā)送。

            基本上,Buffer和marshaller可以說是一個網(wǎng)絡(luò)庫(模塊)的必要部件,你可以在不同的網(wǎng)絡(luò)庫中看到類似的
            東西。

            Etwork在網(wǎng)絡(luò)事件的處理上,除了上面的輪詢外,還支持回調(diào)機制。這主要是通過INotify,以及給各個ISocket
            注冊Notify對象實現(xiàn)。沒什么難度,基本上就是observer模式的簡單實現(xiàn)。

            其他東西就沒什么好說的了,縱觀一下,Etwork實現(xiàn)得還是比較典型的,可以作為開發(fā)網(wǎng)絡(luò)庫的一個簡單例子。

            posted @ 2008-05-21 21:06 Kevin Lynx 閱讀(7005) | 評論 (5)編輯 收藏

            突破select的FD_SETSIZE限制

            Author : Kevin Lynx 

            前言:

            在很多比較各種網(wǎng)絡(luò)模型的文章中,但凡提到select模型時,都會說select受限于輪詢的套接字?jǐn)?shù)量,這個
            數(shù)量也就是系統(tǒng)頭文件中定義的FD_SETSIZE值(例如64)。但事實上這個算不上真的限制。

            C語言的偏方:

            在C語言的世界里存在一個關(guān)于結(jié)構(gòu)體的偏門技巧,例如:

             

            typedef struct _str_type
            {
               
            int _len;
               
            char _s[1];
            }
            str_type;

             

            str_type用于保存字符串(我只是舉例,事實上這個結(jié)構(gòu)體沒什么用處),乍看上去str_type只能保存長度為
            1的字符串('\0')。但是,通過寫下如下的代碼,你將突破這個限制:

            int str_len = 5;
            str_type
            *s = (str_type*) malloc( sizeof( str_type ) + str_len - 1 );
            //
            free( s );


            這個技巧原理很簡單,因為_s恰好在結(jié)構(gòu)體尾部,所以可以為其分配一段連續(xù)的空間,只要注意指針的使用,
            這個就算不上代碼上的罪惡。但是這個技巧有個限制,str_type定義的變量必須是被分配在堆上,否則會破
            壞堆棧。另外,需要動態(tài)增長的成員需要位于結(jié)構(gòu)體的末尾。最后,一個忠告就是,這個是C語言里的技巧,
            如果你的結(jié)構(gòu)體包含了C++的東西,這個技巧將不再安全(<Inside the C++ object model>)。

            其實select也可以這樣做:

            事實上,因為select涉及到的fd_set是一個完全滿足上述要求的結(jié)構(gòu)體:

            winsock2.h :

            typedef
            struct fd_set {
                    u_int fd_count;              
            /* how many are SET? */
                    SOCKET  fd_array[FD_SETSIZE];  
            /* an array of SOCKETs */
            }
            fd_set;


            但是,如果使用了以上技巧來增加fd_array的數(shù)量(也就是保存的套接字?jǐn)?shù)量),那么關(guān)于fd_set的那些宏可
            能就無法使用了,例如FD_SET。

            winsock2.h :

            #define FD_SET(fd, set) do { \
                u_int __i; \
               
            for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
                   
            if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \
                       
            break; \
                    }
            \
                }
            \
               
            if (__i == ((fd_set FAR *)(set))->fd_count) { \
                   
            if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \
                        ((fd_set FAR
            *)(set))->fd_array[__i] = (fd); \
                        ((fd_set FAR
            *)(set))->fd_count++; \
                    }
            \
                }
            \
            }
            while(0)


            有點讓人眼花繚亂,我鼓勵你仔細看,其實很簡單。這里有個小技巧,就是他把這些代碼放到一個do...while(0)
            里,為什么要這樣做,我覺得應(yīng)該是防止名字污染,也就是防止那個__i變量與你的代碼相沖突??梢钥闯?,
            FD_SET會將fd_count與FD_SETSIZE相比較,這里主要是防止往fd_array的非法位置寫數(shù)據(jù)。

            因為這個宏原理不過如此,所以我們完全可以自己寫一個新的版本。例如:

            #define MY_FD_SET( fd, set, size ) do { \
                unsigned
            int i = 0; \
               
            for( i = 0; i < ((fd_set*) set)->fd_count; ++ i ) { \
                   
            if( ((fd_set*)set)->fd_array[i] == (fd) ) { \
                       
            break; \
                    }
            \
                }
            \
               
            if( i == ((fd_set*)set)->fd_count ) { \
                   
            if( ((fd_set*)set)->fd_count < (size) ) { \
                        ((fd_set
            *)set)->fd_array[i] = (fd); \
                        ((fd_set
            *)set)->fd_count ++; \
                    }
            \
                }
            \
            }
            while( 0 )


            沒什么變化,只是為FD_SET加入一個fd_array的長度參數(shù),宏體也只是將FD_SETSIZE換成這個長度參數(shù)。
            于是,現(xiàn)在你可以寫下這樣的代碼:

            unsigned int count = 100;
            fd_set
            *read_set = (fd_set*) malloc( sizeof( fd_set ) + sizeof(SOCKET) * (count - FD_SETSIZE ) );
            SOCKET s
            = socket( AF_INET, SOCK_STREAM, 0 );
            //
            MY_FD_SET( s, read_set, count );
            //
            free( read_set );
            closesocket( s );


            小提下select模型:

            這里我不會具體講select模型,我只稍微提一下。一個典型的select輪詢模型為:

            int r = select( 0, &read_set, 0, 0, &timeout );
            if( r < 0 )
            {
               
            // select error
            }
             

            if( r > 0 )
            {
               
            for( each sockets )
               
            {
                   
            if( FD_ISSET( now_socket, &read_set ) )
                   
            {
                       
            // this socket can read data
                    }

                }

            }
             


            輪詢write時也差不多。在Etwork(一個超小型的基本用于練習(xí)網(wǎng)絡(luò)編程的網(wǎng)絡(luò)庫,google yourself)中,作者
            的輪詢方式則有所不同:

            // read_set, write_set為采用了上文所述技巧的fd_set類型的指針
            int r = select( 0, read_set, write_set, 0, &timeout );
            //  error handling
            for( int i = 0; i < read_set->fd_count; ++ i )
            {
               
            // 輪詢所有socket,這里直接采用read_set->fd_array[i] == now_socket判斷,而不是FD_ISSET
            }
             

            for( int i = 0; i < write_set->fd_count; ++ i )
            {
               
            // 輪詢所有socket,檢查其whether can write,判斷方式同上
            }
             


            兩種方式的效率從代碼上看去似乎都差不多,關(guān)鍵在于,F(xiàn)D_ISSET干了什么?這個宏實際上使用了__WSAFDIsSet
            函數(shù),而__WSAFDIsSet做了什么則不知道。也許它會依賴于FD_SETSIZE宏,那么這在我們這里將是不安全的,
            所以相比之下,如果我們使用了這個突破FD_SETSIZE的偏方手段,那么也許第二種方式要好些。

            相關(guān)下載(5.21.2008)

            隨便寫了一個改進的select模型的echo服務(wù)器,放上源碼

            posted @ 2008-05-20 11:20 Kevin Lynx 閱讀(22398) | 評論 (12)編輯 收藏

            tcp要點學(xué)習(xí)-斷開連接

            Author : Kevin Lynx

            主要部分,四次握手:

            斷開連接其實從我的角度看不區(qū)分客戶端和服務(wù)器端,任何一方都可以調(diào)用close(or closesocket)之類
            的函數(shù)開始主動終止一個連接。這里先暫時說正常情況。當(dāng)調(diào)用close函數(shù)斷開一個連接時,主動斷開的
            一方發(fā)送FIN(finish報文給對方。有了之前的經(jīng)驗,我想你應(yīng)該明白我說的FIN報文時什么東西。也就是
            一個設(shè)置了FIN標(biāo)志位的報文段。FIN報文也可能附加用戶數(shù)據(jù),如果這一方還有數(shù)據(jù)要發(fā)送時,將數(shù)據(jù)附
            加到這個FIN報文時完全正常的。之后你會看到,這種附加報文還會有很多,例如ACK報文。我們所要把握
            的原則是,TCP肯定會力所能及地達到最大效率,所以你能夠想到的優(yōu)化方法,我想TCP都會想到。

            當(dāng)被動關(guān)閉的一方收到FIN報文時,它會發(fā)送ACK確認(rèn)報文(對于ACK這個東西你應(yīng)該很熟悉了)。這里有個
            東西要注意,因為TCP是雙工的,也就是說,你可以想象一對TCP連接上有兩條數(shù)據(jù)通路。當(dāng)發(fā)送FIN報文
            時,意思是說,發(fā)送FIN的一端就不能發(fā)送數(shù)據(jù),也就是關(guān)閉了其中一條數(shù)據(jù)通路。被動關(guān)閉的一端發(fā)送
            了ACK后,應(yīng)用層通常就會檢測到這個連接即將斷開,然后被動斷開的應(yīng)用層調(diào)用close關(guān)閉連接。

            我可以告訴你,一旦當(dāng)你調(diào)用close(or closesocket),這一端就會發(fā)送FIN報文。也就是說,現(xiàn)在被動
            關(guān)閉的一端也發(fā)送FIN給主動關(guān)閉端。有時候,被動關(guān)閉端會將ACK和FIN兩個報文合在一起發(fā)送。主動
            關(guān)閉端收到FIN后也發(fā)送ACK,然后整個連接關(guān)閉(事實上還沒完全關(guān)閉,只是關(guān)閉需要交換的報文發(fā)送
            完畢),四次握手完成。如你所見,因為被動關(guān)閉端可能會將ACK和FIN合到一起發(fā)送,所以這也算不上
            嚴(yán)格的四次握手---四個報文段。

            在前面的文章中,我一直沒提TCP的狀態(tài)轉(zhuǎn)換。在這里我還是在猶豫是不是該將那張四處通用的圖拿出來,
            不過,這里我只給出斷開連接時的狀態(tài)轉(zhuǎn)換圖,摘自<The TCP/IP Guide>:

            tcpclose

            給出一個正常關(guān)閉時的windump信息:

            14:00:38.819856 IP cd-zhangmin.1748 > 220.181.37.55.80: F 1:1(0) ack 1 win 65535
            14:00:38.863989 IP 220.181.37.55.80 > cd-zhangmin.1748: F 1:1(0) ack 2 win 2920
            14:00:38.864412 IP cd-zhangmin.1748 > 220.181.37.55.80: . ack 2 win 65535 

             

            補充細節(jié):

            關(guān)于以上的四次握手,我補充下細節(jié):
            1. 默認(rèn)情況下(不改變socket選項),當(dāng)你調(diào)用close( or closesocket,以下說close不再重復(fù))時,如果
            發(fā)送緩沖中還有數(shù)據(jù),TCP會繼續(xù)把數(shù)據(jù)發(fā)送完。
            2. 發(fā)送了FIN只是表示這端不能繼續(xù)發(fā)送數(shù)據(jù)(應(yīng)用層不能再調(diào)用send發(fā)送),但是還可以接收數(shù)據(jù)。
            3. 應(yīng)用層如何知道對端關(guān)閉?通常,在最簡單的阻塞模型中,當(dāng)你調(diào)用recv時,如果返回0,則表示對端
            關(guān)閉。在這個時候通常的做法就是也調(diào)用close,那么TCP層就發(fā)送FIN,繼續(xù)完成四次握手。如果你不調(diào)用
            close,那么對端就會處于FIN_WAIT_2狀態(tài),而本端則會處于CLOSE_WAIT狀態(tài)。這個可以寫代碼試試。
            4. 在很多時候,TCP連接的斷開都會由TCP層自動進行,例如你CTRL+C終止你的程序,TCP連接依然會正常關(guān)
            閉,你可以寫代碼試試。

            特別的TIME_WAIT狀態(tài):

            從以上TCP連接關(guān)閉的狀態(tài)轉(zhuǎn)換圖可以看出,主動關(guān)閉的一方在發(fā)送完對對方FIN報文的確認(rèn)(ACK)報文后,
            會進入TIME_WAIT狀態(tài)。TIME_WAIT狀態(tài)也稱為2MSL狀態(tài)。

            什么是2MSL?MSL即Maximum Segment Lifetime,也就是報文最大生存時間,引用<TCP/IP詳解>中的話:“
            它(MSL)是任何報文段被丟棄前在網(wǎng)絡(luò)內(nèi)的最長時間?!蹦敲?,2MSL也就是這個時間的2倍。其實我覺得沒
            必要把這個MSL的確切含義搞明白,你所需要明白的是,當(dāng)TCP連接完成四個報文段的交換時,主動關(guān)閉的
            一方將繼續(xù)等待一定時間(2-4分鐘),即使兩端的應(yīng)用程序結(jié)束。你可以寫代碼試試,然后用netstat查看下。

            為什么需要2MSL?根據(jù)<TCP/IP詳解>和<The TCP/IP Guide>中的說法,有兩個原因:
            其一,保證發(fā)送的ACK會成功發(fā)送到對方,如何保證?我覺得可能是通過超時計時器發(fā)送。這個就很難用
            代碼演示了。
            其二,報文可能會被混淆,意思是說,其他時候的連接可能會被當(dāng)作本次的連接。直接引用<The TCP/IP Guide>
            的說法:The second is to provide a “buffering period” between the end of this connection
            and any subsequent ones. If not for this period, it is possible that packets from different
            connections could be mixed, creating confusion.

            TIME_WAIT狀態(tài)所帶來的影響:

            當(dāng)某個連接的一端處于TIME_WAIT狀態(tài)時,該連接將不能再被使用。事實上,對于我們比較有現(xiàn)實意義的
            是,這個端口將不能再被使用。某個端口處于TIME_WAIT狀態(tài)(其實應(yīng)該是這個連接)時,這意味著這個TCP
            連接并沒有斷開(完全斷開),那么,如果你bind這個端口,就會失敗。

            對于服務(wù)器而言,如果服務(wù)器突然crash掉了,那么它將無法再2MSL內(nèi)重新啟動,因為bind會失敗。解決這
            個問題的一個方法就是設(shè)置socket的SO_REUSEADDR選項。這個選項意味著你可以重用一個地址。

            對于TIME_WAIT的插曲:

            當(dāng)建立一個TCP連接時,服務(wù)器端會繼續(xù)用原有端口監(jiān)聽,同時用這個端口與客戶端通信。而客戶端默認(rèn)情況
            下會使用一個隨機端口與服務(wù)器端的監(jiān)聽端口通信。有時候,為了服務(wù)器端的安全性,我們需要對客戶端進行
            驗證,即限定某個IP某個特定端口的客戶端。客戶端可以使用bind來使用特定的端口。

            對于服務(wù)器端,當(dāng)設(shè)置了SO_REUSEADDR選項時,它可以在2MSL內(nèi)啟動并listen成功。但是對于客戶端,當(dāng)使
            用bind并設(shè)置SO_REUSEADDR時,如果在2MSL內(nèi)啟動,雖然bind會成功,但是在windows平臺上connect會失敗。
            而在linux上則不存在這個問題。(我的實驗平臺:winxp, ubuntu7.10)

            要解決windows平臺的這個問題,可以設(shè)置SO_LINGER選項。SO_LINGER選項決定調(diào)用close時,TCP的行為。
            SO_LINGER涉及到linger結(jié)構(gòu)體,如果設(shè)置結(jié)構(gòu)體中l(wèi)_onoff為非0,l_linger為0,那么調(diào)用close時TCP連接
            會立刻斷開,TCP不會將發(fā)送緩沖中未發(fā)送的數(shù)據(jù)發(fā)送,而是立即發(fā)送一個RST報文給對方,這個時候TCP連
            接就不會進入TIME_WAIT狀態(tài)。

            如你所見,這樣做雖然解決了問題,但是并不安全。通過以上方式設(shè)置SO_LINGER狀態(tài),等同于設(shè)置SO_DONTLINGER
            狀態(tài)。

            斷開連接時的意外:
            這個算不上斷開連接時的意外,當(dāng)TCP連接發(fā)生一些物理上的意外情況時,例如網(wǎng)線斷開,linux上的TCP實現(xiàn)
            會依然認(rèn)為該連接有效,而windows則會在一定時間后返回錯誤信息。

            這似乎可以通過設(shè)置SO_KEEPALIVE選項來解決,不過不知道這個選項是否對于所有平臺都有效。

            總結(jié):

            個人感覺,越寫越爛。接下來會講到TCP的數(shù)據(jù)發(fā)送,這會涉及到滑動窗口各種定時器之類的東西。我真誠
            希望各位能夠多提意見。對于TCP連接的斷開,我們只要清楚:
            1. 在默認(rèn)情況下,調(diào)用close時TCP會繼續(xù)將數(shù)據(jù)發(fā)送完畢;
            2. TIME_WAIT狀態(tài)會導(dǎo)致的問題;
            3. 連接意外斷開時可能會出現(xiàn)的問題。
            4. maybe more...

            posted @ 2008-05-14 15:46 Kevin Lynx 閱讀(5219) | 評論 (2)編輯 收藏

            學(xué)生時代做的東西-留個紀(jì)念

            可能我這個人比較懷舊,對什么東西都想做個記錄,方便日后回憶??赡芎芏嗾J(rèn)識我的朋友都是通過GameRes那個作品專

            區(qū)。我對于當(dāng)年那種瘋狂編程的干勁很是自豪,現(xiàn)在差了很多,以前幫別人做小學(xué)生系列游戲外包的時候,可以12小時出

            個弱智的小游戲,那些日子一度被我稱為’12小時編程挑戰(zhàn)賽‘,只是自己跟自己比賽。

             

            每一次發(fā)布在GameRes(排除早期的那些垃圾玩意),在寫簡介時我都要把自己開發(fā)用的時間寫上,可是脾氣好的sea_bug

            每次都給我刪掉了。我自己匯總一下:

             

            1. 最讓我自豪的一個游戲引擎,耗盡了我當(dāng)時所有的設(shè)計能力。我努力把它做得很具擴展性,可是忽略了功能性?,F(xiàn)在基本不維護了,可能是用戶群太少了。我想我還是沒做好吧:

            edge2d google code page

            托sea_bug的忙搞了個論壇,冷清得讓我心寒:http://bbs.gameres.com/showforum.asp?forumid=91

             

            2. PacShooter3d:

            http://data.gameres.com/showmessage.asp?TopicID=90655

            不知道怎么的被人放到一個網(wǎng)站上了:http://noyes.cn/Software.Asp?id=9667

            源代碼下載。

             

            3. Space Demon demo

            當(dāng)初看到dophi寫的俄羅斯方塊營造的那種感覺覺得很不錯,于是決定認(rèn)真地做個游戲出來。結(jié)果后來做的東西讓我很失望。這是一個在代碼上過度設(shè)計的東西。我雖然對這個游戲不滿意,但是我對代碼還基本滿意。后來這個游戲的代碼被我游戲?qū)W院的一個朋友拿給金山的一個主程(在他們學(xué)校教書?)看,還得到了表揚。;D

            這個游戲我是直接開源了的:http://www.gameres.com/showmessage.asp?TopicID=73123

             

            4. Crazy Eggs Clone

            <Crazy Eggs>是小林子他們工作室做的東西,屬于casual games,拿到國外去賣的。我當(dāng)時也覺得casual games市場不錯,還找了個美工,大談特談,吹噓了很多,最終在寫策劃案的時候失敗了。我當(dāng)時心也懶了,最終失敗。

            同樣是在GameRes上:http://www.gameres.com/showmessage.asp?TopicID=72351

            源代碼下載。

            后來我為了宣傳edge2d,特地把這個游戲移植到我的引擎上。我從來很自豪自己代碼的模塊性,所以移植起來很容易。除了edge2d版本,我還做了HGE版本,不過HGE版本是做給別人的外包:

            edge2d版本下載

             

            5. Brick Shooter Jr

            這個游戲也是我翻版別人的,用的別人的美術(shù)+音樂資源,自己重寫代碼。后來網(wǎng)上有個人又用我的資源翻作了個,做的比我好。

            http://data.gameres.com/showmessage.asp?TopicID=65654

            源代碼下載

            6. Feeding Frenzy

            Popcap的經(jīng)典游戲,我做的垃圾東西,不提其他的了:

            http://data.gameres.com/showmessage.asp?TopicID=62796

            源代碼下載

             

            7.是男人就下一百層

            超級古老的東西,這個東西當(dāng)初還和上海一家廣告公司合作過。我簽署了長這么大的第一份合同,結(jié)果后來一分錢沒撈到。他們公司現(xiàn)在也不做這個了。和我合作的產(chǎn)品經(jīng)理現(xiàn)在貌似在搞棋牌。

            http://data.gameres.com/showmessage.asp?TopicID=54475

            源代碼下載

            8. 所謂的雷電,一個我最早做的東西,現(xiàn)在你開baidu搜索 kevin lynx,出來最多的鏈接就是<雷電kevinlynx版>,別信那

            些,全是流氓軟件。

            http://data.gameres.com/showmessage.asp?TopicID=54474

            源代碼下載

             

            其他還給別人做了一些外包,在此特別感謝哆啦G夢老大,給我找了很多工作。他這個人四處跳巢,還給我說了幾次工作。

            只是我還想暫時留在成都,所以都拒絕了。那些外包做的都比較垃圾,做到后來基本有個小游戲框架了。版權(quán)問題可能不

            能發(fā)布出來吧。

            posted @ 2008-05-14 09:23 Kevin Lynx 閱讀(5814) | 評論 (16)編輯 收藏

            tcp要點學(xué)習(xí)-建立連接

            Author : Kevin Lynx

            準(zhǔn)備:

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

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

            關(guān)于WinDump的具體用法你可以從網(wǎng)上其他地方獲取,這里我只稍微提一下。要讓W(xué)inDump開始監(jiān)聽數(shù)據(jù),
            首先需要確定讓其監(jiān)聽哪一個網(wǎng)絡(luò)設(shè)備(或者說是網(wǎng)絡(luò)接口)。你可以:

             

            windump -D

             

            獲取當(dāng)前機器上的網(wǎng)絡(luò)接口。然后使用:

             

            windump -i 2 

             

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

             

            windump -i 2 port 4000 

             

            那么windump只會顯示端口號為4000的網(wǎng)絡(luò)數(shù)據(jù)。

            序號和確認(rèn)號:

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

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

            }
            tcp_header;


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

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

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

            開始三次握手:

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

            三次握手開始于客戶端試圖連接服務(wù)器端。當(dāng)你調(diào)用諸如connect的函數(shù)時,正常情況下就會開始三次握手。
            隨便在網(wǎng)上找張三次握手的圖:

            connection

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

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

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

               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的結(jié)果,建議參看<tcp/ip詳解>中對于tcpdump的描述。

            建立連接的附加信息:

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

            連接的意外:

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

             

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

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

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

             

            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報文給我,但是我這邊根本不在乎這個報文,繼續(xù)發(fā)送SYN報文。
            三次過后connect就返回了。(數(shù)據(jù)反映的事實是這樣)

            關(guān)于listen:

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

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

            同時不做任何回復(fù)。

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

            posted @ 2008-05-11 01:03 Kevin Lynx 閱讀(3704) | 評論 (10)編輯 收藏

            tcp要點學(xué)習(xí)-基礎(chǔ)概念

            Author : Kevin Lynx 

            TCP是TCP/IP協(xié)議簇中傳輸層上的一種網(wǎng)絡(luò)協(xié)議,它是一種面向連接的、可靠的協(xié)議。為了提供這種可靠性,
            TCP實現(xiàn)了各種有效的機制、算法。為了從一種宏觀的角度去了解這個協(xié)議,這里先大致地提一下與之相關(guān)
            的概念。

            1. 什么是‘面向連接的’?
               引用<TCP/IP協(xié)議詳解>中的概念:
               面向連接意味著兩個使用TCP的應(yīng)用(通常是一個客戶和一個服務(wù)器)在彼此交換數(shù)據(jù)之前必須先建立
               一個TCP連接。

            2. 什么是‘三次握手’?
               在建立TCP連接之前,兩個使用TCP的應(yīng)用需要交換三次網(wǎng)絡(luò)數(shù)據(jù)。這三個數(shù)據(jù)包的來往也就是所謂的‘
               三次握手’。

            3. 報文段segment
               我們說TCP是流式的網(wǎng)絡(luò)協(xié)議,那是因為,應(yīng)用程序可以一直往TCP寫數(shù)據(jù),無論你是逐byte,還是write
               a chunk,TCP對應(yīng)用傳給它的數(shù)據(jù)進行緩沖,直到緩沖數(shù)據(jù)達到一定尺寸才發(fā)送。可以看出,對于應(yīng)用
               而言,TCP就像是stream的。但事實上,在TCP層,數(shù)據(jù)還是以塊為單位的。這個塊也就是所謂的報文段
               segment。

            4. 什么是MTU?
               MTU即最大傳輸單元(Maximum Transmission Unit,MTU)是指一種通信協(xié)議的某一層上面所能通過的
               大數(shù)據(jù)報大?。ㄒ宰止?jié)為單位)。我個人目前的理解認(rèn)為,MTU是一個網(wǎng)絡(luò)在硬件層次上所允許的最大
               數(shù)據(jù)包大小,例如以太網(wǎng)大概是1500字節(jié)。

            5. 什么是MSS?
               MSS即最大報文段大小(Maximum Segment Size),它是指TCP中一個報文段上附加的用戶數(shù)據(jù)的最大大小。
               這里稍微說下應(yīng)用層發(fā)送某個數(shù)據(jù)包時整個TCP/IP協(xié)議棧的操作過程:應(yīng)用層將自己的用戶數(shù)據(jù)傳給TCP
               層(傳輸層),TCP在這些數(shù)據(jù)前添加自己的協(xié)議頭(簡單地理解為附加一些數(shù)據(jù)),然后將數(shù)據(jù)交給
               IP層(網(wǎng)絡(luò)層),IP層附加自己的協(xié)議頭,以此類推。
               雖然MSS意思是最大報文段大小,但事實上它是排除了協(xié)議頭的用戶數(shù)據(jù)。

            6. MTU and MSS ?
               可以簡單地給你一個這樣的公示:mss = mtu - tcp_header_size - ip_header_size。
               而通常,IP協(xié)議附加的協(xié)議頭大小和TCP的協(xié)議頭大小都是20字節(jié),所以通常的MSS為1460字節(jié)。
               注意,這里說的數(shù)字并不見得正確,因為MSS是可以被協(xié)商的。各種協(xié)議頭也可能被添加附加數(shù)據(jù),但是
               他們的關(guān)系是這樣的。

            7. 什么是窗口大小?
               找本TCP的書看下TCP數(shù)據(jù)包的包頭(本文多次使用數(shù)據(jù)包、報文的概念,我這里說的都是一樣的),你會
               發(fā)現(xiàn)那個16位的窗口大小。
               窗口這個域?qū)τ谡麄€TCP協(xié)議都很重要。簡單地說,窗口大小是指接收端的接收緩存的大小。上面說了,應(yīng)用
               在發(fā)數(shù)據(jù)的時候,TCP會緩存這些數(shù)據(jù),稍后發(fā)送。接收數(shù)據(jù)時也一樣,TCP接收數(shù)據(jù)并緩存起來,直到應(yīng)用
               調(diào)用recv之類的函數(shù)取數(shù)據(jù)時,TCP才將這些緩存數(shù)據(jù)清除。

               TCP發(fā)送端會根據(jù)TCP接收端那個接收緩存大小決定發(fā)送多少數(shù)據(jù)(如何知道這個緩存大?。可院蠼o概念)。
               這樣,TCP接收端的接收緩存才不至于緩沖溢出。

            8. 提供可靠性的方法之一:ACK確認(rèn)?
               這里還不敢提序號、確認(rèn)號、延時ACK等亂七八糟的東西。我只能告訴你,當(dāng)TCP發(fā)送某些數(shù)據(jù)給TCP接收方
               時,TCP接收方會發(fā)回一個確認(rèn)報文。TCP發(fā)送方收到這個確認(rèn)報文后,就可以確認(rèn)剛才發(fā)送的數(shù)據(jù)包成功到達。

               為什么這個確認(rèn)報文叫ACK確認(rèn)(貌似是我臨時給的概念:D)?再翻到TCP包頭結(jié)構(gòu)那張圖,ACK是TCP包頭中
               的1bit標(biāo)志位,如同SYN、PSH、RST之類的標(biāo)志一樣,這些標(biāo)志都有一個專有的用途。當(dāng)ACK標(biāo)志位被設(shè)置為1
               時,我就稱其為ACK確認(rèn)標(biāo)志,因為ACK就是用于確認(rèn)報文段的。

               在上面所說的窗口大小中,我提到,發(fā)送方如何知道接收方的接收緩存大小呢?這也是通過確認(rèn)報文段實現(xiàn):
               當(dāng)接收方接收到數(shù)據(jù)后,發(fā)送ACK確認(rèn)數(shù)據(jù)包給發(fā)送方,就設(shè)置包頭中的窗口域。

            9. 提供可靠性的方法之二:各種定時器
               TCP中會設(shè)置很多計時器,這些定時器大多用于超時重傳(老半天得不到回應(yīng),所以重傳數(shù)據(jù))。

            10.什么是全雙工?
               全雙工就是你可以同時在一個TCP連接上進行數(shù)據(jù)的發(fā)送和接收。這種雙工特性也促使了關(guān)閉TCP連接時的四次
               握手。

            11.TODO : more concepts...


            這里我盡量簡單地介紹一些TCP中的概念,希望可以讓你有概括性的了解。預(yù)計下一節(jié)我會講講建立TCP連接的相關(guān)細節(jié)。
            除了Stevens的<TCP/IP詳解>,我推薦<The TCP/IP Guide>,據(jù)說是另一部TCP的權(quán)威之作。

            posted @ 2008-05-09 16:30 Kevin Lynx 閱讀(2920) | 評論 (7)編輯 收藏

            lua和python誰更適用于嵌入MMORPG?

             

            預(yù)計新項目會選擇lua或python之一作為游戲的腳本語言。以前草草地接觸過這兩門語言,對于語法,以及嵌入進C/C++程序都有點感性上的認(rèn)識。可能是受《UNIX編程藝術(shù)》中KISS原則的影響,現(xiàn)在總喜歡簡潔的東西。所以我個人比較偏向于使用lua。

             

            這兩天翻了下網(wǎng)絡(luò)上的資料,在lua的wiki上看到一篇比較lua和python的文章,草草地翻譯出要點:

            Python:
            1. 擴展庫很多,資料很多
            2. 數(shù)值計算比較強大,支持多維數(shù)組,而lua沒有數(shù)組類型
            3. 本身帶的c類型(?)支持處理動態(tài)鏈接庫,不需要進行C封裝(C擴展)
            4. 遠程調(diào)試器,似乎lua擴展工具支持
            5. 自然語言似的語法
            6. 對于string和list的支持,lua可以通過擴展庫實現(xiàn)
            7. 對unicode的支持
            8. 空格敏感(代碼不忽略空格),這其實可以使python的代碼風(fēng)格看起來更好一點
            9. 內(nèi)建位操作,lua可以通過擴展庫支持
            10.語言本身對錯誤的處理要好些,可以有效減少程序錯誤
            11.初級文檔比lua多
            12.對面向?qū)ο笾С指?

            Lua:
            1. 比python小巧很多(包括編譯出來的運行時庫)
            2. 占用更小的內(nèi)存
            3. 解釋器速度更快
            4. 比python更容易集成到C語言中
            5. 對于對象不使用引用計數(shù)(引用計數(shù)會導(dǎo)致更多的問題?)
            6. lua早期定位于一種配置語言(作為配置文件),因此比起python來更容易配置數(shù)據(jù)
            7. 語言更漂亮(nice)、簡單(simple)、強大(powerful)。
            8. lua支持多線程,每個線程可以配置獨立的解釋器,因此lua更適合于集成進多線程程序
            9. 對空格不敏感,不用擔(dān)心編輯器會將tab替換成空格

            Useful Comments:
            1. Everything is an object allocated on the heap in Python, including numbers. (So 123+456 creates a new heap object).
            2. lua對于coroutine的支持更適用于嵌入進游戲,雖然python也有,但是并沒有包含進核心模塊

            3.Python was a language better suited to Game AI

             

            本來想去找點對于python的正面資料(嵌入進游戲這方面),但是居然沒找到。客觀地說如果單獨用python做應(yīng)用,python還是很有優(yōu)勢?,F(xiàn)在心意已決,應(yīng)該向leader推薦lua。

             

            ps,希望能補充以上兩種語言的特點。

            posted @ 2008-05-06 17:37 Kevin Lynx 閱讀(8564) | 評論 (15)編輯 收藏

            僅列出標(biāo)題
            共12頁: First 4 5 6 7 8 9 10 11 12 
            久久久久亚洲av成人无码电影 | 久久精品免费一区二区| 中文字幕无码av激情不卡久久| 亚洲熟妇无码另类久久久| 久久精品国产亚洲AV无码麻豆| 日本久久久精品中文字幕| 欧美亚洲日本久久精品| 久久99精品久久久久久hb无码 | 国产99久久久国产精品小说| 国产亚洲欧美成人久久片| 亚洲?V乱码久久精品蜜桃| 香港aa三级久久三级| 亚洲综合精品香蕉久久网| 久久久精品日本一区二区三区| 蜜臀av性久久久久蜜臀aⅴ| 午夜精品久久影院蜜桃 | 久久久久久久波多野结衣高潮| 91精品国产91久久久久久青草 | 久久久精品波多野结衣| 久久精品国产精品亚洲精品| 久久99热这里只有精品国产| 欧美午夜A∨大片久久| 一本大道加勒比久久综合| 久久久久久久久久久久中文字幕| 2021国产精品久久精品| 久久久久亚洲AV无码去区首| 91麻精品国产91久久久久| 伊人久久免费视频| 国产精品成人久久久久三级午夜电影| 97久久综合精品久久久综合| 人妻无码αv中文字幕久久| 性欧美丰满熟妇XXXX性久久久| 久久久久国产精品人妻| 国产精品乱码久久久久久软件| 久久91精品国产91久| 亚洲色婷婷综合久久| 漂亮人妻被黑人久久精品| 久久国产乱子伦免费精品| 久久最新精品国产| 久久久久国产精品三级网| 久久国产AVJUST麻豆|