客戶端-服務器編程模型,每一個網絡應用都是基於客戶端-服務器模型。html
一個應用是由一個服務器進程和一個或者多個客戶端進程組成。linux
服務器管理某種資源,並經過操做資源來爲客戶端提供某種服務。git
基本操做是事務。程序員
四個步驟:編程
- 當客戶端須要服務時,向服務器發送請求,發起一個事務。 - 服務器收到請求後,解釋它,並以適當的方式操做它的資源。 - 服務器給客戶端發送一個響應,並等待下一個請求。 - 客戶端收到響應並處理它。
客戶端和服務器一般運行在不一樣的話主機上,而且經過計算機網絡的硬件和軟件資源來通訊。瀏覽器
網絡的層次系統的最低層時LAN(局域網),其技術爲以太網安全
每一個以太網適配器都有一個全球惟一的48未地址。服務器
運行在每臺主機和路由器上的協議軟件能夠解決不兼容的問題。網絡
兩種基本能力:命名機制、傳送機制多線程
主機和路由器如何使用互聯網協議在不兼容的局域網間傳送數據的示例與步驟。(p617)
每臺因特網主機都運行實現TCP/IP協議的軟件。
因特網的客戶端和服務器混合使用套接字接口函數和Unix I/O函數來進行通訊。
TCP/IP實際是一個協議組,其中每個都提供不一樣的功能。
IP地址
- htonl函數將32位整數由主機字節順序轉換成網絡字節順序。 - ntohl函數將32位整數從網絡字節順序轉換成主機字節。 - htons函數和ntohs爲16位的整數執行相應的轉換。 - 可使用hostname -i來肯定本身主機的點分十進制地址
因特網域名
- 對於IP地址人性化的命名爲域名。 - DNS域名系統 - 能夠用hostinfo程序來挖掘一些DNS映射的特性。 - hostname肯定本身的主機域名。
因特網連接
- 因特網客戶端和服務器經過在鏈接上發送和接收字節流來通訊。 - 一個套接字是鏈接的一個斷點,每一個套接字都由相應的套接字地址,是由一個因特網地址和一個16位的整數端口組成的,用「地址:端口」來表示。 - 在Unix機器上,文件/etc/services包含一張這臺機器提供的服務以及它們的知名端口號的綜合列表。 - 一個連接是由它兩端的套接字地址惟一肯定的,稱爲套接字對。
套接字接口
- sockaddr_in的16字節結構 - sin_family成員是AF_INET - sin_port成員是一個16位的端口號 - sin_addr成員是一個32位的IP地址。 - IP地址和端口號總時以網絡字節順序(大端法)存放的。 - _in是互聯網絡的縮寫,不是輸入input的縮寫。
Web服務器
- Web服務器使用HTTP協議和它們的客戶端(瀏覽器等)彼此通訊。 - 瀏覽器向服務器請求靜態或者動態的內容 - 對靜態內容的請求是經過從服務器磁盤取得文件並把它返回給客戶端來服務的。 - 對動態內容的請求時經過在服務器上一個子進程的上下文中運行一個程序並將它的輸出返回給客戶端來服務的。
CGI標準提供了一組規則,來管理客戶端如何將程序參數傳遞給服務器。服務器如何將這些參數以及其餘信息傳遞給子進程,以及子進程如何將它的輸出發送回客戶端。
併發:邏輯控制流在時間上重疊
併發程序:使用應用級併發的應用程序稱爲併發程序。
三種基本的構造併發程序的方法:
- 進程,用內核來調用和維護,有獨立的虛擬地址空間,顯式的進程間通訊機制。
- I/O多路複用,應用程序在一個進程的上下文中顯式的調度控制流。邏輯流被模型化爲狀態機。
- 線程,運行在一個單一進程上下文中的邏輯流。由內核進行調度,共享同一個虛擬地址空間。
構造併發服務器的天然方法就是,在父進程中接受客戶端鏈接請求,而後建立一個新的子進程來爲每一個新客戶端提供服務。
由於父子進程中的已鏈接描述符都指向同一個文件表表項,因此父進程關閉它的已鏈接描述符的拷貝是相當重要的,並且由此引發的存儲器泄露將最終消耗盡可用的存儲器,使系統崩潰。
基於進程的併發echo服務器的重點內容:
- 須要一個SIGCHLD處理程序,來回收僵死子進程的資源。 - 父子進程必須關閉各自的connfd拷貝。對父進程尤其重要,以免存儲器泄露。 - 套接字的文件表表項中的引用計數,直到父子進程的connfd都關閉了,到客戶端的鏈接纔會終止。
進程的模型:共享文件表,但不是共享用戶地址空間。
Unix IPC是指全部容許進程和同一臺主機上其餘進程進行通訊的技術,包括管道、先進先出(FIFO)、系統V共享存儲器,以及系統V信號量。
echo服務器必須響應兩個相互獨立的I/O時間: 網絡客戶端發起鏈接請求、用戶在鍵盤上鍵入命令行
I/O多路複用技術的基本思路:使用select函數,要求內核掛起進程,只有在一個或多個I/O事件發生後,纔將控制返回給應用程序。
echo函數:未來自科幻段的每一行回送回去,直到客戶端關閉這個連接。
狀態機就是一組狀態、輸入事件和轉移,轉移就是將狀態和輸入時間映射到狀態,自循環是同一輸入和輸出狀態之間的轉移。
事件驅動器的設計優勢:
- 比基於進程的設計給了程序員更多的對程序行爲的控制 - 運行在單一進程上下文中,所以,每一個邏輯流都能訪問該進程的所有地址空間,使得流之間共享數據變得很容易。 - 不須要進程上下文切換來調度新的流。
缺點: 編碼複雜、不能充分利用多核處理器
線程:運行在進程上下文中的邏輯流。
線程有本身的線程上下文,包括一個惟一的整數線程ID、棧、棧指針、程序計數器、通用目的寄存器和條件碼。全部運行在一個進程裏的線程共享該進程的整個虛擬地址空間。
主線程:每一個進程開始生命週期時都是單一線程。
對等線程:某一時刻,主線程建立的對等線程 。
線程與進程的不一樣:
- 線程的上下文切換要比進程的上下文切換快得多; - 和一個進程相關的線程組成一個對等池,獨立於其餘線程建立的線程。 - 主線程和其餘線程的區別僅在於它老是進程中第一個運行的線程。
對等池的影響: 一個線程能夠殺死它的任何對等線程;等待它的任意對等線程終止;
每一個對等線程都能讀寫相同的共享資源。
線程例程:線程的代碼和本地數據被封裝在一個線程例程中。每個線程例程都以一個通用指針做爲輸入,並返回一個通用指針。
建立線程
pthread create函數建立一個新的線程,並帶着一個輸入變量arg,在新線程的上下文中運行線程例程f。新線程能夠經過調用pthread _self函數來得到本身的線程ID。
終止線程
- 一個線程的終止方式: 1. 當頂層的線程例程返回時,線程會隱式的終止; 2. 經過調用pthread _exit函數,線程會顯示地終止。若是主線程調用pthread _exit,它會等待全部其餘對等線程終止,而後再終止主線程和整個進程。
回收已終止線程的資源
pthread _join函數會阻塞,直到線程tid終止,回收已終止線程佔用的全部存儲器資源。pthread _join函數只能等待一個指定的線程終止。
分離線程
在任何一個時間點上,線程是可結合的或者是分離的。一個可結合的線程可以被其餘線程收回其資源和殺死;一個可分離的線程是不能被其餘線程回收或殺死的。它的存儲器資源在它終止時有系統自動釋放。
默認狀況下,線程被建立成可結合的,爲了不存儲器漏洞,每一個可集合的線程都應該要麼被其餘進程顯式的回收,要麼經過調用pthread _detach函數被分離。
初始化線程
pthread _once函數容許初始化與線程例程相關的狀態。
once _control變量是一個全局或者靜態變量,老是被初始化爲PTHREAD _ONCE _INIT.
每一個線程和其餘線程一塊兒共享進程上下文的剩餘部分。包括整個用戶虛擬地址空間,是由只讀文本、讀/寫數據、堆以及全部的共享庫代碼和數據區域組成的。線程也共享一樣的打開文件的集合。
任何線程均可以訪問共享虛擬存儲器的任意位置。寄存器是從不共享的,而虛擬存儲器老是共享的。
將變量映射到存儲器
- 全局變量:虛擬存儲器的讀/寫區域只會包含每一個全局變量的一個實例。 - 本地自動變量:定義在函數內部但沒有static屬性的變量。 - 本地靜態變量:定義在函數內部並有static屬性的變量。
共享變量
- 變量v是共享的,當且僅當它的一個實例被一個以上的線程引用。
用信號量同步線程
- 共享變量引入了同步錯誤的可能性。 - 線程i的循環代碼分解爲五部分: Hi:在循環頭部的指令塊 Li:加載共享變量cnt到寄存器%eax的指令,%eax表示線程i中的寄存器%eax的值 Ui:更新(增長)%eax的指令 Si:將%eaxi的更新值存回到共享變量cnt的指令 Ti:循環尾部的指令塊。
進度圖
- 進度圖將指令執行模式化爲從一種狀態到另外一種狀態的轉換。轉換被表示爲一條從一點到相鄰點的有向邊。合法的轉換是向右或者向上。 - 臨界區:對於線程i,操做共享變量`cnt`內容的指令構成了一個臨界區。 - 互斥的訪問:確保每一個線程在執行它的臨界區中的指令時,擁有對共享變量的互斥的訪問。 - 安全軌跡線:繞開不安全區的軌跡線 不安全軌跡線:接觸到任何不安全區的軌跡線就叫作不安全軌跡線 - 任何安全軌跡線都能正確的更新共享計數器。
使用信號量來實現互斥
二元信號量:將每一個共享變量與一個信號量s聯繫起來,而後用P(S)和V(s)操做將這種臨界區包圍起來,這種方式來保護共享變量的信號量。
互斥鎖:以提供互斥爲目的的二元信號量
加鎖:一個互斥鎖上執行P操做稱爲對互斥鎖加鎖,執行V操做稱爲對互斥鎖解鎖。對一個互斥鎖加了鎖但尚未解鎖的線程稱爲佔用了這個互斥鎖。
計數信號量:一個唄用做一組可用資源的計數器的信號量
競爭
- 競爭:當一個程序的正確性依賴於一個線程要在另外一個線程到達y點以前到達它的控制流中的x點時,就會發生競爭。 - 線程化的程序必須對任何可行的軌跡線都正確工做。 - 消除方法:動態的爲每一個整數ID分配一個獨立的塊,而且傳遞給線程例程一個指向這個塊的指針
死鎖
- 死鎖:一組線程被阻塞了,等待一個永遠也不會爲真的條件。
- 程序員使用P和V操做不當,以致於兩個信號量的禁止區域重疊。
- 重疊的禁止區域引發了一組稱爲死鎖區域的狀態。
- 死鎖是不可預測的。
注意:本章代碼是多線程編譯,pthread
庫不是linux
系統默認的庫,所以在編譯的時候須要加上-lpthread
參數。
PTHREAD_MUTEX_INITIALIZER
來靜態初始化互斥鎖。先建立tidA
線程後運行doit函數
,利用互斥鎖鎖定資源,進行計數,執行完畢後解鎖。後建立tidB
,與tidA
交替執行。因爲定義的NLOOP值爲5000,因此程序最後的輸出值爲10000.程序的最後還須要分別回收tidA
和tidB
的資源。
mmap函數void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
將一個文件或者其餘對象映射進內存。文件被映射到多個頁上,若是文件的大小不是全部頁的大小之和,最後一個頁不被使用的空間將會清零。mmap在用戶空間映射調用系統中做用很大。
成功執行時,mmap()
返回被映射區的指針,munmap()
返回0.失敗時,mmap()
返回MAP_FAILED
,munmap
返回-1.
pthread_create()
函數的使用,用來打印進程和線程的IDsem_init函數sem_init(sem_t *sem, int pshared, umsigned int value);
函數初始化一個定位在sem的匿名信號量;pshared參數爲0指明信號量是由進程內線程共享,若爲非0值則信號量在進程之間共享;value參數指定信號量的初始值。
sem_init()成功時返回0;錯誤時返回-1,並把errno設置爲合適的值。
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 | |
第一週 | 200/200 | 2/2 | 20/20 | 掌握核心的linux命令,瞭解了linux操做系統 |
第二週 | 300/500 | 2/4 | 18/38 | 學會了虛擬機上的C編程 |
第三週 | 500/1000 | 3/7 | 22/60 | 初步學習計算機中各類數的表示和運算 |
第五週 | 300/1300 | 2/9 | 30/90 | 經過學習彙編,瞭解逆向的思想應用 |
第六週 | 300/1500 | 2/11 | 28/110 | 安裝了Y86處理器,瞭解了ISA抽象 |
第七週 | 150/1700 | 2/13 | 30/130 | 學習了存儲器的體系結構| |
第八週 | 200/1900 | 2/15 | 30/150 | 複習前7章知識 |
第九周 | 340/2100 | 1/17 | 31/170 | 學習了系統級I/O |
第十週 | 599/2700 | 1/19 | 33/200 | 學習了Linux命令 |
第十一週 | 1016/3300 | 1/21 | 35/240 | 學習了異常控制流 |
第十二週 | 300/3500 | 3/25 | 30/280 | 複習前三週代碼知識點和實驗 |
第十三週 | 300/4000 | 2/26 | 34/320 | 學習了網絡編程和併發編程 |