第十二章

1.客戶端-服務器編程模型程序員

- 一個服務器進程 -> 管理某種資源 -> 經過操做這種資源來爲它的客戶端提供某種服務編程

- 一個或多個客戶端進程數組

基本操做:事務瀏覽器

- 當一個客戶端須要服務時,向服務器發送一個請求,發起一個事務。安全

- 服務器收到請求後,解釋它,並以適當的方式操做它的資源。服務器

- 服務器給客戶端發送一個相應,並等待下一個請求。網絡

- 客戶端收到響應並處理它。多線程

*客戶端和服務器都是進程。併發

2.網絡app

1)對主機而言:網絡是一種I/O設備

從網絡上接收到的數據從適配器通過I/O和存儲器總線拷貝到存儲器,典型地是經過DMA(直接存儲器存取方式)傳送。

2)物理上:網絡是一個按照地理遠近組成的層次系統

最底層:LAN(局域網),最流行的是以太網,

  • 以太網段
    • 包括一些電纜和集線器。每根電纜都有相同的最大位帶寬,集線器不加分辯地將一個端口上收到的每一個位複製到其餘全部的端口上,所以每臺主機都能看到每一個位。
    • 每一個以太網適配器都有一個全球惟一的48位地址,存儲在適配器的非易失性存儲器上。
    • 一臺主機能夠發送一段位:幀,到這個網段內其它任何主機。每一個幀包括一些固定數量的頭部位(標識此幀的源和目的地址及幀長)和數據位(有效載荷)。每一個主機都能看到這個幀,可是隻有目的主機能讀取。
    • 使用電纜和網橋,多個以太網段能夠鏈接成較大的局域網,稱爲橋接以太網。這些電纜的帶寬能夠是不一樣的。
    • 多個不兼容的局域網能夠經過叫作路由器的特殊計算機鏈接起來,組成一個internet互聯網絡。

3)協議

  • 互聯網重要特性:由採用不一樣技術,互不兼容的局域網和廣域網組成,並能使其相互通訊。其中不一樣網絡相互通訊的解決辦法是一層運行在每臺主機和路由器上的協議軟件,消除不一樣網絡的差別。
  • 協議提供的兩種基本能力
    • 命名機制:惟一的標示一臺主機
    • 傳送機制:定義一種把數據位捆紮成不連續的片的同一方式

4)全球IP因特網

  • TCP/IP協議族
  • 混合使用套接字接口函數和UnixI/O函數進行通訊
  • 世界範圍的主機集合
  • 特性:
  • - 主機集合被映射爲一組32位的IP地址
  • - 這組IP地址被映射爲一組稱爲因特網域名的標識符

- 因特網主機上的進程可以經過鏈接和任何其餘主機上的進程

檢索並打印一個DNS主機條目:

#include "csapp.h"

int main(int argc, char **argv)

{

    char **pp;

    struct in_addr addr;

    struct hostent *hostp;

    if (argc != 2) {

    fprintf(stderr, "usage: %s <domain name or dotted-decimal>\n",

        argv[0]);

    exit(0);

    }

    if (inet_aton(argv[1], &addr) != 0)

    hostp = Gethostbyaddr((const char *)&addr, sizeof(addr), AF_INET);

    else                               

    hostp = Gethostbyname(argv[1]);

    printf("official hostname: %s\n", hostp->h_name);

    for (pp = hostp->h_aliases; *pp != NULL; pp++)

    printf("alias: %s\n", *pp);

    for (pp = hostp->h_addr_list; *pp != NULL; pp++) {

    addr.s_addr = ((struct in_addr *)*pp)->s_addr;

    printf("address: %s\n", inet_ntoa(addr));

    }

    exit(0);

}

3.套接字

1)函數

  • socket函數
  • connect函數
  • open_clientfd函數
  • bind函數
  • listen函數
  • open_listenfd函數
  • accept函數

2echo客戶端和服務器示例

4.Web服務器

1)協議

Web 客戶端和服務器之間的交互用的是一個基於文本的應用級協議,叫作 HTTP (Hypertext Transfer Protocol,超文本傳輸協議). HTTP 是一個簡單的協議。一個 Web 客戶端(即瀏覽器) 打開一個到服務器的因特網鏈接,而且請求某些內容。服務器響應所請求的內容,而後關閉鏈接。瀏覽器讀取這些內容,並把它顯示在屏幕上。

2)內容

Web內容能夠用一種叫作 HTML(Hypertext Markup Language,超文本標記語言)的語言來編寫。一個 HTML 程序(頁)包含指令(標記),它們告訴瀏覽器如何顯示這頁中的各類文本和圖形對象。

Web 服務器以兩種不一樣的方式向客戶端提供內容:

取一個磁盤文件,並將它的內容返回給客戶端。磁盤文件稱爲靜態內容 (static content), 而返回文件給客戶端的過程稱爲服務靜態內容 (serving static content)。
運行一個可執行文件,並將它的輸出返回給客戶端。運行時可執行文件產生的輸出稱爲態內容 (dynamic content),而運行程序並返回它的輸出到客戶端的過程稱爲服務動態內容 (serving dynamic content)。

第十二章併發編程


學習目標:
1.掌握三種併發的方式:進程、線程、I/O多路複用
2.掌握線程控制及相關係統調用
3.掌握線程同步互斥及相關係統調用

知識點梳理

1.併發概述

  • 邏輯控制流在時間上重疊,那麼它們就是併發的。
  • 併發(concurrency ) ,出如今計算機系統的許多不一樣層面上。
  • 應用級併發
    • 訪問慢速I/O設備。當一個應用正在等待來自慢速 I/O 設備(例如磁盤)的數據到達時, 內核會運行其餘進程,使 CPU保持繁忙。每一個應用均可以按照相似的方式,經過交替執行 I/O 請求和其餘有用的工做來使用併發。
    • 與人交互。和計算機交互的人要求計算機有同時執行多個任務的能力。例如,他們在打印一個文檔時,可能想要調整一個窗口的大小。現代視窗系統利用併發來提供這種能力。每次用戶請求某種操做(好比經過單擊鼠標)時,一個獨立的併發邏輯流被建立來執行這個操做。
    • 經過推遲工做以下降延遲。有時,應用程序可以經過推遲其餘操做和併發地執行它們,利用併發來下降某些操做的延遲。好比,一個動態存儲分配器能夠經過推遲合併,把它放到一個運行在較低優先級上的併發"合併"流中,在有空閒的 CPU 週期時充分利用這些空閒 週期,從而下降單個 free 操做的延遲。
    • 服務多個網絡客戶端。
    • 在多核機器上進行並行計算。許多現代系統都配備有多核處理器,多核處理器中包含多個 CPU。被劃分紅併發流的應用程序一般在多核機器上比在單處理器機器上運行得快,由於這些流會並行執行,而不是交錯執行。
  • 使用應用級併發的應用程序稱爲併發程序。現代操做系統提供了三種基本的構造併發程序的方法:
    • 進程。每一個邏輯控制流都是一個進程,由內核來調度和維護。由於進程 有獨立的虛擬地址空間,想要和其餘流通訊,控制流必須使用某種顯式的進程間通訊(IPC)機制。
    • I/O 多路複用。在這種形式的併發編程中,應用程序在一個進程的上下文中顯式地調度它們本身的邏輯流。邏輯流被模型化爲狀態機,數據到達文件描述符後,主程序顯式地從一個狀態轉換到另外一個狀態。由於程序是一個單獨的進程,因此全部的流都共享同一個地址空間。
    • 線程。線程是運行在一個單一進程上下文中的邏輯流,由內核進行調度。是其餘兩種方式的混合體,像進程流同樣由內核進行調度,而像I/O 多路複用流同樣共享同一個虛擬地址空間。

2.基於進程的併發編程

  • 構造併發程序最簡單的方法就是用進程。

一個構造併發服務器的天然方法就是,在父進程中接受客戶端鏈接請求,而後建立一個新的子進程來爲每一個新客戶端提供服務。

  • 基於進程的併發服務器

一般服務器會運行很長的時間,因此咱們必需要包括一個 SIGCHLD 處理程序,來回收僵死 (zombie) 子進程的資源。由於當 SIGCHLD 處理程序執行時, SIGCHLD 信號是阻塞的,而 Unix 信號是不排隊的,因此 SIGCHLD 處理程序必須準備好回收多個僵死子進程的資源。

父子進程必須關閉它們各自的 connfd 拷貝。這對父進程而言尤其重要,它必須關閉它的已鏈接描述 符,以免存儲器泄漏。

由於套接字的文件表表項中的引用計數,直到父子進程的 connfd 都關閉了,到客戶端的鏈接纔會終止。

第一步:服務器接受客戶端的鏈接請求

第二步:服務器派生一個子進程爲這個客戶端服務

第三步:服務器接受另外一個鏈接請求

第四步:服務器派生另外一個子進程爲新的客戶端服務

  • 進程的優劣

在父、子進程間共享狀態信息,進程有一個很是清晰的模型:共享文件表,可是不共享用戶地址空間。

進程有獨立的地址空間既是優勢也是缺點。
優勢:一個進程不可能不當心覆蓋另外一個進程的虛擬存儲器,這就消除了許多使人迷惑的錯誤。
缺點:獨立的地址空間使得進程共享狀態信息變得更加困難。爲了共享信息,它們必須使用顯式的IPC(進程間通訊)機制。基於進程的設計的另外一個缺點是,它們每每比較慢,由於進程控制和 IPC 的開銷很高。

3.基於 I/O 多路複用的併發編程

I/O 多路複用(I/O multiplexing) 技術。基本的思路就是使用 select 函數,要求內核掛起進程,只有在一個或多個I/O事件發生後,纔將控制返回給應用程序。

1)基於 I/O 多路複用的併發事件驅動服務器

I/O 多路複用能夠用作併發事件驅動 (event-driven) 程序的基礎,在事件驅動程序中,流是由於某種事件而前進的。

將邏輯流模型化爲狀態機。不嚴格地說,一個狀態機 (state machine) 就是一組狀態 (state)、輸入事件(input event) 和轉移他(transition),其中轉移就是將狀態和輸入事件映射到狀態。每一個轉移都將一個(輸入狀態,輸入事件)對映射到一個輸出狀態。自循環(self-loop) 是同一輸入和輸出狀態之間的轉移。節 點表示狀態,有向弧表示轉移,而弧上的標號表示輸入事件。一個狀態機從某種初始狀態開始執行。每一個輸入事件都會引起一個從當前狀態到下一狀態的轉移。

服務器使用I/O多路複用,藉助 select 函數檢測輸入事件的發生。

服務器調用 select 函數來 檢測兩種不一樣類型的輸人事件:

  • a) 來自一個新客戶端的鏈接請求到達
  • b) 一個己存在的客戶 端的己鏈接描述符準備好能夠讀了。
    • init_pool 函數初始化客戶端池。 clientfd 數組表示已鏈接描述符的集合, 其中整數 -1 表示一個可用的槽位。初始時,已鏈接描述符集合是空的,並且監聽描述符是 select 讀集合中惟一的描述符。
    • add_clieht函數添加一個新的客戶端到活動客戶端池中。在 clientfd 數組中找到一個空槽位後,服務器將這個已鏈接描述符添加到數組中,並初始化相應的RIO讀緩衝區,這樣一來咱們就可以對這個描述符調用rio_readlineb。將這個已鏈接描述符添加到 select 讀集合,並更新該池的一些全局屬性。 maxfd 變量記錄了 select 的最大文件描述符。 maxi 變量記錄的 是到 clientfd數組的最大索引,這樣 check_clients 函數就無需搜索整個數組了。
    • check_clients 函數回送來自每一個準備好的已鏈接描述符的一個文本行。 若是成功地從描述符讀取了一個文本行,那麼咱們就將該文本行回送到客戶。
    • select 函數檢測到輸入事件,而 add_client 函數建立 一個新的邏輯流(狀態機)。
    • check_clients 函數經過回送輸入行來執行狀態轉移,並且當客 戶端完成文本行發送時,它還要刪除這個狀態機。

2I/O 多路複用技術的優劣

  • 事件驅動設計的優勢:
    • 它比基於進程的設計給了程序員更多的對程序行爲的控制。
    • 一個基於 I/O 多路複用的事件驅動服務器是運行在單一進程上下文中的,因 此每一個邏輯流都能訪問該進程的所有地址空間。
  • 缺點就是編碼複雜。咱們的事件驅動的併發 echo 服務器須要的代碼比基於進程的服務器多三倍。不幸的是,隨着併發粒度的減少,複雜性還會上升。這裏的粒度是指每一個邏輯流每一個時間片執行的指令數量。

4.基於線程的併發編程

線程(thread) 就是運行在進程上下文中的邏輯流。

每一個線程都有它本身的線程上下文 (thread context),包括一個惟一的整數線程 (Thread ID, TID)、棧、棧指針、程序計數器、通用目的寄存器和條件碼。全部的運行在一個進程裏的線程共享該進程的整個虛擬地址空間。

基於線程的邏輯流結合了基於進程和基於 I/O 多路複用的流的特性。同進程同樣,線程由內核自動調度,而且內核經過一個整數 ID 來識別線程。同基於 I/O 多路複用的流同樣,多個線程 運行在單一進程的上下文中,所以共享這個進程虛擬地址空間的整個內容,包括它的代碼、數據、堆、共享庫和打開的文件。

1)線程執行模型

每一個進程開始生命週期時都是單一線程,這個線程稱爲主線程 (main thread)。在某一時刻,主線程建立一個對等線程 (peer thread),從這個時間點開始,兩個線程就併發地運行。最後,由於主線程執行一個慢速系統調用。或者由於它被系統的間隔計時器中斷, 控制就會經過上下文切換傳遞到對等線程。對等線程會執行一段時間,而後控制傳遞迴主線程,依次類推。

- 線程的上下文切換要比進程的上下文切換快得多。

- 不是按照嚴格的父子層次來組織的。

- 和一個進程相關的線程組成一個對等(線程)池 (pool),獨立於其餘線程建立的線程。

- 主線程和其餘線程的區別僅在於它老是進程中第一個運行的線程。

- 對等 (線程)池概念的主要影響是,一個線程能夠殺死它的任何對等線程,或者等待它的任意對等線程終止。

- 每一個對等線程都能讀寫相同的共享數據。

2Posix 線程

Posix 線程 (Pthreads) 是在 C 程序中處理線程的一個標準接口。Pthreads 定義了大約 60 個函數,容許程序建立、殺死和回收線程,與對等線程安全地共享數據,還能夠通知對等線程系統狀態的變化。

線程的代碼和本地數據被封裝在一個線程例程(thread routine) 中。若是想傳遞多個參數給錢程例程,那麼你應該將參數放 到一個結構中,並傳遞一個指向該結構的指針。想要線程例程返回多個參數,你能夠返回一個指向一個結構的指針。

3)建立線程

pthread_create 函數建立一個新的線程,並帶着一個輸入變量arg,在新線程的上下文中運行線程例程f。能用attr參數來改變新建立線程的默認屬性。

當 pthread_create 返回時,參數 tid包含新建立線程的ID。新線程能夠經過調用 pthread_self 函數來得到它本身的線程 ID.

4)終止線程

當頂層的線程例程返回時,線程會隱式地終止。
經過調用 pthread_exit 函數,線程會顯式地終止。若是主線程調用 pthread_exit , 它會等待全部其餘對等線程終止,而後再終止主線程和整個進程,返回值爲 thread_return。

5)回收已終止線程的資源

線程經過調用 pthread_join 函數等待其餘線程終止。

  • pthread_join 函數會阻塞,直到線程 tid 終止,將線程例程返回的 (void*) 指針賦值爲 thread_return 指向的位置,而後回收己終止線程佔用的全部存儲器資源。
  • pthread join 函數只能等待一個指定的線程終止。

6)分離線程

在任何一個時間點上,線程是可結合的 (joinable) 或者是分離的 (detached)。一個可結合的線程可以被其餘線程收回其資源和殺死。在被其餘線程回收以前,它的存儲器資源(例如棧)是沒有被釋放的。相反,一個分離的線程是不能被其餘線程回收或殺死的。它的存儲器資源在它終止時由系統自動釋放。

默認狀況下,線程被建立成可結合的。爲了不存儲器泄漏,每一個可結合線程都應該要麼被其餘線程顯式地收回,要麼經過調用 pthread_detach 函數被分離。

  • pthread_detach 函數分離可結合線程 tid. 線程可以經過以 pthread_self()爲參數的 pthread_detach 調用來分離它們本身。

7)初始化線程

pthread_once 函數容許你初始化與線程例程相關的狀態。

8)一個基於線程的併發服務器

調用 pthread_ create 時,如何將已鏈接描述符傳遞給對等線程。最明顯的方法就是傳遞一個指向這個描述符的指針。
對等線程間接引用這個指針,並將它賦值給一個局部變量。

5.多線程程序中的共享變量

1)線程存儲器模型

2)將變量映射到存儲器

3)共享變量

6.用信號量同步線程

1)進度圖

2)信號量

3)使用信號量來實現互斥

4)利用信號量來調度共享資源

生產者消費者問題

讀者寫者問題

7.使用線程提升並行性

8.其它併發問題

1)線程安全

2)可重入性

3)在線程化的程序中使用已存在的庫函數

4)競爭

5)死鎖

相關文章
相關標籤/搜索