第 4 章 基于 TCP 的服務(wù)端/客戶端(1)
????????根據(jù)數(shù)據(jù)傳輸方式的不同,基于網(wǎng)絡(luò)協(xié)議的套接字一般分為 TCP 套接字和 UDP 套接字。因?yàn)?TCP 套接字是面向連接的,因此又被稱為基于流(stream)的套接字。????????
????????TCP 是 Transmission Control Protocol (傳輸控制協(xié)議)的簡寫,意為「對(duì)數(shù)據(jù)傳輸過程的控制」。
? ? ? ? 下圖為TCP/IP 協(xié)議棧:
????????TCP/IP 協(xié)議棧共分為 4 層,可以理解為數(shù)據(jù)收發(fā)分成了 4 個(gè)層次化過程,通過層次化的方式來解決問題。 下面分別介紹一下4個(gè)層:
-
鏈路層
????????鏈路層是物理鏈接領(lǐng)域標(biāo)準(zhǔn)化的結(jié)果,也是最基本的領(lǐng)域,專門定義LAN、WAN、MAN等網(wǎng)絡(luò)標(biāo)準(zhǔn)。若兩臺(tái)主機(jī)通過網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)交換,則需要物理連接,鏈路層就負(fù)責(zé)這些標(biāo)準(zhǔn)。
-
IP 層
? ? ? ? 準(zhǔn)備好物理連接后就要傳輸數(shù)據(jù)。為了在復(fù)雜網(wǎng)絡(luò)中傳輸數(shù)據(jù),首先要考慮路徑的選擇。向目標(biāo)傳輸數(shù)據(jù)需要經(jīng)過哪條路徑?解決此問題的就是IP層,該層使用的協(xié)議就是IP。
? ? ? ? ?IP 是面向消息的、不可靠的協(xié)議。每次傳輸數(shù)據(jù)時(shí)會(huì)幫我們選擇路徑,但并不一致。如果傳輸過程中發(fā)生錯(cuò)誤,則選擇其他路徑,但是如果發(fā)生數(shù)據(jù)丟失或錯(cuò)誤,則無法解決。換言之,IP協(xié)議無法應(yīng)對(duì)數(shù)據(jù)錯(cuò)誤。
-
TCP/UDP 層
? ? ? ? IP 層解決數(shù)據(jù)傳輸中的路徑選擇問題,只需照此路徑傳輸數(shù)據(jù)即可。TCP 和 UDP 層以 IP 層提供的路徑信息為基礎(chǔ)完成實(shí)際的數(shù)據(jù)傳輸,故該層又稱為傳輸層。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??TCP 可以保證數(shù)據(jù)的可靠傳輸,但是它發(fā)送數(shù)據(jù)時(shí)以 IP 層為基礎(chǔ)(這也是協(xié)議棧層次化的原因)。IP 層只關(guān)注一個(gè)數(shù)據(jù)包(數(shù)據(jù)傳輸基本單位)的傳輸過程。因此,即使傳輸多個(gè)數(shù)據(jù)包,每個(gè)數(shù)據(jù)包也是由 IP 層實(shí)際傳輸?shù)模簿褪钦f傳輸順序及傳輸本身是不可靠的。若只利用IP層傳輸數(shù)據(jù),則可能導(dǎo)致后傳輸?shù)臄?shù)據(jù)包B比先傳輸?shù)臄?shù)據(jù)包A提早到達(dá)。另外,傳輸?shù)臄?shù)據(jù)包A、B、C中可能只收到A和C,甚至收到的C可能已經(jīng)損毀 。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?反之,若添加 TCP 協(xié)議則按照如下對(duì)話方式進(jìn)行數(shù)據(jù)交換:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
這就是 TCP 的作用。如果交換數(shù)據(jù)的過程中可以確認(rèn)對(duì)方已經(jīng)收到數(shù)據(jù),并重傳丟失的數(shù)據(jù),那么即便IP層不保證數(shù)據(jù)傳輸,這類通信也是可靠的。
-
應(yīng)用層
? ? ? ? 上述內(nèi)容是套接字通信過程中自動(dòng)處理的。選擇數(shù)據(jù)傳輸路徑、數(shù)據(jù)確認(rèn)過程都被隱藏到套接字內(nèi)部。向程序員提供的工具就是套接字,只需要利用套接字編出程序即可。編寫軟件的過程中,需要根據(jù)程序的特點(diǎn)來決定服務(wù)器和客戶端之間的數(shù)據(jù)傳輸規(guī)則,這便是應(yīng)用層協(xié)議。
????????實(shí)現(xiàn)基于 TCP 的服務(wù)器/客戶端:
? ? ? ? 下圖為TCP 服務(wù)端的默認(rèn)函數(shù)的調(diào)用程序 :
- 調(diào)用 socket 函數(shù)創(chuàng)建套接字,聲明并初始化地址信息的結(jié)構(gòu)體變量,調(diào)用 bind 函數(shù)向套接字分配地址。
-
進(jìn)入等待連接請求狀態(tài):
已經(jīng)調(diào)用了 bind 函數(shù)給套接字分配地址,接下來就是要通過調(diào)用 listen 函數(shù)進(jìn)入等待鏈接請求狀態(tài)。只有調(diào)用了 listen 函數(shù),客戶端才能進(jìn)入可發(fā)出連接請求的狀態(tài)??蛻舳丝梢哉{(diào)用 connect 函數(shù),向服務(wù)端請求連接,對(duì)于客戶端發(fā)來的請求,先進(jìn)入連接請求等待隊(duì)列,等待服務(wù)端受理請求。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//成功時(shí)返回0,失敗時(shí)返回-1
//sock: 希望進(jìn)入等待連接請求狀態(tài)的套接字文件描述符,傳遞的描述符套接字參數(shù)稱為服務(wù)端套接字
//backlog: 連接請求等待隊(duì)列的長度,若為5,則隊(duì)列長度為5,表示最多使5個(gè)連接請求進(jìn)入隊(duì)列
-
受理客戶端連接請求:
調(diào)用 listen 函數(shù)后,套接字應(yīng)該按序受理客戶端發(fā)起的連接請求。受理請求就是服務(wù)端處理一個(gè)連接請求,進(jìn)入可接受客戶端數(shù)據(jù)的狀態(tài)。進(jìn)入這種狀態(tài)所需的部件是套接字,但是此時(shí)使用的不是服務(wù)端套接字,此時(shí)需要另一個(gè)套接字,但是沒必要親自創(chuàng)建,下面的函數(shù)將自動(dòng)創(chuàng)建套接字。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*
成功時(shí)返回文件描述符,失敗時(shí)返回-1
sock: 服務(wù)端套接字的文件描述符
addr: 受理的請求中,客戶端地址信息會(huì)保存到該指針指向的地址
addrlen: 該指針指向的地址中保存第二個(gè)參數(shù)的結(jié)構(gòu)體長度
*/
????????
????????accept 函數(shù)受理連接請求隊(duì)列中待處理的客戶端連接請求。函數(shù)調(diào)用成功后,accept 內(nèi)部將產(chǎn)生用于數(shù)據(jù) I/O 的套接字,并返回其文件描述符。需要強(qiáng)調(diào)的是套接字是自動(dòng)創(chuàng)建的,并自動(dòng)與發(fā)起連接請求的客戶端建立連接。
注意:accept 函數(shù)返回的套接字不等于服務(wù)端套接字,也需要通過 close 函數(shù)關(guān)閉。
? ? ? ? 下圖為TCP 客戶端的默認(rèn)函數(shù)調(diào)用順序:
????????與服務(wù)端相比,區(qū)別就在于「請求連接」,它是創(chuàng)建客戶端套接字后向服務(wù)端發(fā)起的連接請求。服務(wù)端調(diào)用 listen 函數(shù)后創(chuàng)建連接請求等待隊(duì)列,之后客戶端即可請求連接。
#include <sys/socket.h>
int connect(int sock, struct sockaddr *servaddr, socklen_t addrlen);
/*
成功時(shí)返回0,失敗返回-1
sock:客戶端套接字文件描述符
servaddr: 保存目標(biāo)服務(wù)器端地址信息的變量地址值
addrlen: 第二個(gè)結(jié)構(gòu)體參數(shù) servaddr 變量的字節(jié)長度
*/
客戶端調(diào)用 connect 函數(shù)后,發(fā)生以下函數(shù)之一才會(huì)返回(完成函數(shù)調(diào)用):
- 服務(wù)端接受連接請求
- 發(fā)生斷網(wǎng)等異常狀況而中斷連接請求
????????注意:接受連接不代表服務(wù)端調(diào)用 accept 函數(shù),其實(shí)只是服務(wù)器端把連接請求信息記錄到等待隊(duì)列。因此 connect 函數(shù)返回后并不應(yīng)該立即進(jìn)行數(shù)據(jù)交換。
????????客戶端在調(diào)用connect函數(shù)時(shí)自動(dòng)分配主機(jī)的IP,隨機(jī)分配端口。無需調(diào)用標(biāo)記的bind函數(shù)進(jìn)行分配。
????????
? ? ? ? 下圖為基于 TCP 的服務(wù)端/客戶端函數(shù)調(diào)用關(guān)系:
- 客戶端只能等到服務(wù)端調(diào)用 listen 函數(shù)后才才能調(diào)用 connect 函數(shù)
- 服務(wù)器端可能會(huì)在客戶端調(diào)用 connect 之前調(diào)用 accept 函數(shù),這時(shí)服務(wù)器端進(jìn)入阻塞(blocking)狀態(tài),直到客戶端調(diào)用 connect 函數(shù)后接收到連接請求。
實(shí)驗(yàn):實(shí)現(xiàn)迭代服務(wù)端/客戶端:
????????程序運(yùn)行的基本方式:
- 服務(wù)器端在同一時(shí)刻只與一個(gè)客戶端相連,并提供回聲服務(wù)。
- 服務(wù)器端依次向 5 個(gè)客戶端提供服務(wù)并退出。
- 客戶端接受用戶輸入的字符串并發(fā)送到服務(wù)器端。
- 服務(wù)器端將接受的字符串?dāng)?shù)據(jù)傳回客戶端,即「回聲」
- 服務(wù)器端與客戶端之間的字符串回聲一直執(zhí)行到客戶端輸入 Q 為止。
服務(wù)器端:
5個(gè)客戶端:
(客戶端沒顯示完全)
????????在一個(gè)服務(wù)端開啟后,用另一個(gè)終端窗口開啟客戶端,然后程序會(huì)讓你輸入字符串,然后客戶端輸入什么字符串,客戶端就會(huì)返回什么字符串,按 q 退出。這時(shí)服務(wù)端的運(yùn)行并沒有結(jié)束,服務(wù)端一共要處理 5 個(gè)客戶端的連接,所以另外開多個(gè)終端窗口同時(shí)開啟客戶端,服務(wù)器按照順序進(jìn)行處理。
回聲客戶端存在的問題:
????????以上客戶端代碼有一個(gè)假設(shè)「每次調(diào)用 read、write函數(shù)時(shí)都會(huì)以字符串為單位執(zhí)行實(shí)際 I/O 操作」
????????但是「第二章」中說過「TCP 不存在數(shù)據(jù)邊界」,上述客戶端是基于 TCP 的,因此多次調(diào)用 write 函數(shù)傳遞的字符串有可能一次性傳遞到服務(wù)端。此時(shí)客戶端有可能從服務(wù)端收到多個(gè)字符串,這不是我們想要的結(jié)果。還需要考慮服務(wù)器的如下情況:
「字符串太長,需要分 2 個(gè)包發(fā)送!」
????????服務(wù)端希望通過調(diào)用 1 次 write 函數(shù)傳輸數(shù)據(jù),但是如果數(shù)據(jù)太大,操作系統(tǒng)就有可能把數(shù)據(jù)分成多個(gè)數(shù)據(jù)包發(fā)送到客戶端。另外,在此過程中,客戶端可能在尚未收到全部數(shù)據(jù)包時(shí)就調(diào)用 read 函數(shù)。
????????以上的問題都是源自 TCP 的傳輸特性,解決方法在第 5 章。
習(xí)題:
1、請你說明 TCP/IP 的 4 層協(xié)議棧,并說明 TCP 和 UDP 套接字經(jīng)過的層級(jí)結(jié)構(gòu)差異。
????????TCP/IP 的四層協(xié)議分為:應(yīng)用層、TCP/UDP 層、IP層、鏈路層。TCP和UDP套接字在協(xié)議棧中的位置相同(傳輸層),但TCP提供了可靠的、面向連接的通信,而UDP提供了不可靠的、無連接的通信。
2、請說出 TCP/IP 協(xié)議棧中鏈路層和IP層的作用,并給出二者關(guān)系。
????????鏈路層的作用是在物理網(wǎng)絡(luò)上傳輸數(shù)據(jù)幀,進(jìn)行封裝與解封裝、物理尋址以及差錯(cuò)檢測與糾正。IP層的作用是處理網(wǎng)絡(luò)互連問題,進(jìn)行數(shù)據(jù)包分組與封裝,并包含源地址和目標(biāo)地址的信息。鏈路層和IP層是TCP/IP協(xié)議棧中的不同層級(jí),鏈路層提供物理傳輸?shù)墓δ?,而IP層負(fù)責(zé)處理網(wǎng)絡(luò)的互連,將數(shù)據(jù)包從源主機(jī)傳輸?shù)侥繕?biāo)主機(jī)。
3、客戶端調(diào)用 connect 函數(shù)向服務(wù)器端發(fā)送請求。服務(wù)器端調(diào)用哪個(gè)函數(shù)后,客戶端可以調(diào)用 connect 函數(shù)?
????????服務(wù)端調(diào)用 listen 函數(shù)后,客戶端可以調(diào)用 connect 函數(shù)。因?yàn)?,服?wù)端調(diào)用 listen 函數(shù)后,服務(wù)端套接字才有能力接受請求連接的信號(hào)。
4、什么時(shí)候創(chuàng)建連接請求等待隊(duì)列?它有何種作用?與 accept 有什么關(guān)系?
????????服務(wù)端調(diào)用 listen 函數(shù)后,accept函數(shù)正在處理客戶端請求時(shí), 更多的客戶端發(fā)來了請求連接的數(shù)據(jù),此時(shí),就需要?jiǎng)?chuàng)建連接請求等待隊(duì)列。以便于在accept函數(shù)處理完手頭的請求之后,按照正確的順序處理后面正在排隊(duì)的其他請求。????????
????????與accept函數(shù)的關(guān)系:accept函數(shù)受理連接請求等待隊(duì)列中待處理的客戶端連接請求。
5、客戶端中為何不需要調(diào)用 bind 函數(shù)分配地址?如果不調(diào)用 bind 函數(shù),那何時(shí)、如何向套接字分配IP地址和端口號(hào)?文章來源:http://www.zghlxwxcb.cn/news/detail-558734.html
????????在客戶端IP地址和端口在調(diào)用 connect 函數(shù)時(shí)自動(dòng)分配,無需調(diào)用標(biāo)記的 bind 函數(shù)進(jìn)行分配。文章來源地址http://www.zghlxwxcb.cn/news/detail-558734.html
到了這里,關(guān)于《TCP IP網(wǎng)絡(luò)編程》第四章的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!