<TCP/IP網(wǎng)絡編程> Chap.4 基于TCP的服務器端/客戶端(1)

實現(xiàn)基于TCP的服務器端

#include <sys/socket.h>
/* 
 * @params
 *   sock: 服務器套接字文件描述符。
 *   backlog: 等待連接的請求的隊列長度
 */
int listen(int sock, int backlog);    // 0: 成功。 -1: 失敗。
#include <sys/socket.h>
/* 
 * @params
 *   sock: 服務器套接字文件描述符。這里的套接字只是為了接收連接的,因為連接本身也是一種數(shù)據(jù),需要用套接字來接收。
 *   addr: 客戶端地址。傳入時是空值,接收到連接后填入該客戶端的地址信息。
 *   addrlen: 第二個參數(shù)addr結(jié)構(gòu)的長度。在調(diào)用后填入。
 */
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);    // 成功返回新創(chuàng)建的套接字文件描述符,失敗返回-1。


實現(xiàn)基于TCP的客戶端

#include <sys/socket.h>
/* 
 * @params
 *   sock: 客戶端套接字文件描述符。這里的套接字只是為了接收連接的,因為連接本身也是一種數(shù)據(jù),需要用套接字來接收。
 *   addr: 目標服務器地址。
 *   addrlen: 第二個參數(shù)addr結(jié)構(gòu)的長度。
 */
int connect(int sock, struct sockaddr *addr, socklen_t addrlen);    // 0: 成功。 -1: 失敗。

客戶端的IP地址就是主機的IP地址,端口在調(diào)用connect()時自動由內(nèi)核隨機分配。
發(fā)生以下兩種情況之一就會返回:

  1. 服務器端將請求記錄到等待隊列。
  2. 發(fā)生斷網(wǎng)等異常情況而中斷請求。

實現(xiàn)迭代服務器/客戶端

# ./eserver 9190
Connected client 1 
Connected client 2 
Connected client 3 
Connected client 4 
Connected client 5 
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): orange
Message from server: orange
Input message (Q to quit): bottle
Message from server: bottle
Input message (Q to quit): q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): when      
Message from server: when
Input message (Q to quit): where
Message from server: where
Input message (Q to quit): q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): FIRST
Message from server: FIRST
Input message (Q to quit): Q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): sunny
Message from server: sunny
Input message (Q to quit): smile
Message from server: smile
Input message (Q to quit): q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): last one
Message from server: last one
Input message (Q to quit): q

這只是一個簡單的例子而已。實際上客戶端服務器的處理不是很謹慎,如果他們不在同一臺機器上,發(fā)生了網(wǎng)絡延遲等狀況,可能會粘包或分包。

習題

  1. 請說明TCP/IP的4層協(xié)議棧,并說明TCP和UDP套接字經(jīng)過的層級結(jié)構(gòu)差異。
    從低到高依次是數(shù)據(jù)鏈路層、網(wǎng)絡層、傳輸層和應用層。TCP和UDP的差異在傳輸層。
  2. 請說出TCP/IP協(xié)議棧中鏈路層和IP層的作用,并給出兩者關系。
    鏈路層提供物理連接,IP層基于物理鏈路選擇合適的路徑。
  3. 為何需要把TCP/IP協(xié)議棧分成4層(或7層)?結(jié)合開放式系統(tǒng)回答。
    為了通過標準化操作設計開放式系統(tǒng)。
  4. 客戶端調(diào)用connect函數(shù)向服務器端發(fā)送連接請求。服務器端調(diào)用哪個函數(shù)后,客戶端可以調(diào)用connect函數(shù)?
    必須在服務器端調(diào)用listen函數(shù)后。
  5. 什么時候創(chuàng)建連接請求等待隊列?它有何作用?與accept有什么關系?
    listen函數(shù)創(chuàng)建連接請求等待隊列。使得同時只能處理一個客戶端連接的服務器暫存其他客戶端的連接,以待后續(xù)處理。accept從隊列中取第一個進行服務。
  6. 客戶端中為何不需要調(diào)用bind函數(shù)分配地址?如果不調(diào)用bind函數(shù),那何時、如何向套接字分配IP地址和端口號?
    因為在這里客戶端是主動發(fā)起連接的一端,它不需要在某個固定的地址和端口去監(jiān)聽連接。在調(diào)用connect的時候內(nèi)核會自動隨機分配地址和端口,服務器可以根據(jù)收到的消息解析出客戶端的地址和端口。
  7. 把第1章的hello_server.c改成迭代服務器段,并利用客戶端測試更改是否準確。
    同本章例子。


我的問題

  1. 服務器端是阻塞在listen()還是accept()?
    阻塞在accept()。listen()的作用只是使主動連接套接字變?yōu)楸贿B接套接字,使得一個進程可以接受其它進程的請求。因此客戶端的connect()可以發(fā)生在listen()的前面或后面。如果在前面則進入等待隊列,如果在后面則直接被服務器接受請求。
  2. 為什么循環(huán)接收同一個client的數(shù)據(jù),判斷read()返回值用0?
    當接收隊列為空,且本端或?qū)Χ苏{(diào)用shutdown或close連接,read()才會返回零。并不是接收隊列為空就返回0。所以該函數(shù)能一直讀到client退出輸入循環(huán)。


附錄

[1] 探討read的返回值的三種情況
[2] Github

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

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