第一章 引言和網(wǎng)絡(luò)編程基礎(chǔ)知識(shí)
1.1 分別簡述OSI參考模型和TCP/IP模型,并闡述他們之間的對應(yīng)關(guān)系

1.2 簡述數(shù)據(jù)傳輸?shù)娜N方式及其優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) | |
|---|---|---|
| 電路交換 | 雙方可以隨時(shí)通信,實(shí)時(shí)性強(qiáng). 雙方通信時(shí)按發(fā)送順序傳送數(shù)據(jù),不存在失序問題. | 電路交換的平均連接建立時(shí)間較長. 信道利用低. |
| 報(bào)文交換 | 不需要為通信雙方預(yù)先建立一條專用的通信線路。 通信雙方不是固定占有一條通信線路,提高了通信線路的利用率. | 經(jīng)歷存儲(chǔ)、轉(zhuǎn)發(fā)這一過程,從而引起轉(zhuǎn)發(fā)時(shí)延. 要求網(wǎng)絡(luò)中每個(gè)結(jié)點(diǎn)有較大的緩沖區(qū) |
| 分組交換 | 加速了數(shù)據(jù)在網(wǎng)絡(luò)中的傳輸 簡化了存儲(chǔ)管理 減少了出錯(cuò)機(jī)率和重發(fā)數(shù)據(jù)量 | 每個(gè)分組都要加上源、目的地址和分組編號(hào)等信息,使傳送的信息量大 可能出現(xiàn)失序、丟失或重復(fù)分組 |
1.3 簡述C/S,B/S模型并分析他們的優(yōu)缺點(diǎn)
C/S架構(gòu)
C/S 架構(gòu)是一種典型的兩層架構(gòu),其全稱是Client/Server,即客戶端、服務(wù)器端架構(gòu),其客戶端包含一個(gè)或多個(gè)在用戶的電腦上運(yùn)行的程序,而服務(wù)器端有兩種,一種是數(shù)據(jù)庫服務(wù)器端,客戶端通過數(shù)據(jù)庫連接訪問服務(wù)器端的數(shù)據(jù);另一種是Socket服務(wù)器端,服務(wù)器端的程序通過Socket與客戶端的程序通信。
C/S 架構(gòu)也可以看做是胖客戶端架構(gòu)。因?yàn)榭蛻舳诵枰獙?shí)現(xiàn)絕大多數(shù)的業(yè)務(wù)邏輯和界面展示。
C/S優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
- C/S架構(gòu)的界面和操作可以很豐富。
- 安全性能可以很容易保證,實(shí)現(xiàn)多層認(rèn)證也不難。
- 由于只有一層交互,因此響應(yīng)速度較快。
缺點(diǎn):
- 用戶群固定。由于程序需要安裝才可使用,因此不適合面向一些不可知的用戶。
- 維護(hù)成本高,發(fā)生一次升級(jí),則所有客戶端的程序都需要改變。
B/S架構(gòu)
B/S架構(gòu)的全稱為Browser/Server,即瀏覽器/服務(wù)器結(jié)構(gòu)。主要事務(wù)邏輯在服務(wù)器端實(shí)現(xiàn),B/S架構(gòu)的系統(tǒng)無須特別安裝,只有Web瀏覽器即可。因此也被稱為瘦客戶端。
必須強(qiáng)調(diào)的是C/S和B/S并沒有本質(zhì)的區(qū)別:B/S是基于特定通信協(xié)議(HTTP)的C/S架構(gòu),也就是說B/S包含在C/S中,是特殊的C/S架構(gòu)。
B/S優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
- 客戶端無需安裝,有Web瀏覽器即可。
- B/S架構(gòu)可以直接放在廣域網(wǎng)上,通過一定的權(quán)限控制實(shí)現(xiàn)多客戶訪問的目的,交互性較強(qiáng)。
- B/S架構(gòu)無需升級(jí)多個(gè)客戶端,升級(jí)服務(wù)器即可。
缺點(diǎn):
- 在跨瀏覽器上,B/S架構(gòu)不盡如人意。
- 表現(xiàn)要達(dá)到C/S程序的程度需要花費(fèi)不少精力。
- 在速度和安全性上需要花費(fèi)巨大的設(shè)計(jì)成本,這是B/S架構(gòu)的最大問題。
- 客戶端服務(wù)器端的交互是請求-響應(yīng)模式,通常需要刷新頁面,這并不是客戶樂意看到的。
1.4 應(yīng)用程序在什么情況下建議使用UDP
UDP: 無連接交互
- 沒有可靠保證
- 依賴下層系統(tǒng)保證
- 程序中應(yīng)該有相應(yīng)保障措施
TCP: 面向連接的交互
- 提供傳輸可靠性
- 程序要求簡單
應(yīng)用程序只在以下情況使用UDP:
- 應(yīng)用程序指明必須使用UDP;
- 應(yīng)用程序協(xié)議要依靠硬件進(jìn)行廣播或組播
- 應(yīng)用協(xié)議在可靠的環(huán)境中運(yùn)行,不需要額外的可靠性處理。
1.5 請闡述無狀態(tài)服務(wù)器和有狀態(tài)服務(wù)器概念及其特點(diǎn),并介紹其優(yōu)缺點(diǎn)。
服務(wù)器所維護(hù)的與客戶交互的信息稱為狀態(tài)信息。不保存任何狀態(tài)信息的服務(wù)器稱為無狀態(tài)服務(wù)器,反之稱為有狀態(tài)服務(wù)器。
有狀態(tài)服務(wù)器
有狀態(tài)服務(wù)器在服務(wù)器中保存少量信息,可減少客戶端與服務(wù)器端交換報(bào)文的大小,保存了客戶之前有過的請求,允許服務(wù)器快速的相應(yīng)請求。
特點(diǎn)
- 保存客戶請求的數(shù)據(jù)(狀態(tài))
- 服務(wù)端容易對客戶狀態(tài)進(jìn)行管理
- 服務(wù)端并不要求每次客戶請求都攜帶額外的狀態(tài)數(shù)據(jù)
優(yōu)點(diǎn)
- 由于服務(wù)器可以區(qū)分各個(gè)客戶,并保留每個(gè)客戶以前的請求信息,報(bào)文中不必包含所有字段信息。
缺點(diǎn)
- 通常情況下報(bào)文丟失、重復(fù)或交付失序,或者客戶端程序崩潰都會(huì)使服務(wù)器的狀態(tài)信息不正確,此時(shí)就可能產(chǎn)生不正確的響應(yīng)。
無狀態(tài)服務(wù)器
無狀態(tài)服務(wù)器的動(dòng)機(jī)是協(xié)議的不可靠性??蛻粽埱髨?bào)文必須指定操作類型、文件名、傳輸數(shù)據(jù)在文件中的位置和傳輸字節(jié)數(shù)、要寫入的文件數(shù)據(jù)等。
特點(diǎn):
- 并不保存客戶請求的數(shù)據(jù)(狀態(tài))
- 客戶在請求時(shí)需要攜帶額外的狀態(tài)數(shù)據(jù)
- 無狀態(tài)服務(wù)器更加健壯,重啟服務(wù)器不會(huì)丟失狀態(tài)信息,這使得維護(hù)和擴(kuò)容更加簡單
優(yōu)點(diǎn)
無狀態(tài)服務(wù)器則不會(huì)因?yàn)閳?bào)文丟失,失序等問題導(dǎo)致狀態(tài)信息出錯(cuò),出現(xiàn)問題。
缺點(diǎn)
每次都要攜帶額外的狀態(tài)信息,產(chǎn)生額外的數(shù)據(jù)。
第二章 客戶服務(wù)器軟件中的并發(fā)處理
2.1 并發(fā)、并行的概念及其區(qū)別
并發(fā):
當(dāng)有多個(gè)線程在操作時(shí),如果系統(tǒng)只有一個(gè)CPU,則它根本不可能真正同時(shí)進(jìn)行一個(gè)以上的線程,它只能把CPU運(yùn)行時(shí)間劃分成若干個(gè)時(shí)間段,再將時(shí)間
段分配給各個(gè)線程執(zhí)行,在一個(gè)時(shí)間段的線程代碼運(yùn)行時(shí),其它線程處于掛起狀。這種方式我們稱之為并發(fā)(Concurrent)。
并行:
當(dāng)系統(tǒng)有一個(gè)以上CPU時(shí),
當(dāng)一個(gè)CPU執(zhí)行一個(gè)線程時(shí),另一個(gè)CPU可以執(zhí)行另一個(gè)線程,兩個(gè)線程互不搶占CPU資源,可以同時(shí)進(jìn)行,這種方式我們稱之為并行(Parallel)。
區(qū)別:
并行是指兩個(gè)或者多個(gè)事件在同一時(shí)刻發(fā)生;
并發(fā)是指兩個(gè)或多個(gè)事件在同一時(shí)間間隔內(nèi)發(fā)生。是指在一段時(shí)間內(nèi)宏觀上有多個(gè)程序在同時(shí)運(yùn)行,在單處理機(jī)系統(tǒng)中,每一時(shí)刻卻僅能有一道程序執(zhí)行,故微觀上這些程序只能是分時(shí)地交替執(zhí)行。
2.2 進(jìn)程、線程的概念及其聯(lián)系和區(qū)別
進(jìn)程的概念
進(jìn)程是表示資源分配的基本單位。它是一個(gè)執(zhí)行某一個(gè)特定程序的實(shí)體,它擁有獨(dú)立的地址空間、執(zhí)行堆棧、文件描述符等。
線程的概念
有時(shí)被稱為輕量級(jí)進(jìn)程,線程是進(jìn)程中執(zhí)行運(yùn)算的最小單位,亦即執(zhí)行處理機(jī)調(diào)度的基本單位。
進(jìn)程和線程的聯(lián)系
一個(gè)進(jìn)程至少擁有一個(gè)線程——主線程,也可以擁有多個(gè)線程;一個(gè)線程必須有一個(gè)父進(jìn)程。
多個(gè)進(jìn)程可以并發(fā)執(zhí)行;一個(gè)線程可以創(chuàng)建和撤銷另一個(gè)線程;同一個(gè)進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。
進(jìn)程與線程的區(qū)別:
調(diào)度:線程作為CPU調(diào)度和分配的基本單位,進(jìn)程作為擁有資源(內(nèi)存資源)的基本單位
并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個(gè)進(jìn)程的多個(gè)線程之間也可并發(fā)執(zhí)行
擁有資源:進(jìn)程是擁有資源的一個(gè)獨(dú)立單位,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源.
系統(tǒng)開銷:在創(chuàng)建或撤消進(jìn)程時(shí),由于系統(tǒng)都要為之分配和回收資源,導(dǎo)致系統(tǒng)的開銷明顯大于創(chuàng)建或撤消線程時(shí)的開銷。
2.3 阻塞、非阻塞、同步和異步的概念
同步/異步調(diào)用:
同步:
所謂同步,就是在發(fā)出一個(gè)功能調(diào)用時(shí),在沒有得到結(jié)果之前,該調(diào)用就不返回。也就是必須一件一件事做,等前一件做完了才能做下一件事。
異步:
異步的概念和同步相對。當(dāng)一個(gè)異步過程調(diào)用發(fā)出后,調(diào)用者不能立刻得到結(jié)果。實(shí)際處理這個(gè)調(diào)用的部件在完成后,通過狀態(tài)、通知和回調(diào)來通知調(diào)用者。
阻塞/非阻塞:
阻塞:
是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起(線程進(jìn)入非可執(zhí)行狀態(tài),在這個(gè)狀態(tài)下,cpu不會(huì)給線程分配時(shí)間片,即線程暫停運(yùn)行)。函數(shù)只有在得到結(jié)果之后才會(huì)返回。
阻塞調(diào)用和同步調(diào)用實(shí)際上是不同的。對于同步調(diào)用來說,很多時(shí)候當(dāng)前線程還是激活的,只是從邏輯上當(dāng)前函數(shù)沒有返回而已。
非阻塞:
非阻塞和阻塞的概念相對應(yīng),指在不能立刻得到結(jié)果之前,該函數(shù)不會(huì)阻塞當(dāng)前線程,而會(huì)立刻返回。(一般采用輪詢方式,沒好會(huì)返回沒完成,然后繼續(xù)輪詢,好了就返回完成)
2.4 介紹linux fork實(shí)現(xiàn)的原理
fork相當(dāng)于復(fù)制了一個(gè)進(jìn)程的執(zhí)行版本。以當(dāng)前進(jìn)程作為父進(jìn)程創(chuàng)建出一個(gè)新的子進(jìn)程,并且將父進(jìn)程的所有資源拷貝給子進(jìn)程,這樣子進(jìn)程作為父進(jìn)程的一個(gè)副本存在。父子進(jìn)程幾乎時(shí)完全相同的,但也有不同的如父子進(jìn)程pid不同。
fork后,父子進(jìn)程具有相同的數(shù)據(jù)空間、代碼空間、堆棧、所有的文件描述字;但相互之間互不影響。
fork函數(shù)有三個(gè)返回值
- 該進(jìn)程為父進(jìn)程時(shí),返回子進(jìn)程的pid
- 該進(jìn)程為子進(jìn)程時(shí),返回0
- fork執(zhí)行失敗,返回-1
那么fork函數(shù)為什么是一次調(diào)用,卻返回了兩次呢?
當(dāng)程序執(zhí)行到下面的語句: pid=fork();
由于在復(fù)制時(shí)復(fù)制了父進(jìn)程的堆棧段,所以兩個(gè)進(jìn)程都停留在fork函數(shù)中,等待返回。因此fork函數(shù)會(huì)返回兩次,一次是在父進(jìn)程中返回,另一次是在子進(jìn)程中返回,這兩次的返回值是不一樣的。
2.5 exec函數(shù)的使用
系統(tǒng)調(diào)用execl執(zhí)行另一個(gè)程序。調(diào)用execl并不創(chuàng)建新進(jìn)程,所以前后的進(jìn)程ID并未改變,execl只是用另一個(gè)新程序替換了當(dāng)前進(jìn)程的正文、數(shù)據(jù)、堆棧;
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
path 是要執(zhí)行的二進(jìn)制文件或腳本的完整路徑。
arg是要傳給程序的完整參數(shù)列表,包括arg[0],一般是執(zhí)行程序的名字。
最后一個(gè)參數(shù)可為NULL
exec函數(shù)一共有六個(gè),其中execve為內(nèi)核級(jí)系統(tǒng)調(diào)用,其他(execl,execle,execlp,execv,execvp)都是調(diào)用execve的庫函數(shù)。fork是分身術(shù),exec變身術(shù)。
2.6 Linux 下五種I/O模型
-
阻塞I/O
進(jìn)程會(huì)一直阻塞,直到數(shù)據(jù)拷貝完成
-
非阻塞I/O
非阻塞IO通過進(jìn)程反復(fù)調(diào)用IO函數(shù)(多次系統(tǒng)調(diào)用,并馬上返回);在數(shù)據(jù)拷貝的過程中,進(jìn)程是阻塞的;
-
I/O復(fù)用(select 和poll)
可以同時(shí)對多個(gè)讀操作,多個(gè)寫操作的I/O函數(shù)進(jìn)行檢測,直到有數(shù)據(jù)可讀或可寫時(shí),才真正調(diào)用I/O操作函數(shù)。
image -
信號(hào)驅(qū)動(dòng)I/O
允許套接口進(jìn)行信號(hào)驅(qū)動(dòng)I/O,并使用一個(gè)信號(hào)處理函數(shù),進(jìn)程繼續(xù)運(yùn)行并不阻塞。當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí),進(jìn)程會(huì)收到一個(gè)SIGIO信號(hào),可以在信號(hào)處理函數(shù)中調(diào)用I/O操作函數(shù)處理數(shù)據(jù)。
image -
異步I/O
數(shù)據(jù)拷貝的時(shí)候進(jìn)程無需阻塞
image
區(qū)別和比較
同步IO引起進(jìn)程阻塞,直至IO操作完成。IO復(fù)用是先通過select調(diào)用阻塞。異步IO不會(huì)引起進(jìn)程阻塞。
同步IO和異步IO的區(qū)別就在于:數(shù)據(jù)拷貝的時(shí)候進(jìn)程是否阻塞!
阻塞IO和非阻塞IO的區(qū)別就在于:應(yīng)用程序的調(diào)用是否立即返回!

第三章
3.1 什么是套接字?
套接字是一個(gè)主機(jī)本地網(wǎng)絡(luò)應(yīng)用程序所創(chuàng)建的, 為操作系統(tǒng)所控制的接口 (“門”) .
應(yīng)用進(jìn)程通過這個(gè)接口,使用傳輸層提供的服務(wù), 跨網(wǎng)絡(luò)發(fā)送(或接收)消息.
Client/server模式的通信接口——套接字接口.
主動(dòng)和被動(dòng)套接字
創(chuàng)建方式相同,使用方式不同
等待傳入連接的套接字——被動(dòng),如服務(wù)器套接字
發(fā)起連接的套接字——主動(dòng),如客戶套接字
指明端點(diǎn)地址:創(chuàng)建時(shí)不指定,使用時(shí)指明,允許協(xié)議族自由的選擇地址表示方式
TCP/IP需要指明協(xié)議端口號(hào)和IP地址
-
TCP/IP協(xié)議族和地址族的對應(yīng):
TCP/IP協(xié)議族:PF_INET
對應(yīng)的TCP/IP的地址族:AF_INET
3.2 socket調(diào)用的參數(shù)含義
PF_INET: TCP/IP的協(xié)議族
AF_INET: TCP/IP的地址族
struct sockaddr 是通用的地址結(jié)構(gòu)
struct socketaddr_in 是IP專用的地址結(jié)構(gòu)
SSOCK_DGRAM: 數(shù)據(jù)報(bào)服務(wù),UDP協(xié)議
SSOCK_STREAM: 流服務(wù),TCP協(xié)議
3.4 socket調(diào)用的相關(guān)函數(shù)
通用函數(shù)
socket函數(shù)
int Socket( int domain, int type, int protocol)
功能:創(chuàng)建一個(gè)新的套接字,返回套接字描述符
參數(shù)說明:
domain:域類型,指明使用的協(xié)議棧,
AF_INET:IPv4協(xié)議,
AF_INET6:IPv6協(xié)議,
AF_LOCAL:Unix域協(xié)議,
AF_ROUTE:路由套接口,
AF_KEY:密鑰套接口
type: 指明需要的服務(wù)類型, AF_INET地址族下如
SOCK_DGRAM: 數(shù)據(jù)報(bào)服務(wù),UDP協(xié)議
SOCK_STREAM: 流服務(wù),TCP協(xié)議
SOCKET_RAW:提供傳輸層以下的協(xié)議,例如接收和發(fā)送ICMP報(bào)文
protocol:一般都取0(由系統(tǒng)根據(jù)服務(wù)類型選擇默認(rèn)的協(xié)議)
IPPROTO_TCP、
IPPROTO_UDP、
IPPROTO_ICMP
請創(chuàng)建一個(gè)用于TCP通信的套接字。
舉例:
s=socket(AF_INET,SOCK_STREAM,0)
send函數(shù)
int send(int sockfd, const void * data, int data_len, unsigned int flags)
功能:
在TCP連接上發(fā)送數(shù)據(jù),返回成功傳送數(shù)據(jù)的長度,出錯(cuò)時(shí)返回-1。
send會(huì)將外發(fā)數(shù)據(jù)復(fù)制到OS內(nèi)核中,也可以使用send發(fā)送面向連接的UDP報(bào)文。
參數(shù)說明:
sockfd:套接字描述符
data:指向要發(fā)送數(shù)據(jù)的指針
data_len:數(shù)據(jù)長度
flags:通常為0,設(shè)置為 MSG_DONTWAIT為非阻塞
記住如果send()函數(shù)的返回值小于len的話,則你需要再次發(fā)送剩下的數(shù)據(jù)。
802.3,MTU為1492B,如果包小于1K,那么send()一般都會(huì)一次發(fā)送光的。
舉例(p50):
send(s,req,strlen(req),0);另外可嘗試send的阻塞效果
recv函數(shù)
int recv(int sockfd, void *buf, int buf_len,unsigned int flags);
功能:
從TCP接收數(shù)據(jù),返回實(shí)際接收的數(shù)據(jù)長度,出錯(cuò)時(shí)返回-1。
服務(wù)器使用其接收客戶請求,客戶使用它接受服務(wù)器的應(yīng)答。如果沒有數(shù)據(jù),將阻塞。
如果TCP收到的數(shù)據(jù)大于(/小于)緩存的大小,只抽出能夠填滿緩存的足夠數(shù)據(jù)(/抽出所有數(shù)據(jù)并返回它實(shí)際接收的字節(jié)數(shù))。
也可以使用recv接收面向連接的UDP的報(bào)文,若緩存不能裝下整個(gè)報(bào)文,填滿緩存后剩下的數(shù)據(jù)將被丟棄。
參數(shù)說明:
Sockfd:套接字描述符
Buf:指向內(nèi)存塊的指針
Buf_len:內(nèi)存塊大小,以字節(jié)為單位
flags:一般為0(MSG_WAITALL接收到指定長度數(shù)據(jù)時(shí)才返回),設(shè)置為 MSG_DONTWAIT為非阻塞
舉例:
recv(sockfd,buf,8192,0)
sendto函數(shù)(UDP)
int sendto(int sockfd, const void * data, int data_len, unsigned int flags, struct sockaddr *remaddr,sock_len remaddr_len)
功能:基于UDP發(fā)送數(shù)據(jù)報(bào),返回實(shí)際發(fā)送的數(shù)據(jù)長度,出錯(cuò)時(shí)返回-1
參數(shù)說明:
sockfd:套接字描述符
data:指向要發(fā)送數(shù)據(jù)的指針
data_len:數(shù)據(jù)長度
flags:通常為0,設(shè)置為 MSG_DONTWAIT為非阻塞
remaddr:遠(yuǎn)端地址:IP地址和端口號(hào)
remaddr_len :地址長度
舉例:
sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&address, sizeof(address));
recvfrom函數(shù)(UDP)
int recvfrom(int sockfd, void *buf, int buf_len,unsigned int flags,struct sockaddr *from,sock_len *fromlen);
功能:從UDP接收數(shù)據(jù),返回實(shí)際接收的字節(jié)數(shù),失敗時(shí)返回-1
參數(shù)說明:
Sockfd:套接字描述符
buf:指向內(nèi)存塊的指針
buf_len:內(nèi)存塊大小,以字節(jié)為單位
flags:一般為0
from:遠(yuǎn)端的地址,IP地址和端口號(hào)
fromlen:遠(yuǎn)端地址長度
舉例:
recvfrom(sockfd,buf,8192,0,(struct sockaddr *)&address, &sizeof(address));
close函數(shù)
close(int sockfd);
功能:
撤銷套接字.
如果只有一個(gè)進(jìn)程使用,立即終止連接并撤銷該套接字,如果多個(gè)進(jìn)程共享該套接字,將引用數(shù)減一,如果引用數(shù)降到零,則關(guān)閉連接并撤銷套接字。
參數(shù)說明:
Sockfd:套接字描述符
舉例:
close(socket_descriptor)
服務(wù)端常用函數(shù)
bind函數(shù)
int bind(int sockfd,struct sockaddr * my_addr,int addrlen)
功能:為套接字指明一個(gè)本地端點(diǎn)地址
TCP/IP協(xié)議使用sockaddr_in結(jié)構(gòu),包含IP地址和端口號(hào),服務(wù)器使用它來指明熟知的端口號(hào),然后等待連接。
參數(shù)說明:
Sockfd: 套接字描述符,指明創(chuàng)建連接的套接字
my_addr: 本地地址,IP地址和端口號(hào)
addrlen: 地址長度
為什么TCP服務(wù)端需要調(diào)用bind函數(shù)而客戶端通常不需要呢?
客戶端使用socket服務(wù)時(shí),操作系統(tǒng)隨之指定一個(gè)不會(huì)產(chǎn)生沖突的端口給客戶端。在很多場景下, 我們要在一個(gè)pc上開啟多個(gè)客戶端進(jìn)程, 如果指定固定端口, 必然會(huì)造成端口沖突, 影響通信!
舉例:
struct sockaddr_in server_addr; /*服務(wù)器地址結(jié)構(gòu)*/ /*設(shè)置服務(wù)器地址*/ bzero(&server_addr, sizeof(server_addr)); /*清零*/ server_addr.sin_family = AF_INET; /*協(xié)議族*/ server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/ server_addr.sin_port = htons(PORT); /*服務(wù)器端口*/ /*綁定地址結(jié)構(gòu)到套接字描述符*/ err = bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr));
accept函數(shù)
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
功能:獲取傳入連接請求,返回新的連接的套接字描述符。
為每個(gè)新的連接請求創(chuàng)建了一個(gè)新的套接字,服務(wù)器只對新的連接使用該套接字,原來的監(jiān)聽套接字接收其他的連接請求。新的連接上傳輸數(shù)據(jù)使用新的套接字,使用完畢,服務(wù)器將關(guān)閉這個(gè)套接字。
參數(shù)說明:
Sockfd: 套接字描述符,指明正在監(jiān)聽的套接字
addr: 提出連接請求的主機(jī)地址
addrlen: 地址長度
舉例:
new_sockfd = accept(sockfd, (struct sockaddr *)&address, sizeof(address));
listen函數(shù)
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/**
* 監(jiān)聽socket
*
* @param sockfd socket文件描述符
* @param backlog 提示內(nèi)核監(jiān)聽隊(duì)列的最大長度
* @return 函數(shù)執(zhí)行成功返回0,失敗返回-1
*/
客戶端常用函數(shù)
connect函數(shù)
int connect(int sockfd,struct sockaddr *server_addr,int sockaddr_len)
功能: 同遠(yuǎn)程服務(wù)器建立主動(dòng)連接,成功時(shí)返回0,若連接失敗返回-1。
參數(shù)說明:
Sockfd: 套接字描述符,指明創(chuàng)建連接的套接字
Server_addr: 指明遠(yuǎn)程端點(diǎn):IP地址和端口號(hào)
sockaddr_len: 地址長度
舉例(P49):
/*連接服務(wù)器*/ connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
3.5 大端字節(jié)序、小端字節(jié)序的概念,理解其轉(zhuǎn)換的原理。
大于一個(gè)字節(jié)的變量類型的表示方法有兩種:
小端字節(jié)序(Little Endian,LE):在表示變量的內(nèi)存地址的起始地址存放低字節(jié),高字節(jié)順序存放;
大端字節(jié)序(Big Endian,BE):在表示變量的內(nèi)存地址的起始地址存放高字節(jié),低字節(jié)順序存放。
網(wǎng)絡(luò)字節(jié)順序:最高位字節(jié)在前(大端字節(jié)序)
主機(jī)字節(jié)序:一般和主機(jī)制造商的規(guī)定有關(guān),不同PC字節(jié)序也不同
主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); /*主機(jī)字節(jié)序到網(wǎng)絡(luò)字節(jié)序的長整型轉(zhuǎn)換*/
uint32_t ntohl(uint32_t netlong); /*網(wǎng)絡(luò)字節(jié)序到主機(jī)字節(jié)序的長整型轉(zhuǎn)換*/
uint16_t htons(uint16_t hostshort); /*主機(jī)字節(jié)序到網(wǎng)絡(luò)字節(jié)序的短整型轉(zhuǎn)換*/
uint16_t ntohs(uint16_t netshort); /*網(wǎng)絡(luò)字節(jié)序到主機(jī)字節(jié)序的短整型轉(zhuǎn)換*/
3.6 不精確指明協(xié)議軟件接口的優(yōu)缺點(diǎn)
TCP/IP和應(yīng)用程序之間的接口應(yīng)該是不精確指明的:
- 不規(guī)定接口的細(xì)節(jié)
- 只建議需要的功能集
- 允許系統(tǒng)設(shè)計(jì)者選擇有關(guān)API的具體實(shí)現(xiàn)細(xì)節(jié)
優(yōu)點(diǎn):
- 提供了靈活性和容錯(cuò)能力
- 便于各種OS實(shí)現(xiàn)TCP/IP
- 接口可以是過程的,也可以是消息的
缺點(diǎn):
- 不同的OS中的接口細(xì)節(jié)不同
- 廠商增加與現(xiàn)有API不同的新接口時(shí),編程更困難,移植性差
- 程序員需要重新學(xué)習(xí)接口知識(shí)
3.7 拓展Linux I/O 用于TCP/IP
擴(kuò)展文件描述符: 可以用于網(wǎng)絡(luò)通信
擴(kuò)展read和write: 可以用于網(wǎng)絡(luò)標(biāo)識(shí)符
額外功能的處理,增加新系統(tǒng)調(diào)用:
- 指明本地和遠(yuǎn)端的端口,遠(yuǎn)程IP地址
- 使用TCP還是UDP
- 啟動(dòng)傳輸還是等待傳入連接
- 可以接收多少傳入連接
第4章 TCP網(wǎng)絡(luò)編程基礎(chǔ)
4.1 TCP客戶-服務(wù)器模型實(shí)現(xiàn)的流程

服務(wù)端:
- 套接字初始化
socket() - 套接字與端口的綁定
bind() - 設(shè)置服務(wù)器的偵聽連接連接
listen() - 接受客戶端連接請求
accept() - 接收和發(fā)送數(shù)據(jù)
read()、write()并進(jìn)行數(shù)據(jù)處理 - 處理完畢的套接字關(guān)閉
close()
客戶端:
- 套接字初始化
socket() - 連接服務(wù)器
connect() - 讀寫網(wǎng)絡(luò)數(shù)據(jù)
read()、write()并進(jìn)行數(shù)據(jù)處理 - 最后套接字關(guān)閉
close()過程。
4.2 用戶層和內(nèi)核層交互過程
用戶層和內(nèi)核層交互過程
- 向內(nèi)核傳入數(shù)據(jù)的交互過程,向內(nèi)核傳入數(shù)據(jù)的函數(shù)有send()、bind()等
- 內(nèi)核傳出數(shù)據(jù)的交互過程,從內(nèi)核得到數(shù)據(jù)的函數(shù)有accept()、recv()等。
4.3 簡介信號(hào)處理的實(shí)現(xiàn)和其函數(shù)原型
信號(hào)是發(fā)生某件事情時(shí)的一個(gè)通知。信號(hào)將事件發(fā)送給相關(guān)的進(jìn)程,相關(guān)進(jìn)程可以對信號(hào)進(jìn)行捕捉并處理。信號(hào)的捕捉由系統(tǒng)自動(dòng)完成,信號(hào)處理函數(shù)的注冊通過函數(shù)signal()完成。
signal(int signum, sighandler_t handler);
參數(shù)signum指出要設(shè)置處理方法的信號(hào):
SIGINT:用于終止進(jìn)程運(yùn)行向當(dāng)前活動(dòng)的進(jìn)程發(fā)送這個(gè)信號(hào)。通常是由Ctrl+C終止進(jìn)程造成的,與Ctrl+C一 致,kill命令默認(rèn)發(fā)送SIGINT信號(hào)。
SIGPIPE:正在寫入套接字的時(shí)候,當(dāng)讀取端已經(jīng)關(guān)閉時(shí),可以得到一個(gè)SIGPIPE信號(hào)。
第二個(gè)參數(shù)handler是一個(gè)處理函數(shù)。
void sig_int(int sign)
{
printf("Catch a SIGINT signal\n");
/*釋放資源*/
}
signal(SIGINT, sig_int);
第5章 基于UDP協(xié)議的接收和發(fā)送
5.1 UDP編程框架

服務(wù)器端:
- 建立套接字文件描述符,使用函數(shù)
socket(),生成套接字文件描述符。 - 設(shè)置服務(wù)器地址和偵聽端口,初始化要綁定的網(wǎng)絡(luò)地址結(jié)構(gòu)。
- 綁定偵聽端口,使用
bind()函數(shù),將套接字文件描述符和一個(gè)地址類型變量進(jìn)行綁定。 - 接收客戶端的數(shù)據(jù),使用
recvfrom()函數(shù)接收客戶端的網(wǎng)絡(luò)數(shù)據(jù)。 - 向客戶端發(fā)送數(shù)據(jù),使用
sendto()函數(shù)向服務(wù)器主機(jī)發(fā)送數(shù)據(jù)。 - 關(guān)閉套接字,使用
close()函數(shù)釋放資源。
客戶端:
- 建立套接字文件描述符,
socket(); - 設(shè)置服務(wù)器地址和端口,
struct sockaddr_in; - 向服務(wù)器發(fā)送數(shù)據(jù),
sendto(); - 接收服務(wù)器的數(shù)據(jù),
recvfrom(); - 關(guān)閉套接字,
close()。
bind()
int s;
s = socket(AF_INET, SOCK_DGRAM, 0);
5.2 UDP協(xié)議程序設(shè)計(jì)中的幾個(gè)問題和解決辦法
-
UDP報(bào)文丟失數(shù)據(jù)
對策:客戶端和服務(wù)端會(huì)對超時(shí)的數(shù)據(jù)進(jìn)行重發(fā)。
-
UDP數(shù)據(jù)發(fā)送中的亂序
主要是由于路由的和路由的存儲(chǔ)轉(zhuǎn)發(fā)的順序不同造成的。路由器的存儲(chǔ)轉(zhuǎn)不同發(fā)可能造成數(shù)據(jù)順序的更改。
對策:可以采用發(fā)送端在數(shù)據(jù)段中加入數(shù)據(jù)報(bào)序號(hào)的方法
-
UDP協(xié)議中的connect()函數(shù)
connect()函數(shù)在TCP協(xié)議中會(huì)發(fā)生三次握手,建立一個(gè)持續(xù)的連接,一般不用于UDP。在UDP協(xié)議中使用connect()函數(shù)的作用僅僅表示確定了另一方的地址,并沒有其他的含義。
-
UDP缺乏流量控制
UDP協(xié)議沒有TCP協(xié)議所具有的滑動(dòng)窗口概念,接收數(shù)據(jù)的時(shí)候直接將數(shù)據(jù)放到緩沖區(qū)中。當(dāng)緩沖區(qū)滿的時(shí)候,后面到來的數(shù)據(jù)會(huì)覆蓋之前的數(shù)據(jù)造成數(shù)據(jù)的丟失。
對策:增大接收數(shù)據(jù)緩沖區(qū) 或 接收方接收單獨(dú)處理
第六講 客戶端軟件設(shè)計(jì)中的算法和問題
6.1 客戶標(biāo)識(shí)服務(wù)器位置的幾種方式
-
在編譯程序時(shí),將服務(wù)器的域名或者IP地址說明為常量
執(zhí)行快,但是服務(wù)器移動(dòng)后不便
-
要求用戶在啟動(dòng)程序時(shí)指定服務(wù)器
使用機(jī)器名,不必重新編譯客戶程序
-
從穩(wěn)定的存儲(chǔ)設(shè)備中獲得關(guān)于服務(wù)器的信息
如果文件不存在,客戶軟件就不能執(zhí)行
-
使用某個(gè)單獨(dú)的協(xié)議來找到服務(wù)器(如廣播或組播)
只能在本地小環(huán)境下應(yīng)用
6.2 為什么TCP調(diào)用recv接收數(shù)據(jù)時(shí)要進(jìn)行多次接收?
TCP不保持記錄的邊界,面向流的概念,多次接收。
原因:大塊數(shù)據(jù)被分片封裝發(fā)送或由于接收方接收緩沖小而數(shù)據(jù)被發(fā)方分次發(fā)送
/**
* 客戶發(fā)送請求,等待響應(yīng)
* 發(fā)送請求:send;
* 等待響應(yīng):recv;
*/
send(s, req, strlen(req), 0);
while ((n = recv (s, bptr, buflen, 0)) > 0)
{
bptr +=n;
buflen -=n;
}
6.3 gethostbyname、 getservbyname 和getprotobyname 的功能
gethostbyname:
主機(jī)域名到二進(jìn)制的轉(zhuǎn)換
接受一個(gè)機(jī)器域名字符串,返回一個(gè)hostent結(jié)構(gòu),內(nèi)含一個(gè)二進(jìn)制表示的主機(jī)IP地址
getservbyname:
兩個(gè)參數(shù)指明期望的服務(wù)和協(xié)議。返回servent類型的結(jié)構(gòu)指針;
注意按網(wǎng)絡(luò)字節(jié)序返回協(xié)議端口號(hào);
getprotobyname:
由協(xié)議名返回協(xié)議號(hào);
返回一個(gè)protoent類型結(jié)構(gòu)的地址
6.4 TCP連接到服務(wù)器
TCP客服服務(wù)器中客戶端connect接口完成的四項(xiàng)任務(wù)
- 對指明的套接字進(jìn)行檢測:有效,還沒有連接
- 將第二個(gè)參數(shù)給出的端點(diǎn)地址填入套接字中
- 為此套接字選擇一個(gè)本地端點(diǎn)地址
- 發(fā)起一個(gè)TCP連接,并返回一個(gè)值
6.5 簡述TCP面向連接的客戶端算法和UDP無連接客戶端算法
TCP 面向連接的算法:
- 分配套接字
- 找到期望與之通信的服務(wù)器IP地址和協(xié)議端口號(hào)
- 指明此連接需要在本地機(jī)器中的、任意的、未使用的協(xié)議端口,并允許TCP選擇一個(gè)這樣的端口
- 將這個(gè)套接字連接到服務(wù)器
- 使用應(yīng)用級(jí)協(xié)議與服務(wù)器通信
- 關(guān)閉連接
UDP無連接客戶端算法
- 分配套接字
- 找到期望與之通信的服務(wù)器IP地址和協(xié)議端口號(hào)
- 指明這種通信需要本地機(jī)器中的、任意的、未使用的協(xié)議端口,并允許UDP選擇一個(gè)這樣的端口
- 指明報(bào)文所要發(fā)往的服務(wù)器
- 使用應(yīng)用級(jí)協(xié)議與服務(wù)器通信
- 關(guān)閉連接
第七講 服務(wù)器軟件設(shè)計(jì)的算法和問題
本章與第八第九章內(nèi)容相似,故結(jié)合第八第九章一起復(fù)習(xí)。
7.1 無連接和面向連接的服務(wù)器訪問
使用TCP的服務(wù)器是面向連接的服務(wù)器
使用UDP的服務(wù)器是無連接的服務(wù)器
選擇要考慮TCP和UDP的語義特點(diǎn)
TCP的語義
- 點(diǎn)到點(diǎn)通信
- 建立可靠連接
- 可靠交付
- 具有流控的傳輸
- 雙工傳輸
- 流模式
UDP的語義
- 多對多通信
- 不可靠服務(wù)
- 缺乏流控制
- 報(bào)文模式
面向連接服務(wù)的優(yōu)點(diǎn):
-
易于編程
自動(dòng)處理分組丟失,分組失序問題
自動(dòng)驗(yàn)證數(shù)據(jù)差錯(cuò),處理連接狀態(tài)
面向連接服務(wù)的缺點(diǎn):
- 對每個(gè)連接都有一個(gè)單獨(dú)的套接字,耗費(fèi)更多的資源
- 在空閑的連接上不發(fā)送任何分組
- 始終運(yùn)行的服務(wù)器會(huì)因?yàn)榭蛻舻谋罎ⅲ瑢?dǎo)致無用套接字的過多而耗盡資源,終止運(yùn)行
無連接服務(wù)的優(yōu)點(diǎn):沒有資源耗盡問題
無連接服務(wù)的缺陷:需要自己完成可靠通信問題
- 必要時(shí),需要一種自適應(yīng)重傳的復(fù)雜技術(shù),需要程序員具有相當(dāng)?shù)膶I(yè)知識(shí)
- 對于不可靠通信的場合,盡量使用tcp
特殊情況
是否需要組播或者廣播是考慮選擇何種傳輸方式的一個(gè)因素
支持組播或者廣播的服務(wù)器必須是無連接的,今后會(huì)不斷增加這樣的應(yīng)用。
7.2 無狀態(tài)和有狀態(tài)的服務(wù)器應(yīng)用
狀態(tài)信息:服務(wù)器維護(hù)的,關(guān)于它和客戶正進(jìn)行的交互狀態(tài)信息
無狀態(tài)服務(wù)器:沒有保留任何狀態(tài)信息
狀態(tài)服務(wù)器:維護(hù)狀態(tài)信息的服務(wù)器
狀態(tài)問題源于對確??煽啃缘囊?特別對無連接傳輸
傳輸協(xié)議不能保證可靠,應(yīng)用協(xié)議的設(shè)計(jì)必須保證可靠
優(yōu)化服務(wù)器
- 在服務(wù)器加入大的文件緩存和信息索引可以改善服務(wù)器性能
- 需要程序員極其小心:檢查文件名、文件偏移等,重復(fù)、失序問題
- 如果客戶出了故障重新啟動(dòng),將會(huì)重新獲得一個(gè)不同的端口號(hào),先前的表項(xiàng)將會(huì)失去作用,最終會(huì)耗盡服務(wù)器資源
- 服務(wù)器可以選擇刪除LRU(最近最少使用)但是如果客戶經(jīng)常崩潰,可能讓服務(wù)器刪除一個(gè)合法的客戶條目
- 優(yōu)化無狀態(tài)服務(wù)器的時(shí)候,程序員必須及其小心
- 如果客戶經(jīng)常崩潰或者重啟,或者網(wǎng)絡(luò)使報(bào)文重復(fù)或者遲延,管理少量狀態(tài)信息也會(huì)消耗資源
7.3 一些概念&循環(huán)服務(wù)器是否夠用
觀測響應(yīng)時(shí)間
客戶發(fā)送請求到服務(wù)器響應(yīng)之間的全部時(shí)延。
請求處理時(shí)間
服務(wù)器處理單個(gè)孤立的請求所花費(fèi)的時(shí)間。
循環(huán)服務(wù)器觀測響應(yīng)時(shí)間為N/2+1的推導(dǎo)
假設(shè)服務(wù)器處理一個(gè)任務(wù)的時(shí)間為t,隊(duì)列第一個(gè)等待的時(shí)間是t ,第二個(gè)等待的時(shí)間為:2t ,這樣第N個(gè)等待的時(shí)間為Nt ,所以等待的總時(shí)間變?yōu)?t+2t+…+Nt=(1+N)Nt/2.所以平均等待時(shí)間為(1+N)Nt/2/N=
(1+N)t/2=(N/2+1/2)t,因此約等于(N/2+1 )個(gè)服務(wù)器處理時(shí)間.
判斷循環(huán)服務(wù)器是否夠用
如果一個(gè)服務(wù)器設(shè)計(jì)處理K個(gè)客戶,每個(gè)客戶每秒發(fā)送R個(gè)請求,服務(wù)器請求處理時(shí)間必須小于每請求1/KR秒。否則請求隊(duì)列將溢出。這時(shí)設(shè)計(jì)者必須考慮并發(fā)實(shí)現(xiàn)
7.4 服務(wù)器的四種基本類型及其特點(diǎn)
| 基本類型 | 特點(diǎn) |
|---|---|
| 循環(huán)的 無連接 | 對每個(gè)請求的處理少,通常為無狀態(tài)的,簡單服務(wù),計(jì)算少 |
| 循環(huán)的 面向連接 | 要求可靠傳輸?shù)?,對每個(gè)請求處理少的服務(wù) |
| 并發(fā)的 無連接 | 不常見,為每個(gè)請求創(chuàng)建一個(gè)新線程或進(jìn)程 |
| 并發(fā)的 面向連接 | 最一般的??煽總鬏?,并發(fā)處理多個(gè)請求。 |
7.5 四種基本類型服務(wù)器的算法
結(jié)合8,9章的進(jìn)程結(jié)構(gòu)模型圖,一起復(fù)習(xí)。
循環(huán)面向連接服務(wù)器算法
- 創(chuàng)建套接字并將其綁定到它所提供服務(wù)的熟知端口上;
- 將該端口設(shè)置為被動(dòng)模式,使其準(zhǔn)備為服務(wù)器所用;
- 從該套接字上接收下一個(gè)連接請求,獲得該連接的新的套接字;
- 在新套接字上重復(fù)地讀取來自客戶的數(shù)據(jù),構(gòu)造響應(yīng),按照應(yīng)用協(xié)議向客戶發(fā)回響應(yīng);
- 當(dāng)某個(gè)特定客戶完成交互時(shí),關(guān)閉連接,并返回步驟3以接受新的連接。
進(jìn)程結(jié)構(gòu)
使用一個(gè)單執(zhí)行線程
使用兩個(gè)套接字
- 一個(gè)套接字處理請求
- 另外一個(gè)套接字處理和客戶的通信(臨時(shí)的)

循環(huán)無連接服務(wù)器算法
- 創(chuàng)建套接字并將其綁定到所提供服務(wù)的熟知端口上;
- 重復(fù)讀取來自客戶的請求,構(gòu)造響應(yīng),按照應(yīng)用協(xié)議向客戶發(fā)回響應(yīng)。
進(jìn)程結(jié)構(gòu):只需要一個(gè)執(zhí)行進(jìn)程

并發(fā)無連接服務(wù)器算法
- 主1. 創(chuàng)建套接字并將其綁定到所提供服務(wù)的熟知地址上。讓該套接字保持為未連接的
- 主2. 反復(fù)調(diào)用recvfrom接收來自客戶的下一個(gè)請求,創(chuàng)建一個(gè)新的從線程來處理響應(yīng)
- 從1. 從來自主線程的特定請求以及到該套接字的訪問開始
- 從2. 根據(jù)應(yīng)用協(xié)議構(gòu)造應(yīng)答,并用sendto將該應(yīng)答發(fā)回給客戶
- 從3. 退出(即:從線程處理完一個(gè)請求后就終止)
并發(fā)面向連接的服務(wù)器算法
- 主1. 創(chuàng)建套接字并將其綁定到所提供服務(wù)的熟知地址上。讓該套接字保持為無連接的
- 主2. 將該端口設(shè)置為被動(dòng)模式
- 主3. 反復(fù)調(diào)用accept以便接收來自客戶的下一個(gè)連接請求,并創(chuàng)建新的從線程或者進(jìn)程來處理響應(yīng)
- 從1. 由主線程傳遞來的連接請求開始
- 從2. 用該連接與客戶進(jìn)行交互;讀取請求并發(fā)回響應(yīng)
- 從3. 關(guān)閉連接并退出
并發(fā)面向連接單線程進(jìn)程服務(wù)器進(jìn)程結(jié)構(gòu)
- 服務(wù)器包括一個(gè)主進(jìn)程,以及零個(gè)或者多個(gè)從進(jìn)程,每個(gè)進(jìn)程一個(gè)線程。與循環(huán)面向連接的服務(wù)器(右側(cè))進(jìn)程的區(qū)別!
- 主服務(wù)器使用accept阻塞調(diào)用,節(jié)約CPU資源,連接到來的時(shí)候,accept馬上返回。

單線程服務(wù)器的線程結(jié)構(gòu)
單線程、并發(fā)服務(wù)器的線程和套接字結(jié)構(gòu)
- 一個(gè)執(zhí)行線程管理所有的套接字

7.6 單線程實(shí)現(xiàn)表面并發(fā)、面向連接的服務(wù)器算法
可以通過共享內(nèi)存的線程達(dá)到期望的并發(fā),但當(dāng):
- 出現(xiàn)在服務(wù)器的請求沒有超過服務(wù)器的處理能力可以獲得表面上的并發(fā)。
- 單線程的服務(wù)器使用select系統(tǒng)調(diào)用進(jìn)行I/O復(fù)用
- 創(chuàng)建套接字并將其綁定到這個(gè)服務(wù)的熟知端口上,將該套接字加到一個(gè)表中,該表中的項(xiàng)是可以進(jìn)行I/O的描述符。
- 使用select在已經(jīng)有的套接字上等待I/O
- 如果最初的套接字準(zhǔn)備就緒,使用accept獲得下一個(gè)連接,并將這個(gè)新的套接字加入到表中,該表中的項(xiàng)是可以進(jìn)行I/O的描述符。
- 如果最初的套接字以外的套接字就緒,就使用recv或read獲得下一個(gè)請求,構(gòu)造響應(yīng),用send或者write將響應(yīng)發(fā)回給客戶
- 繼續(xù)按照以上的步驟2進(jìn)行處理
客戶的問題或者使用了阻塞系統(tǒng)調(diào)用的情況對于循環(huán)服務(wù)器以及使用單線程實(shí)現(xiàn)的并發(fā)服務(wù)中死鎖都可能發(fā)生
7.7 Windows和linux下socket編程區(qū)別
- 頭文件
windows下winsock.h或winsock2.h linux下netinet/in.h、<sys/socket.h> - 初始化
windows下需要用WSAStartup啟動(dòng)Ws2_32.lib,并且要用#pragma comment(lib,"Ws2_32")來告知編譯器鏈接該lib。linux下不需要 - 關(guān)閉socket
windows下closesocket(...) linux下close(...) - 獲取錯(cuò)誤碼
windows下getlasterror()/WSAGetLastError() linux下,未能成功執(zhí)行的socket操作會(huì)返回-1;如果包含了errno.h,就會(huì)設(shè)置errno變量 - 多線程/多進(jìn)程
windows下包含process.h,使用_beginthread和_endthread,進(jìn)程創(chuàng)建CreateProcess() linux下包含pthread.h,使用pthread_create和pthread_exit,進(jìn)程創(chuàng)建fork() - 用IP定義一個(gè)地址(sockaddr_in的結(jié)構(gòu)的區(qū)別)
名稱相同,都是struct sockaddr、struct sockaddr_in,這兩者通常轉(zhuǎn)換使用;在Windows下面名稱都是大寫,而在Linux下為小寫
windows下addr_var.sin_addr.S_un.S_addr linux下addr_var.sin_addr.s_addr 而且Winsock里最后那個(gè)32bit的S_addr也有幾個(gè)以聯(lián)合(Union)的形式與它共享內(nèi)存空間的成員變量(便于以其他方式賦值),而Linux的Socket沒有這個(gè)聯(lián)合,就是一個(gè)32bit的s_addr。
第8章 循環(huán)服務(wù)器
8.1 一些服務(wù)的模型選擇
TIME服務(wù)器選用什么模型最合適?
TIME服務(wù)幾乎不需要什么計(jì)算,可以利用循環(huán)實(shí)現(xiàn)
Time服務(wù)需要實(shí)時(shí)性,因此選擇無連接方式
因此循環(huán)無連接服務(wù)器模型最為合適
循環(huán)的面向連接的服務(wù)器每處理一個(gè)連接循環(huán)一次
連接達(dá)到以前在accept阻塞
建立新的連接以后創(chuàng)建新套接字處理
處理完畢,關(guān)閉,返回accept阻塞
DAYTIME服務(wù)
不需要客戶的請求信息,檢測到連接就響應(yīng)
發(fā)送完響應(yīng),服務(wù)器主動(dòng)關(guān)閉連接
每個(gè)連接只發(fā)送一個(gè)響應(yīng)
DAYTIME服務(wù)用循環(huán)面向連接的服務(wù)模型比較好
第9章 并發(fā)服務(wù)器
9.1 并發(fā)服務(wù)器類型有幾種?
- 并發(fā)的面向連接的服務(wù)器
- 并發(fā)無連接服務(wù)器
- 單線程表面并發(fā)服務(wù)器
9.2 線程的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
-
更高的效率:上下文切換的額外開銷減少
- 上下文切換:線程切換需要執(zhí)行的指令
- 同一進(jìn)程中的兩個(gè)線程比不同進(jìn)程中的兩個(gè)線程切換要快
- 進(jìn)程內(nèi)的線程切換不用改變虛擬存儲(chǔ)器的映射
-
共享存儲(chǔ):
- 并發(fā)服務(wù)器中的多個(gè)副本需要相互通信或者訪問共享的數(shù)據(jù)
- 利用線程容易構(gòu)造監(jiān)控系統(tǒng)
缺點(diǎn)
由于線程間共享存儲(chǔ)和進(jìn)程狀態(tài),一個(gè)線程的動(dòng)作可能對同一個(gè)進(jìn)程內(nèi)的其他線程產(chǎn)生影響。
- 兩個(gè)線程如果同一時(shí)刻訪問同一個(gè)變量,會(huì)產(chǎn)生相互干擾
- 調(diào)用一個(gè)靜態(tài)的數(shù)據(jù)項(xiàng)的庫函數(shù)不是線程安全(thread safe)的,覆蓋將會(huì)導(dǎo)致錯(cuò)誤
- 缺乏健壯性,一個(gè)線程出錯(cuò),服務(wù)器將會(huì)終止整個(gè)進(jìn)程
并發(fā)服務(wù)器可以在一個(gè)進(jìn)程中用若干線程實(shí)現(xiàn)
優(yōu)點(diǎn)是:
- 開銷少
- 共享存儲(chǔ)器
- 可以監(jiān)控
缺點(diǎn)是:
- 增加了編程的復(fù)雜性
- 必須使用同步機(jī)制(信號(hào)量和條件變量)協(xié)調(diào)線程對全局變量和一些庫程序的訪問
- 必須弄清一些可能影響整個(gè)進(jìn)程的系統(tǒng)函數(shù)
9.3 單線程服務(wù)器的線程結(jié)構(gòu)
單線程并發(fā)服務(wù)器必須完成原來主線程和從線程雙方的職責(zé)
- 維護(hù)一組套接字
- 組中主套接字綁定到接受連接的熟知端口上
- 其它從套接字對應(yīng)一個(gè)連接
- 服務(wù)器把這組套接字描述符作為一個(gè)參數(shù)傳遞給select,并等待任何一個(gè)套接字的活動(dòng),select返回一個(gè)屏蔽位,指明哪個(gè)套接字就緒,服務(wù)器再?zèng)Q定如何處理。
- 使用描述符來區(qū)別主套接字和從套接字的操作
- 主套接字描述符準(zhǔn)備就緒,使用原來主線程的操作accept
- 從套接字的描述符就緒,使用原來從線程的操作read
9.4 select
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
- int maxfdp是一個(gè)整數(shù)值,是指描述符集合中所有文件描述符的范圍,即所有文件描述符的最大值加1;
- fd_set *readfds是指向fd_set結(jié)構(gòu)的指針,如果有一個(gè)文件可讀,select就會(huì)返回一個(gè)大于0的值,表示有文件可讀
- fd_set *writefds,如果有一個(gè)文件可寫,select就會(huì)返回一個(gè)大于0的值
- fd_set *errorfds同上面兩個(gè)參數(shù)的意圖,用來監(jiān)視文件錯(cuò)誤異常。
- struct timeval* timeout是select的超時(shí)時(shí)間,
Select函數(shù)有三種執(zhí)行結(jié)果:
- 永遠(yuǎn)等待下去:僅在有一個(gè)或一個(gè)以上描述字準(zhǔn)備好i/o才返回,為此,我們將timeout設(shè)置為空指針。
- 等待固定時(shí)間:在有一個(gè)描述字準(zhǔn)備好時(shí)返回,但不超過由timeout參數(shù)指定的秒數(shù)和微秒數(shù)。
- 根本不等待,檢查描述字后立即返回,這稱為輪詢。這種情況下,timeout必須指向結(jié)構(gòu)timeval,且定時(shí)器的值必須為0。
Select函數(shù)的返回值如下:
- 如果在指定超時(shí)值到達(dá)之前有一個(gè)或多個(gè)描述字滿足條件,則函數(shù)返回值大于零;
- 如果超時(shí)時(shí)間到時(shí),沒有描述字滿足條件,函數(shù)返回值為0;
第十講 復(fù)雜服務(wù)器設(shè)計(jì)
10.1 復(fù)雜服務(wù)器
多協(xié)議服務(wù)器(TCP、UDP)
一個(gè)服務(wù)器的服務(wù)可以同時(shí)在TCP和UDP傳輸協(xié)議之上來提供。多服務(wù)服務(wù)器( DayTIME、TIME、Echo )
利用同一臺(tái)服務(wù)器提供多種服務(wù)多協(xié)議多服務(wù)服務(wù)器(DayTIME、TIME、Echo 、TCP、UDP)
利用同一臺(tái)服務(wù)器提供多種服務(wù),而且可以通過不同的協(xié)議進(jìn)行傳輸。
10.2 復(fù)雜服務(wù)器的進(jìn)程結(jié)構(gòu)
循環(huán)多協(xié)議服務(wù)器進(jìn)程結(jié)構(gòu)

在任何時(shí)候,一個(gè)循環(huán)的多協(xié)議服務(wù)器至少打開3個(gè)套接字。最初,服務(wù)器打開一個(gè)UDP和一個(gè)TCP套接字。當(dāng)一個(gè)請求到達(dá)UDP套接字,服務(wù)器會(huì)計(jì)算出相應(yīng),通過UDP套接字返回給客戶端。當(dāng)一個(gè)TCP請求到達(dá)TCP套接字時(shí),服務(wù)器調(diào)用accept獲得這個(gè)新的連接。accept為這個(gè)連接創(chuàng)建第三個(gè)套接字,服務(wù)器使用這個(gè)套接字與客戶端通信。一旦交互結(jié)束,服務(wù)器將關(guān)閉第三個(gè)套接字,并等待另外兩個(gè)套接字激活。
單線程并發(fā)多協(xié)議服務(wù)器進(jìn)程結(jié)構(gòu)

單線程并發(fā)多協(xié)議服務(wù)器是有多個(gè)用于一個(gè)TCP鏈連接的套接字
循環(huán)無連接、多服務(wù)服務(wù)器設(shè)計(jì)

打開一組套接字,每一個(gè)套接字與一個(gè)熟知服務(wù)相對應(yīng)。服務(wù)器使用select系統(tǒng)調(diào)用的等待任一套接字的數(shù)據(jù)報(bào)的到達(dá)。
循環(huán)面向連接多服務(wù)器設(shè)計(jì)

先為每一種服務(wù)創(chuàng)建一個(gè)套接字,并將該套接字綁定在熟知服務(wù)端口上,使用select等待任一套接字上傳入連接請求。只要有一個(gè)套接字就緒,服務(wù)器就調(diào)用accept獲得這個(gè)新連接。accept為這個(gè)傳入連接創(chuàng)建新的套接字。服務(wù)器使用這個(gè)新的套接字與客戶交互,之后便將關(guān)閉。除了主套接字外,服務(wù)器在任何時(shí)候最多只有一個(gè)打開的附加套接字。
并發(fā)、面向連接、多服務(wù)服務(wù)器設(shè)計(jì)

當(dāng)一個(gè)連接請求到達(dá)時(shí),服務(wù)器就調(diào)用一個(gè)進(jìn)程,接受并直接處理這個(gè)新的連接,或者,他也可以創(chuàng)建一個(gè)新的從進(jìn)程來處理這個(gè)新連接。實(shí)際上,一個(gè)多服務(wù)器程序可以設(shè)計(jì)成循環(huán)的處理某些服務(wù),而對其他一些服務(wù)則并發(fā)處理。
單線程表面并發(fā)面向連接多服務(wù)器設(shè)計(jì)

當(dāng)這個(gè)多服務(wù)服務(wù)器開始執(zhí)行時(shí),它先為每個(gè)服務(wù)創(chuàng)建一個(gè)套接字,并將該套接字綁定在熟知服務(wù)端口上,,使用select等待任一套接字上傳入連接請求。只要有一個(gè)套接字就緒,服務(wù)器就調(diào)用accept獲得這個(gè)新連接。accept為這個(gè)傳入連接創(chuàng)建新的套接字。服務(wù)器使用這個(gè)新的套接字與客戶交互,之后便將關(guān)閉。除了主套接字外,服務(wù)器在任何時(shí)候最多只有一個(gè)打開的附加套接字。
從多服務(wù)服務(wù)器調(diào)用單獨(dú)的程序

主服務(wù)器使用fork創(chuàng)建一個(gè)新進(jìn)程來處理每個(gè)鏈接。然而,與以前的設(shè)計(jì)不同,從進(jìn)程以調(diào)用execve的方式用一個(gè)新的程序替代原來的代碼,這個(gè)新的程序?qū)⑻幚硭械目蛻舳送ㄐ拧母拍钌蟻砜?,使用execve就把處理各個(gè)服務(wù)同設(shè)立在連接的主服務(wù)器代碼分離開了。
第十一講 服務(wù)器并發(fā)性的統(tǒng)一、高效管理
11.1 為什么需要在服務(wù)器中進(jìn)行從進(jìn)程/線程的預(yù)分配
- 當(dāng)創(chuàng)建進(jìn)程/線程時(shí)間較長的時(shí)候,也能維持高吞吐量。
- 主服務(wù)器在開始執(zhí)行時(shí)就創(chuàng)建N個(gè)從進(jìn)程/線程
- 將所接受的新的請求分配給這N個(gè)從進(jìn)程/線程中的一個(gè)處理
- 進(jìn)程/線程并不退出
11.2 結(jié)合圖示,介紹無連接服務(wù)器預(yù)分配的進(jìn)程結(jié)構(gòu)

算法:多個(gè)從線程同時(shí)綁定在一個(gè)socket上調(diào)用recvfrom獲得發(fā)送方的地址和其發(fā)送的數(shù)據(jù)報(bào),并調(diào)用sendto應(yīng)答。一個(gè)數(shù)據(jù)報(bào)到達(dá)的時(shí)候系統(tǒng)只喚醒一個(gè)從進(jìn)程。
無連接服務(wù)器并發(fā)等級(jí)取決于到達(dá)的請求數(shù)
如果某操作系統(tǒng)在調(diào)用recvfrom時(shí),會(huì)同時(shí)激活所有的從進(jìn)程,請問應(yīng)該使用什么技術(shù)手段加以解決,并簡要描述。
互斥,調(diào)用recvfrom前申請互斥,pthread_mutex_lock調(diào)用結(jié)束pthread_mutex_unlock
11.3 結(jié)合圖示,介紹有連接服務(wù)器預(yù)分配的進(jìn)程結(jié)構(gòu)

所有的從進(jìn)程繼承了對熟知端口套接字的訪問。當(dāng)各個(gè)從進(jìn)程調(diào)用accept返回時(shí),它接受新套接字以用于這個(gè)連接。雖然主進(jìn)程創(chuàng)建了對應(yīng)熟知端口的套接字,但是它并不使用這個(gè)套接字進(jìn)行其他操作。圖中虛線表明主進(jìn)程使用該套接字的方式與從進(jìn)程不同。
面向連接服務(wù)器的并發(fā)等級(jí)與活躍的連接數(shù)有關(guān)
預(yù)分配方案怎樣用于不能進(jìn)程并發(fā)調(diào)用accept的系統(tǒng)中?
使用一個(gè)共享的互斥量mutex或文件鎖定,以便保證任何時(shí)候只有一個(gè)從線程能夠調(diào)用accept。


