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

            依舊的博客

            技術學習

            C++博客 首頁 新隨筆 聯系 聚合 管理
              17 Posts :: 1 Stories :: 2 Comments :: 0 Trackbacks

            1. 客戶-服務器通信中的基本問題

            客戶和服務器通信是為了使用服務,為此在傳輸機制的基礎上設計協議,通過對通信行為的規范,實現通信的目的,并解決傳輸中的問題。

            傳輸機制通常由下層協議提供,根據不同的通信需要選擇不同的下層協議,這是一個基本的問題。對應用協議來說,可用的傳輸機制有可靠連接的字節流類型和不可靠無連接的數據報類型。

            服務器處理大量客戶的請求,從而并發是服務器的一個基本問題,如何處理這個問題也取決于通信需要。處理方式上,服務器可以是循環的或并發的,并發服務器有多種實現方式(異步I/O,多線程和多進程)。

            一件事情能無重復地連續進行,通常會獲得更好的效率,這要求主體始終知道當前的狀態。一次通信過程的連續性取決于通信雙方,它們都要知道通信進行的狀態。這對客戶一般不成問題,但服務器要和大量客戶通信,不一定能為每個客戶的每次通信保存狀態。如果服務器是有狀態的,那么就更快地計算響應,減少通信的數據量。但是傳輸和客戶的故障使有狀態服務器面臨很大問題,當傳輸不可靠(報文重復,丟失,亂序)時,服務器維護的狀態會和客戶失去一致,一個不斷崩潰重啟的客戶會造成狀態信息不能發揮作用,而維護開銷卻極大增加。

            這就提出了客戶-服務器通信中的三個基本問題,它們的解決方案都取決于實際需要,客戶-服務器通信中有哪些情況的需要呢?

            • 是否要求可靠傳輸;
            • 是否需要服務器進行大量處理。對循環服務器進行分析可以知道,需要大量處理的通信用循環方案可能會丟失請求,用并發方案還可以提高服務器資源利用率,改善性能,只要少量處理的通信則無法忍受開銷大的解決方案。
            • 在局域網還是互聯網環境下,局域網中傳輸很少出錯,互聯網環境則不然;


            通常根據前兩個基本問題把服務器實現分為四種類型,它們的適用范圍如下:

            • 循環無連接服務器,少量處理的通信時,并且在局域網中或不要求可靠傳輸。這種做法主要是為了避免開銷。
            • 循環連接服務器,較少用,主要是循環的方式不夠高效,因為連接有一定開銷,響應時間可能不低。在少量處理并要求可靠性的情況下使用。
            • 并發無連接服務器,很少用,因為要給每個請求開線程,開銷太大。在不要求可靠性的情況下,如果線程開銷遠小于計算響應開銷,或者并發可以讓各請求的I/O并行,或者循環方案會丟失請求時可以考慮。
            • 并發連接服務器,常用。


            2. winsock基本函數的使用

            winsock的基本函數有WSAStartup(),WSACleanup(),socket(),closesocket(),bind(),listen(),accept(), connect(),send()和recv()。

            使用這些函數,客戶端的大概算法是,

            1.  調用WSAStartup()初始化winsock庫。
            2.  調用socket()創建套接字,返回套接字描述符s。
            3.  指定遠程套接字地址sa,對s調用connect(),向sa標識的服務進程請求連接。
            4.  連接成功后,對s調用send()發送請求,調用recv()接收響應,如此反復直到完成任務。
            5.  對s調用closesocket()關閉連接。
            6.  不再發起連接處理新的任務時,調用WSACleanup()釋放winsock庫。

            服務器端的大概算法是,

            1. 調用WSAStartup()初始化winsock庫。
            2. 調用socket()創建套接字,返回套接字描述符s。
            3. 對s調用bind(),將其綁定到本地的套接字sa。
            4. 調用listen(),將s置為被動模式。此時開始偵聽客戶端的連接請求,將其放入一個隊列。
            5. 對s調用accept(),即從請求隊列中取出一項,接受該連接后返回一個新的套接字描述符s',以及對應客戶端的套接字地址sa'。
            6. 對s'調用recv()接收請求,調用send()發送響應,如此反復直到完成任務。
            7. 對s'調用closesocket()關閉該連接。
            8. 重復5到7的過程。
            9. 從8退出后,調用WSACleanup()釋放winsock庫。

            有以下幾點需要進一步說明,

            1). 客戶端調用connect()和服務器端調用accept()成功后將在客戶進程和服務器進程之間建立一個TCP連接。連接兩端的每個套接字描述符都包含一個本地端點地址和一個遠程端點地址。所以在使用連接套接字發送數據時不用指示目的地址。

            2). 多宿主主機的IP地址選擇問題。從上面的算法容易提出這樣的問題,為什么客戶端在使用套接字時不綁定端點地址?通常的主機只有一個IP,但是多宿主主機有多個IP地址,在這種情況下,客戶端為套接字指定的IP可能與實際發送時經過的IP不符,所以允許客戶端不指定套接字地址,而由TCP/IP軟件在實際發送時指定IP,同時選擇一個未用過的端口號,這正是在connect()調用中完成的。那么服務器端就不存在同樣的情況嗎?不是,在它調用bind()時指定一個套接字地址,其端口部分采用應用協議的熟知端口,而IP地址部分有著同樣的問題。為此定義了一個代表統配地址的常量INADDR_ANY,用它指示IP地址部分。實際使用的IP仍然是由TCP/IP軟件分配。

            3). TCP為一個連接的發送端和接收端各維護一個緩沖區。當發送端緩沖區滿的時候,send()調用會阻塞,在接收端緩沖區為空的時候,recv()調用會阻塞。為什么要在通信進程和TCP連接之間維護一個間接層呢?可能是為了在一端有多個進程要使用信道的情況下,在多個進程之間進行信道分配的協調。比如在發送端,信道傳輸數據時send()調用可以繼續執行,多個進程的send()調用同緩沖區打交道,彼此影響不大,因為讀寫緩沖區速度很快,而信道同緩沖區打交道,這時可以對各進程的發送數據進行協調,實現公平的信道分配。另外,在TCP中有滑動窗口概念,是用于流量控制的,前述緩沖區和滑動窗口有什么關系?我現在不太清楚。

            4). 套接字的關閉問題。在客戶機和服務器通過TCP連接完成數據交換后,有一個安全關閉的問題。一方面,服務器不能關閉連接,因為客戶機可能還有請求,另一方面,客戶機雖然知道何時不再請求,但是它不知道服務器的響應何時發送完,因為有些應用協議的響應數據量不確定。為此采用部分關閉的辦法,雖然連接是雙向的,但是允許在一個方向上關閉它,當客戶端不再請求時,可以部分關閉連接,使服務器收到一個信號,如果響應發送完了,服務器就可以關閉連接,此時連接被完全關閉

            3. 套接字接口中的端點地址

            端點地址用來表示通信的進程,是傳輸層協議及其套接字接口中的重要概念。不同的協議族可以用不同方式表示端點地址,一個協議族還可以有多個地址族,每個地址族的地址格式不同。TCP/IP只有一個地址族,它的端點地址包括一個32位IP地址和一個16位端口號。在協議族和地址族的基礎上,套接字接口用更為具體的結構來表示端點地址。

            套接字是一種適用于多個協議族的接口,并允許一個協議族使用多個地址族。TCP/IP協議族及其唯一地址族的標識分別是PF_INET和AF_INET。由于套接字接口的通用性,它提供一個通用的地址結構,其格式為(地址族,該族中的套接字地址)。套接字作為一個接口標準,可以有不同實現,以下我們只討論windows套接字。

            如下定義的sockaddr實現了前述通用地址結構,

            // winsock2.h

            struct sockaddr {
                    u_short sa_family;              /* address family */
                    char    sa_data[14];            /* up to 14 bytes of direct address */
            };

            sockaddr的通用性是相對的,某些地址族不適合這個結構。

            盡管sockaddr適合于TCP/IP協議族,但是winsock還定義了TCP/IP專用的地址格式,

            // winsock2.h

            struct sockaddr_in {
                    short   sin_family;
                    u_short sin_port;
                    struct  in_addr sin_addr;
                    char    sin_zero[8];
            };

            sin_family域取值恒為AF_INET。其中的in_addr結構表示IP地址,定義如下,

            //winsock2.h

            struct in_addr {
                    union {
                            struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                            struct { u_short s_w1,s_w2; } S_un_w;
                            u_long S_addr;
                    } S_un;
            #define s_addr  S_un.S_addr
                                            /* can be used for most TCP & IP code */
            #define s_host  S_un.S_un_b.s_b2
                                            /* host on imp */
            #define s_net   S_un.S_un_b.s_b1
                                            /* network */
            #define s_imp   S_un.S_un_w.s_w2
                                            /* imp */
            #define s_impno S_un.S_un_b.s_b4
                                            /* imp # */
            #define s_lh    S_un.S_un_b.s_b3
                                            /* logical host */
            };

            為了保證軟件的可移植性與可維護性,訪問TCP/IP的代碼不應使用sockaddr。只使用TCP/IP的應用程序可以只使用sockaddr_in,而永遠不用sockaddr。

            4. winsock程序實例

            《vc6技術內幕》的例程ex34a包括一個web服務器和三個客戶,服務器用winsock實現,一個客戶用winsock,另兩個用wininet。我們以winsock實現的服務器和客戶為例。

            CBlockingSocket對各接口函數進行封裝,使它們的調用可以統一報錯。把錯誤檢查和函數調用一起封裝可以避免每次調用這些函數時都檢錯。為統一報錯,采用了異常機制,在檢出錯誤后拋出異常,然后統一進行異常處理。異常機制使我們可以把錯誤檢查和錯誤處理分開,檢查必須是分散的,但是處理可以適當集中,使代碼簡化。

            CHttpBlockingSocket根據接收http報文的特點對CBlockingSocket進行了擴展。成員函數ReadHttpHeaderLine()可以從TCP連接中按行接收字符(它引入了一個緩沖區,緩沖區中收到的字符構成一行后再輸出。該緩沖區的長度需要能夠容納每一行字符,溢出時報錯。接收輸出行的緩沖區也可能長度不足,這時接收的數據不足一行,在調用ReadHttpHeaderLine()時要注意這一點),ReadHttpResponse()用于接收首部行之后所有的響應,當調用者提供的緩沖區不足時會報錯。緩沖區不足的情況都是在CBlockingSocket::Receive()函數中檢測到的,該函數調用以上層次中的代碼按照正常情況編寫。

            CSockAddr是一個與sockaddr_in同樣用途的類,但是用法更方便。winsock函數使用的端點地址結構是sockaddr,sockaddr_in本身用來代替它,所以CSockAddr需要能夠替代sockaddr。sockaddr可能用在傳值或傳址參數中,CSockAddr必須在邏輯上和存儲上都和sockaddr有等價性,并實現有關強制類型轉換。CSockAddr還實現了和sockaddr, sockaddr_in互相轉換的成員函數,因為一種結構很難在所有情況下都好用,新結構也需要和舊結構保持兼容。

             

            本例中采用服務器關閉套接字的辦法,因為每次連接只處理一個請求。

            參考:

            《用TCP/IP進行網際互聯第三卷(windows套接字版)》/清華出版社

            《vc6技術內幕 5th ed》/希望電子出版社

            posted on 2006-05-02 12:46 依舊的博客 閱讀(2272) 評論(0)  編輯 收藏 引用 所屬分類: 編程
            婷婷久久综合九色综合98| 午夜欧美精品久久久久久久| 精品久久久久中文字幕日本| 久久不射电影网| 久久国产成人午夜AV影院| 国产午夜电影久久| 一日本道伊人久久综合影| 久久无码AV中文出轨人妻| 久久超碰97人人做人人爱| 久久久久久毛片免费看| 伊人色综合久久天天人手人婷| 国产V亚洲V天堂无码久久久| 久久影院午夜理论片无码 | 97超级碰碰碰久久久久| 国产精品gz久久久| 久久亚洲日韩精品一区二区三区| 国产精品免费久久久久影院 | 久久香蕉国产线看观看精品yw| 欧美精品一区二区精品久久 | 国产精品va久久久久久久| 久久精品蜜芽亚洲国产AV| 亚洲精品99久久久久中文字幕| 久久久精品午夜免费不卡| 精品无码久久久久国产动漫3d | 久久夜色精品国产噜噜亚洲AV| 久久se精品一区二区影院| 热久久这里只有精品| 天天躁日日躁狠狠久久| 亚洲伊人久久综合中文成人网| 国产日韩欧美久久| 久久综合久久久| 久久噜噜电影你懂的| 国产成人精品白浆久久69| 久久久久久国产精品无码超碰 | 久久美女人爽女人爽| 国产精品久久久久久影院| 成人国内精品久久久久影院| 久久久av波多野一区二区| 97久久久精品综合88久久| 99久久精品影院老鸭窝| 久久免费国产精品一区二区|