網(wǎng)絡(luò)程序設(shè)計(jì)(C語言)復(fù)習(xí)筆記

第一章 引言和網(wǎng)絡(luò)編程基礎(chǔ)知識(shí)

1.1 分別簡述OSI參考模型和TCP/IP模型,并闡述他們之間的對應(yīng)關(guān)系

image

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):

  1. C/S架構(gòu)的界面和操作可以很豐富。
  2. 安全性能可以很容易保證,實(shí)現(xiàn)多層認(rèn)證也不難。
  3. 由于只有一層交互,因此響應(yīng)速度較快。

缺點(diǎn):

  1. 用戶群固定。由于程序需要安裝才可使用,因此不適合面向一些不可知的用戶。
  2. 維護(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):

  1. 客戶端無需安裝,有Web瀏覽器即可。
  2. B/S架構(gòu)可以直接放在廣域網(wǎng)上,通過一定的權(quán)限控制實(shí)現(xiàn)多客戶訪問的目的,交互性較強(qiáng)。
  3. B/S架構(gòu)無需升級(jí)多個(gè)客戶端,升級(jí)服務(wù)器即可。

缺點(diǎn):

  1. 在跨瀏覽器上,B/S架構(gòu)不盡如人意。
  2. 表現(xiàn)要達(dá)到C/S程序的程度需要花費(fèi)不少精力。
  3. 在速度和安全性上需要花費(fèi)巨大的設(shè)計(jì)成本,這是B/S架構(gòu)的最大問題。
  4. 客戶端服務(wù)器端的交互是請求-響應(yīng)模式,通常需要刷新頁面,這并不是客戶樂意看到的。

1.4 應(yīng)用程序在什么情況下建議使用UDP

UDP: 無連接交互

  • 沒有可靠保證
  • 依賴下層系統(tǒng)保證
  • 程序中應(yīng)該有相應(yīng)保障措施

TCP: 面向連接的交互

  • 提供傳輸可靠性
  • 程序要求簡單

應(yīng)用程序只在以下情況使用UDP:

  1. 應(yīng)用程序指明必須使用UDP;
  2. 應(yīng)用程序協(xié)議要依靠硬件進(jìn)行廣播或組播
  3. 應(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ū)別:

  1. 調(diào)度:線程作為CPU調(diào)度和分配的基本單位,進(jìn)程作為擁有資源(內(nèi)存資源)的基本單位

  2. 并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個(gè)進(jìn)程的多個(gè)線程之間也可并發(fā)執(zhí)行

  3. 擁有資源:進(jìn)程是擁有資源的一個(gè)獨(dú)立單位,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源.

  4. 系統(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模型

  1. 阻塞I/O

    進(jìn)程會(huì)一直阻塞,直到數(shù)據(jù)拷貝完成

  2. 非阻塞I/O

    非阻塞IO通過進(jìn)程反復(fù)調(diào)用IO函數(shù)(多次系統(tǒng)調(diào)用,并馬上返回);在數(shù)據(jù)拷貝的過程中,進(jìn)程是阻塞的;

  3. I/O復(fù)用(select 和poll)

    可以同時(shí)對多個(gè)讀操作,多個(gè)寫操作的I/O函數(shù)進(jìn)行檢測,直到有數(shù)據(jù)可讀或可寫時(shí),才真正調(diào)用I/O操作函數(shù)。

    image
  4. 信號(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
  5. 異步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)用是否立即返回!

image

第三章

3.1 什么是套接字?

  1. 套接字是一個(gè)主機(jī)本地網(wǎng)絡(luò)應(yīng)用程序所創(chuàng)建的, 為操作系統(tǒng)所控制的接口 (“門”) .

  2. 應(yīng)用進(jìn)程通過這個(gè)接口,使用傳輸層提供的服務(wù), 跨網(wǎng)絡(luò)發(fā)送(或接收)消息.

  3. 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):

  1. 提供了靈活性和容錯(cuò)能力
  2. 便于各種OS實(shí)現(xiàn)TCP/IP
  3. 接口可以是過程的,也可以是消息的

缺點(diǎn):

  1. 不同的OS中的接口細(xì)節(jié)不同
  2. 廠商增加與現(xiàn)有API不同的新接口時(shí),編程更困難,移植性差
  3. 程序員需要重新學(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)的流程

image

服務(wù)端:

  1. 套接字初始化socket()
  2. 套接字與端口的綁定bind()
  3. 設(shè)置服務(wù)器的偵聽連接連接listen()
  4. 接受客戶端連接請求accept()
  5. 接收和發(fā)送數(shù)據(jù)read()、write()并進(jìn)行數(shù)據(jù)處理
  6. 處理完畢的套接字關(guān)閉close()

客戶端:

  1. 套接字初始化socket()
  2. 連接服務(wù)器connect()
  3. 讀寫網(wǎng)絡(luò)數(shù)據(jù)read()、write()并進(jìn)行數(shù)據(jù)處理
  4. 最后套接字關(guān)閉close()過程。

4.2 用戶層和內(nèi)核層交互過程

用戶層和內(nèi)核層交互過程

  1. 向內(nèi)核傳入數(shù)據(jù)的交互過程,向內(nèi)核傳入數(shù)據(jù)的函數(shù)有send()、bind()等
  2. 內(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編程框架

image

服務(wù)器端:

  1. 建立套接字文件描述符,使用函數(shù)socket(),生成套接字文件描述符。
  2. 設(shè)置服務(wù)器地址和偵聽端口,初始化要綁定的網(wǎng)絡(luò)地址結(jié)構(gòu)。
  3. 綁定偵聽端口,使用bind()函數(shù),將套接字文件描述符和一個(gè)地址類型變量進(jìn)行綁定。
  4. 接收客戶端的數(shù)據(jù),使用recvfrom()函數(shù)接收客戶端的網(wǎng)絡(luò)數(shù)據(jù)。
  5. 向客戶端發(fā)送數(shù)據(jù),使用sendto()函數(shù)向服務(wù)器主機(jī)發(fā)送數(shù)據(jù)。
  6. 關(guān)閉套接字,使用close()函數(shù)釋放資源。

客戶端:

  1. 建立套接字文件描述符,socket();
  2. 設(shè)置服務(wù)器地址和端口,struct sockaddr_in
  3. 向服務(wù)器發(fā)送數(shù)據(jù),sendto();
  4. 接收服務(wù)器的數(shù)據(jù),recvfrom();
  5. 關(guān)閉套接字,close()

bind()

int s;
s =  socket(AF_INET, SOCK_DGRAM, 0);

5.2 UDP協(xié)議程序設(shè)計(jì)中的幾個(gè)問題和解決辦法

  1. UDP報(bào)文丟失數(shù)據(jù)

    對策:客戶端和服務(wù)端會(huì)對超時(shí)的數(shù)據(jù)進(jìn)行重發(fā)。

  2. UDP數(shù)據(jù)發(fā)送中的亂序

    主要是由于路由的和路由的存儲(chǔ)轉(zhuǎn)發(fā)的順序不同造成的。路由器的存儲(chǔ)轉(zhuǎn)不同發(fā)可能造成數(shù)據(jù)順序的更改。

    對策:可以采用發(fā)送端在數(shù)據(jù)段中加入數(shù)據(jù)報(bào)序號(hào)的方法

  3. UDP協(xié)議中的connect()函數(shù)

    connect()函數(shù)在TCP協(xié)議中會(huì)發(fā)生三次握手,建立一個(gè)持續(xù)的連接,一般不用于UDP。在UDP協(xié)議中使用connect()函數(shù)的作用僅僅表示確定了另一方的地址,并沒有其他的含義。

  4. 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ù)器位置的幾種方式

  1. 在編譯程序時(shí),將服務(wù)器的域名或者IP地址說明為常量

    執(zhí)行快,但是服務(wù)器移動(dòng)后不便

  2. 要求用戶在啟動(dòng)程序時(shí)指定服務(wù)器

    使用機(jī)器名,不必重新編譯客戶程序

  3. 從穩(wěn)定的存儲(chǔ)設(shè)備中獲得關(guān)于服務(wù)器的信息

    如果文件不存在,客戶軟件就不能執(zhí)行

  4. 使用某個(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 面向連接的算法:

  1. 分配套接字
  2. 找到期望與之通信的服務(wù)器IP地址和協(xié)議端口號(hào)
  3. 指明此連接需要在本地機(jī)器中的、任意的、未使用的協(xié)議端口,并允許TCP選擇一個(gè)這樣的端口
  4. 將這個(gè)套接字連接到服務(wù)器
  5. 使用應(yīng)用級(jí)協(xié)議與服務(wù)器通信
  6. 關(guān)閉連接

UDP無連接客戶端算法

  1. 分配套接字
  2. 找到期望與之通信的服務(wù)器IP地址和協(xié)議端口號(hào)
  3. 指明這種通信需要本地機(jī)器中的、任意的、未使用的協(xié)議端口,并允許UDP選擇一個(gè)這樣的端口
  4. 指明報(bào)文所要發(fā)往的服務(wù)器
  5. 使用應(yīng)用級(jí)協(xié)議與服務(wù)器通信
  6. 關(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ù)器算法

  1. 創(chuàng)建套接字并將其綁定到它所提供服務(wù)的熟知端口上;
  2. 將該端口設(shè)置為被動(dòng)模式,使其準(zhǔn)備為服務(wù)器所用;
  3. 從該套接字上接收下一個(gè)連接請求,獲得該連接的新的套接字;
  4. 在新套接字上重復(fù)地讀取來自客戶的數(shù)據(jù),構(gòu)造響應(yīng),按照應(yīng)用協(xié)議向客戶發(fā)回響應(yīng);
  5. 當(dāng)某個(gè)特定客戶完成交互時(shí),關(guān)閉連接,并返回步驟3以接受新的連接。

進(jìn)程結(jié)構(gòu)

使用一個(gè)單執(zhí)行線程

使用兩個(gè)套接字

  • 一個(gè)套接字處理請求
  • 另外一個(gè)套接字處理和客戶的通信(臨時(shí)的)
image

循環(huán)無連接服務(wù)器算法

  1. 創(chuàng)建套接字并將其綁定到所提供服務(wù)的熟知端口上;
  2. 重復(fù)讀取來自客戶的請求,構(gòu)造響應(yīng),按照應(yīng)用協(xié)議向客戶發(fā)回響應(yīng)。

進(jìn)程結(jié)構(gòu):只需要一個(gè)執(zhí)行進(jìn)程

image

并發(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馬上返回。
image

單線程服務(wù)器的線程結(jié)構(gòu)

單線程、并發(fā)服務(wù)器的線程和套接字結(jié)構(gòu)

  • 一個(gè)執(zhí)行線程管理所有的套接字
image

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ù)用
  1. 創(chuàng)建套接字并將其綁定到這個(gè)服務(wù)的熟知端口上,將該套接字加到一個(gè)表中,該表中的項(xiàng)是可以進(jìn)行I/O的描述符。
  2. 使用select在已經(jīng)有的套接字上等待I/O
  3. 如果最初的套接字準(zhǔn)備就緒,使用accept獲得下一個(gè)連接,并將這個(gè)新的套接字加入到表中,該表中的項(xiàng)是可以進(jìn)行I/O的描述符。
  4. 如果最初的套接字以外的套接字就緒,就使用recv或read獲得下一個(gè)請求,構(gòu)造響應(yīng),用send或者write將響應(yīng)發(fā)回給客戶
  5. 繼續(xù)按照以上的步驟2進(jìn)行處理

客戶的問題或者使用了阻塞系統(tǒng)調(diào)用的情況對于循環(huán)服務(wù)器以及使用單線程實(shí)現(xiàn)的并發(fā)服務(wù)中死鎖都可能發(fā)生

7.7 Windows和linux下socket編程區(qū)別

  1. 頭文件
    windows下winsock.h或winsock2.h linux下netinet/in.h、<sys/socket.h>
  2. 初始化
    windows下需要用WSAStartup啟動(dòng)Ws2_32.lib,并且要用#pragma comment(lib,"Ws2_32")來告知編譯器鏈接該lib。linux下不需要
  3. 關(guān)閉socket
    windows下closesocket(...) linux下close(...)
  4. 獲取錯(cuò)誤碼
    windows下getlasterror()/WSAGetLastError() linux下,未能成功執(zhí)行的socket操作會(huì)返回-1;如果包含了errno.h,就會(huì)設(shè)置errno變量
  5. 多線程/多進(jìn)程
    windows下包含process.h,使用_beginthread和_endthread,進(jìn)程創(chuàng)建CreateProcess() linux下包含pthread.h,使用pthread_create和pthread_exit,進(jìn)程創(chuàng)建fork()
  6. 用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ù)器選用什么模型最合適?

  1. TIME服務(wù)幾乎不需要什么計(jì)算,可以利用循環(huán)實(shí)現(xiàn)

  2. Time服務(wù)需要實(shí)時(shí)性,因此選擇無連接方式

  3. 因此循環(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ù)器

  1. 多協(xié)議服務(wù)器(TCP、UDP)
    一個(gè)服務(wù)器的服務(wù)可以同時(shí)在TCP和UDP傳輸協(xié)議之上來提供。

  2. 多服務(wù)服務(wù)器( DayTIME、TIME、Echo )
    利用同一臺(tái)服務(wù)器提供多種服務(wù)

  3. 多協(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)

image

在任何時(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)

image

單線程并發(fā)多協(xié)議服務(wù)器是有多個(gè)用于一個(gè)TCP鏈連接的套接字

循環(huán)無連接、多服務(wù)服務(wù)器設(shè)計(jì)

image

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

循環(huán)面向連接多服務(wù)器設(shè)計(jì)

image

先為每一種服務(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ì)

image

當(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ì)

image

當(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ú)的程序

image

主服務(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)

image

算法:多個(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)

image

所有的從進(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容