1.客戶端-服務器編程模型程序員
- 一個服務器進程 -> 管理某種資源 -> 經過操做這種資源來爲它的客戶端提供某種服務編程
- 一個或多個客戶端進程數組
基本操做:事務瀏覽器
- 當一個客戶端須要服務時,向服務器發送一個請求,發起一個事務。安全
- 服務器收到請求後,解釋它,並以適當的方式操做它的資源。服務器
- 服務器給客戶端發送一個相應,並等待下一個請求。網絡
- 客戶端收到響應並處理它。多線程
*客戶端和服務器都是進程。併發
2.網絡app
(1)對主機而言:網絡是一種I/O設備
從網絡上接收到的數據從適配器通過I/O和存儲器總線拷貝到存儲器,典型地是經過DMA(直接存儲器存取方式)傳送。
(2)物理上:網絡是一個按照地理遠近組成的層次系統
最底層:LAN(局域網),最流行的是以太網,
(3)協議
(4)全球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)函數
(2)echo客戶端和服務器示例
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.併發概述
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 函數來 檢測兩種不一樣類型的輸人事件:
(2)I/O 多路複用技術的優劣
4.基於線程的併發編程
線程(thread) 就是運行在進程上下文中的邏輯流。
每一個線程都有它本身的線程上下文 (thread context),包括一個惟一的整數線程 (Thread ID, TID)、棧、棧指針、程序計數器、通用目的寄存器和條件碼。全部的運行在一個進程裏的線程共享該進程的整個虛擬地址空間。
基於線程的邏輯流結合了基於進程和基於 I/O 多路複用的流的特性。同進程同樣,線程由內核自動調度,而且內核經過一個整數 ID 來識別線程。同基於 I/O 多路複用的流同樣,多個線程 運行在單一進程的上下文中,所以共享這個進程虛擬地址空間的整個內容,包括它的代碼、數據、堆、共享庫和打開的文件。
(1)線程執行模型
每一個進程開始生命週期時都是單一線程,這個線程稱爲主線程 (main thread)。在某一時刻,主線程建立一個對等線程 (peer thread),從這個時間點開始,兩個線程就併發地運行。最後,由於主線程執行一個慢速系統調用。或者由於它被系統的間隔計時器中斷, 控制就會經過上下文切換傳遞到對等線程。對等線程會執行一段時間,而後控制傳遞迴主線程,依次類推。
- 線程的上下文切換要比進程的上下文切換快得多。
- 不是按照嚴格的父子層次來組織的。
- 和一個進程相關的線程組成一個對等(線程)池 (pool),獨立於其餘線程建立的線程。
- 主線程和其餘線程的區別僅在於它老是進程中第一個運行的線程。
- 對等 (線程)池概念的主要影響是,一個線程能夠殺死它的任何對等線程,或者等待它的任意對等線程終止。
- 每一個對等線程都能讀寫相同的共享數據。
(2)Posix 線程
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 函數等待其餘線程終止。
(6)分離線程
在任何一個時間點上,線程是可結合的 (joinable) 或者是分離的 (detached)。一個可結合的線程可以被其餘線程收回其資源和殺死。在被其餘線程回收以前,它的存儲器資源(例如棧)是沒有被釋放的。相反,一個分離的線程是不能被其餘線程回收或殺死的。它的存儲器資源在它終止時由系統自動釋放。
默認狀況下,線程被建立成可結合的。爲了不存儲器泄漏,每一個可結合線程都應該要麼被其餘線程顯式地收回,要麼經過調用 pthread_detach 函數被分離。
(7)初始化線程
pthread_once 函數容許你初始化與線程例程相關的狀態。
(8)一個基於線程的併發服務器
調用 pthread_ create 時,如何將已鏈接描述符傳遞給對等線程。最明顯的方法就是傳遞一個指向這個描述符的指針。
對等線程間接引用這個指針,並將它賦值給一個局部變量。
5.多線程程序中的共享變量
(1)線程存儲器模型
(2)將變量映射到存儲器
(3)共享變量
6.用信號量同步線程
(1)進度圖
(2)信號量
(3)使用信號量來實現互斥
(4)利用信號量來調度共享資源
生產者消費者問題
讀者寫者問題
7.使用線程提升並行性
8.其它併發問題
(1)線程安全
(2)可重入性
(3)在線程化的程序中使用已存在的庫函數
(4)競爭
(5)死鎖