第一章 網絡編程基礎知識
網絡由節點和連線構成。現實用應用中的網絡由硬件設備(路由器、交換機、網線)+應用軟件組成。
計算機網路技術發展的第一個里程碑以報文或分組交換技術的出現爲標誌。
數據交換的三種主要形式:
- 電路交換:在電路交換方式中,通過網絡節點(交換設備)在工作站之間建立專用的通信通道,即在兩個工作站之間建立實際的物理連接。一旦通信線路建立,這對端點就獨佔該條物理通道,直至通信線路被取消。
- 報文交換:報文是一個帶有目的端信息和控制信息的數據包。報文交換採取的是“存儲—轉發” 方式,不需要在通信的兩個節點之間建立專用的物理線路。
- 分組交換:它是報文交換的一種改進,也屬於存儲-轉發交換方式,但它不是以報文爲單位,而是以長度受到限制的報文分組爲單位進行傳輸交換的。
計算機網絡技術發展的第二個里程碑:開放式系統互聯參考模型OSI/RM
第三個里程碑:Internet的迅速發展與推廣。
OSI模型層次圖:
OSI模型各層對應的實際對象舉例如下:
應用層--- 計算機:應用程序,如FTP,SMTP,HTTP等
表示層--- 計算機:編碼方式,圖像編解碼、URL字段編碼等
會話層--- 計算機:建立會話,SESSION認證、斷點續傳等
傳輸層---計算機:進程和端口等
網絡層--- 網絡:路由器,防火牆、多層交換機等
數據鏈路層--- 網絡:網卡,網橋,交換機等
物理層--- 網絡:中繼器、集線器、HUB等
TCP/IP模型與OSI模型對照:
TCP/IP模型中的數據傳輸:
TCP/IP協議的特點:
- 開放的協議標準
- 獨立於特定的計算機硬件與操作系統
- 獨立於特定的網絡硬件。可以運行在局域網、廣域網,更適用於互聯網中
- 統一的網絡地址分配方案,網絡中的設備都有唯一的地址
- 標準化的高層協議,可以提供多種可靠的服務
C/S客戶服務器模型的優缺點:
優點:
- C/S架構的界面和操作可以很豐富
- 安全性能可以個很容易保證,實現多層認證也不難
- 由於只有一層交互,因此響應速度較快
缺點:
- 用戶羣固定。程序需要安裝才能使用,不適合面向一些不可知的用戶
- 維護成本高,發生一次升級,所有的客戶端程序都需要改變。
B/S架構的全稱爲Browser/Server,即瀏覽器/服務器結構。主要事務邏輯在服務器端實現,B/S架構的系統無須特別安裝,只有Web瀏覽器即可。因此也被成爲瘦客戶端。
必須強調的是C/S和B/S並沒有本質的區別:B/S是基於特定通信協議(HTTP)的C/S架構,也就是說B/S包含在C/S中,是特殊的C/S架構。
B/S優點和缺點
優點:
1)客戶端無需安裝,有Web瀏覽器即可。
2)B/S架構可以直接放在廣域網上,通過一定的權限控制實現多客戶訪問的目的,交互性較強。
3)B/S架構無需升級多個客戶端,升級服務器即可。
缺點:
1)在跨瀏覽器上,B/S架構不盡如人意。
2)表現要達到C/S程序的程度需要花費不少精力。
3)在速度和安全性上需要花費巨大的設計成本,這是B/S架構的最大問題。
4)客戶端服務器端的交互是請求-響應模式,通常需要刷新頁面,這並不是客戶樂意看到的。
無連接和面向連接的服務器
UDP:無連接交互
沒有可靠保證
依賴下層系統保證
程序中應該有相應的保障措施
TCP:面向連接的交互
提供傳輸可靠性
程序要求簡單
選用UDP的情況:
下層系統可靠(例如在局域網環境)
應用不需要額外的可靠處理
廣播或者組播
無狀態和有狀態服務器
服務器所維護的與客戶交互的信息成爲狀態信息。不保存任何狀態信息的服務器成爲無狀態服務器,反之成爲有狀態服務器。
狀態服務器在服務器中保存少量信息,可減少客戶端與服務器端交換報文的大小,保存了客戶之前有過的請求,允許服務器快速的相應請求。
無狀態服務器的動機是協議的不可靠性。通常情況下報文丟失、重複或交付失序,或者客戶端程序崩潰都會使服務器的狀態信息不正確,此時就可能產生不正確的響應。無狀態服務器則不會因爲這些原因出現問題。
有狀態服務器具有以下特點:
- 保存客戶請求的數據(狀態)
- 服務端容易對客戶狀態進行管理
- 服務端並不要求每次客戶請求都攜帶額外的狀態數據
無狀態服務器具有以下特點:
- 不保存客戶請求的數據
- 客戶在請求時需要攜帶額外的狀態數據
- 無狀態服務器更加健壯,重啓服務器不會丟書狀態信息,這使得維護和擴容更加簡單
無狀態服務器最著名的就是WEB服務器
互聯網結構有三個基本原則,交互性,開放性,端到端
互聯網上的網絡應用需求、使用方法千差萬別,但可以大體上分爲三類:
按通信方式:“客戶端/服務器” “遊覽器/WEB服務器”和“點對點”
第二章 客戶服務器軟件中的併發處理
1、併發、並行的概念及其區別。
併發:當有多個線程在操作時,如果系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間劃分成若干個時間段,再將時間 段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處於掛起狀。這種方式我們稱之爲併發(Concurrent)。
並行:當系統有一個以上CPU時, 當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶佔CPU資源,可以同時進行,這種方式我們稱之爲並行(Parallel)。
區別:
並行是指兩個或者多個事件在同一時刻發生;
併發是指兩個或多個事件在同一時間間隔內發生。是指在一段時間內宏觀上有多個程序在同時運行,在單處理機系統中,每一時刻卻僅能有一道程序執行,故微觀上這些程序只能是分時地交替執行。
如果在計算機系統中有多個處理機,則這些可以併發執行的程序便可被分配到多個處理機上,實現並行執行,即利用每個處理機來處理一個可併發執行的程序,這樣,多個程序便可以同時執行。
2、進程、線程的概念及其聯繫和區別
進程的概念:進程是表示資源分配的基本單位。它是一個執行某一個特定程序的實體,它擁有獨立的地址空間、執行堆棧、文件描述符等。
線程的概念:有時被稱爲輕量級進程,線程是進程中執行運算的最小
單位,亦即執行處理機調度的基本單位。
進程與線程的區別:
(1)調度:線程作爲調度和分配的基本單位,進程作爲擁有資源的基本單位
(2)併發性:不僅進程之間可以併發執行,同一個進程的多個線程之間也可併發執行
(3)擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但可以訪問隸屬於進程的資源.
(4)系統開銷:在創建或撤消進程時,由於系統都要爲之分配和回收資源,導致系統的開銷明顯大於創建或撤消線程時的開銷。
併發處理機制使程序功能更強,但也會增加一些計算開銷
OS的時間分片機制保證快速切換
線程切換的時候,會發生上下文切換
上下文:線程的執行環境
切換的開銷
設計協議軟件的時候,需要設法將上下文切換的次數減到最少
保證併發處理的好處比上下文切換的開銷多
服務器的設計策略包括:非併發,單線程進程併發,多線程進程併發
3、阻塞、非阻塞、同步和異步的概念,阻塞I/O,非阻塞I/O,多路複用I/O,信號驅動I/O,異步 I/O 的原理及其示意圖。
阻塞:
是指調用結果返回之前,當前線程會被掛起(線程進入非可執行狀態,在這個狀態下,cpu不會給線程分配時間片,即線程暫停運行)。函數只有在得到結果之後纔會返回。
阻塞調用和同步調用實際上是不同的。對於同步調用來說,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回而已。
非阻塞:
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。
對比阻塞與非阻塞:
阻塞模式下,一個線程只能處理一項任務,要想提高吞吐量必須通過多線程。
非阻塞模式下,一個線程永遠在執行計算操作,這個線程所使用的 CPU 核心利用率永遠是 100%,I/O 以事件的方式通知。
在阻塞模式下,多線程往往能提高系統吞吐量,因爲一個線程阻塞時還有其他線程在工作,多線程可以讓 CPU 資源不被阻塞中的線程浪費。
而在非阻塞模式下,線程不會被 I/O 阻塞,永遠在利用 CPU。多線程帶來的好處僅僅是在多核 CPU 的情況下利用更多的核。
同步:
所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。也就是必須一件一件事做,等前一件做完了才能做下一件事。
例如普通B/S模式(同步):提交請求->等待服務器處理->處理完畢返回 這個期間客戶端瀏覽器不能幹任何工作;ATM存款
異步:
異步的概念和同步相對。當一個異步過程調用發出後,調用者不能立刻得到結果。實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者。
例如 ajax請求(異步): 請求通過事件觸發->服務器處理(這時瀏覽器仍然可以作其他事情)->處理完畢
總結:
阻塞和非阻塞是指當的進程訪問的數據如果尚未就緒,進程是否需要等待,簡單說這相當於函數內部的實現區別,也就是未就緒時是直接返回還是等待就緒;
而同步和異步是訪問數據的機制,同步一般指主動請求並等待I/O操作完畢的方式,當數據就緒後在讀寫的時候必須阻塞(區別就緒與讀寫二個階段,同步的讀寫必須阻塞),異步則指主動請求數據後便可以繼續處理其它任務,隨後等待網絡通信(I/O操作)完畢的通知,這可以使進程在數據讀寫時也不阻塞。
五種I/O模型:
- 阻塞I/O
- 非阻塞I/O
- I/O複用(select和poll)
- 信號驅動I/O
- 異步I/O
前四種是同步的,最後一種是異步
Linux fork實現原理
fork函數用來創建一個新的進程,fork將正在運行的程序分成2個幾乎完全一樣的進程,每個進程都啓動一個從代碼同一位置開始的線程。fork後,父子進程具有相同的數據空間、代碼空間、堆棧和所有文件描述符,但相互之間互不影響。fork函數,在父進程中返回子進程的id,在子進程中返回0,出錯返回-1,併發進程利用這個區別讓新進程執行與原來不一樣的代碼。
執行新的代碼:
int execl(const char *path, const char *arg, ...);
系統調用execl執行另一個程序。調用execl並不創建新進程,所以前後的進程ID並未改變,execl只是用另一個新程序替換了當前進程的正文、數據、堆棧;
path 是要執行的二進制文件或腳本的完整路徑。
arg是要傳給程序的完整參數列表,包括arg[0],一般是執行程序的名字。
最後一個參數可爲NULL。
第三章 基本套接口編程
不精確指明的協議軟件接口
TCP/IP和應用程序之間的接口應該是不精確指明的:
不規定接口的細節
只建議需要的功能集
允許系統設計者選擇有關API的具體實現細節
優點:
提供了靈活性和容錯能力,便於各種OS實現TCP/IP;接口可以是過程的,也可以是消息的
缺點:
不同OS中的接口細節不同,廠商增加與現有API不同的接口時,編程更困難,移植性差;程序員需要重新學習接口知識。
目前存在幾種TCP/IP接口:
Berkeley UNIX中的TCP/IP API,成爲套接字接口
Microsoft windows中TCP/IP API,windows socket
AT&T的UNIX系統V的TLI
什麼是套接字?
1、套接字是一個主機本地網絡應用程序所創建的,爲操作系統所控制的接口。
2、應用程序通過這個接口,使用傳輸層提供的服務,跨網絡發送或接收消息
3、Client/server模式的通信接口—套接字接口
套接字概要—主動套接字和被動套接字
創建方式相同,使用方式不同
等待傳入連接的套接字—被動,如服務器套接字
發起連接的套接字—主動,如客戶套接字
指明端點地址:創建時不指定,使用時指明,允許協議族自由的選擇地址表示方式
套接字的普通C定義結構
struct_sockaddr{
u_char sa_len;//1B
u_short sa_fanily;//2B
char sa_data[14];//14B
}通用的的地址結構
TCP/IP的地址定義
struct sockaddr_in{
u_char sin_len;//1B
u_short sin_family;//2B
u_short sin_port;//2B
struct in_addr sin_addr;//4B
char sin_zero[8];//8B
}IP專用地址結構
結構體in_addr用來表示一個32位的IPv4地址。其字節序爲網絡字節序。
主調用的套接字API
int Socket(int domain, int type, int protocol)
功能:創建一個新的套接字,返回套接字描述符
參數說明:
domain:域類型,指明使用的協議棧,AF_INET:IPv4協議;AF_INET6:IPVC6協議;。。。。。。
type:指明需要的服務類型,AF_INET地址族如下
SOCK_DGRAM:數據報服務,UDP協議
SOCK_STREAM:流服務,TCP協議
SOCK_RAW:提供傳輸層以下的協議,例如發送和接收ICMP報文
protocol:一般爲0(由系統根據服務類型選擇默認的協議)
int bind(int sockfd,struct sockaddr * my_addr,int addrlen)
功能:爲套接字指明一個本地端點地址
TCP/IP協議使用sockaddr_in結構,包含IP地址和端口號
服務器使用它來指明熟知的端口號,然後等待連接
參數說明:
Sockfd:套接字描述符,指明創建連接的套接字
my_addr:本地地址,IP地址和端口號
addrlen :地址長度
爲什麼TCP服務端需要調用bind函數而客戶端通常不需要呢?
客戶端也可以bind,但是一般操作系統會爲之分配不衝突的端口,自己bind可能會衝突。
int listen(int sockfd, int input_queue_size)
功能:
面向連接的套接字使用它將一個套接字置爲被動模
式,並準備接收傳入連接。用於服務器,指明某個
套接字連接是被動的
參數說明:
Sockfd:套接字描述符,指明創建連接的套接字
input_queue_size:該套接字使用的隊列長度,指定在請求隊列中允許的最大請求數
請將套接字設爲被動模式,並允許最大請求數爲20。
舉例:listen(sockfd,20)
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
爲每個新的連接請求創建了一個新的套接字,服務器只對新的連接使用該套接字,原來的監聽套接字接收其他的連接請求。
新的連接上傳輸數據使用新的套接字,使用完畢,服務器將關閉這個套接字。
參數說明:
Sockfd:套接字描述符,指明正在監聽的套接字
addr:提出連接請求的主機地址
addrlen:地址長度
舉例:new_sockfd = accept(sockfd, (struct sockaddr *)&address, sizeof(address));
int connect(int sockfd, struct sockaddr *server_addr, int sockaddrlen)
功能: 同遠程服務器建立主動連接,成功時返回0,若連接失敗返回-1。
參數說明:
Sockfd:套接字描述符,指明創建連接的套接字
Server_addr:指明遠程端點:IP地址和端口號
sockaddr_len :地址長度
舉例(P49): connect(s,remaddr,remaddrlen)
int sned(int sockfd, const void *data, int data_len, unsigned int falgs)
功能:在TCP連接上發送數據,返回成功傳送數據的長度,出錯時返回-1
send會將外發數據複製到OS內核中,也可以使用send發送面向連接的UDP報文。
參數說明:
sockfd:套接字描述符
data:指向要發送數據的指針
data_len:數據長度
flags:通常爲0,設置爲 MSG_DONTWAIT爲非阻塞
記住如果send()函數的返回值小於len 的話,則你需要再次發送剩下的數據。802.3,MTU爲1492B,如果包小於1K,那麼send()一般都會一次發送光的。
int sendto(int sockfd, const void * data, int data_len, unsigned int flags, struct sockaddr *remaddr,sock_len remaddr_len)
功能:基於UDP發送數據報,返回實際發送的數據長度,出錯時返回-1
參數說明:
sockfd:套接字描述符
data:指向要發送數據的指針
data_len:數據長度
flags:通常爲0,設置爲 MSG_DONTWAIT爲非阻塞
remaddr:遠端地址:IP地址和端口號
remaddr_len :地址長度
int recv(int sockfd, void *buf, int buf_len, unsigned int flags);
功能:從TCP接收數據,返回實際接受的數據長度,出錯時返回-1.
服務器使用其接受客戶請求,客戶使用它接收服務器的應答。如果沒有數據,將阻塞。
如果TCP收到的數據大於(/小於)緩存的大小,只抽出能夠填滿緩存的足夠數據(/抽出所有數據並返回它實際接收的字節數)。
也可以使用recv接收面向連接的UDP的報文,若緩存不能裝下整個報文,填滿緩存後剩下的數據將被丟棄。
參數說明:
Sockfd:套接字描述符
Buf:指向內存塊的指針
Buf_len:內存塊大小,以字節爲單位
flags:一般爲0(MSG_WAITALL接收到指定長度數據時才返回),設置爲 MSG_DONTWAIT爲非阻塞
int recvfrom(int sockfd, void *buf, int buf_len,unsigned int flags,struct sockaddr *from,sock_len *fromlen);
功能:從UDP接收數據,返回實際接收的字節數,失敗時返回-1
參數說明:
Sockfd:套接字描述符
buf:指向內存塊的指針
buf_len:內存塊大小,以字節爲單位
flags:一般爲0
from:遠端的地址,IP地址和端口號
fromlen:遠端地址長度
舉例:recvfrom(sockfd,buf,8192,0,(struct sockaddr *)&address, &sizeof(address));
close(int sockfd);
功能:
撤銷套接字.
如果只有一個進程使用,立即終止連接並撤銷該套接字,如果多個進程共享該套接字,將引用數減一,如果引用數降到零,則關閉連接並撤銷套接字。
參數說明:
Sockfd:套接字描述符
舉例:close(socket_descriptor)
字節序轉換
大於一個字節的變量類型的表示方法有兩種:
小端字節序(Little Endian,LE):在表示變量的內存地址的起始地址存放低字節,高字節順序存放;
大端字節序(Big Endian,BE):在表示變量的內存地址的起始地址存放高字節,低字節順序存放
字節序轉換函數
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); /*主機字節序到網絡字節序的長整型轉換*/
uint16_t htons(uint16_t hostshort); /*主機字節序到網絡字節序的短整型轉換*/
uint32_t ntohl(uint32_t netlong); /*網絡字節序到主機字節序的長整型轉換*/
uint16_t ntohs(uint16_t netshort); /*網絡字節序到主機字節序的短整型轉換*/
套接字描述符和通用文件描述符在形式上沒有區別,那麼如何判斷一個文件描述符是否是套接字描述符呢?進行套接字描述符判定的函數issockettype(),返回值爲1,是套接字描述符,0爲文件描述符。
第四章 TCP網絡編程基礎
向內核傳入數據的函數有send()、bind()等,從內核得到數據的函數有accept()、recv()等。
TCP網絡編程有兩種模式,一種是服務器模式,另一種是客戶端模式。服務器模式創建一個服務程序,等待客戶端用戶的連接,接收到用戶的連接請求後,根據用戶的請求進行處理;客戶端模式則根據目的服務器的地址和端口進行連接,向服務器發送請求並對服務器的響應進行數據處理。
TCP客戶服務器編程服務端流程主要分爲:
1)套接字初始化(socket())
2)套接字與端口的綁定(bind())
3)設置服務器的偵聽連接連接(listen())
4)接受客戶端連接請求(accept())
5)接收和發送數據(read()、write())並進行數據處理
6)處理完畢的套接字關閉(close())
TCP客戶服務器編程客戶端流程主要分爲:
1)套接字初始化(socket())
2)連接服務器(connect())
3)讀寫網絡數據(read()、write())並進行數據處理
4)最後套接字關閉(close())過程。
客戶端與服務器在連接、讀寫數據、關閉過程中有交互過程。
當服務器端在接收到一個客戶端的連接後,可以通過套接字描述符進行數據的寫入操作。對套接字進行寫入的形式和過程與普通文件的操作方式一致,內核會根據文件描述符的值來查找所對應的屬性,當爲套接字的時候,會調用相對應的內核函數。
ssize_t write(int fd, const void*buf,size_t nbytes);
int send(int sockfd,void *buf,int len,int flags)
返回實際所寫入或發出的字節數,小於0表示出現了錯誤
使用read()函數可以從套接字描述符中讀取數據。在讀取數據之前,必須建立套接字並連接。
ssize_t read(int fd,void *buf,size_t nbyte)
int recv(int sockfd,void *buf,int len,int flags)
返回實際所讀的字節數,如果返回的值是0,表示已經讀到文件的結束了,小於0表示出現了錯誤
第五章 UDP網絡編程基礎
UDP協議的程序設計框架
,客戶端和服務器之間的差別在於服務器必須使用bind()函數來綁定偵聽的本地UDP端口,而客戶端則可以不進行綁定,直接發送到服務器地址的某個端口地址。
UDP協議服務器和客戶端之間的交互
UDP協議中服務器和客戶端的交互存在於數據的收發過程中。進行網絡數據收發的時候,服務器和客戶端的數據是對應的:客戶端發送數據的動作,對服務器來說是接收數據的動作;客戶端接收數據的動作,對服務器來說是發送數據的動作。
UDP服務器編程框架
服務器流程主要分爲下述6個部分,即建立套接字、設置套接字地址參數、進行端口綁定、接收數據、發送數據、關閉套接字等。
(1)建立套接字文件描述符,使用函數socket(),生成套接字文件描述符。
(2)設置服務器地址和偵聽端口,初始化要綁定的網絡地址結構。
(3)綁定偵聽端口,使用bind()函數,將套接字文件描述符和一個地址類型變量進行綁定。
(4)接收客戶端的數據,使用recvfrom()函數接收客戶端的網絡數據。
(5)向客戶端發送數據,使用sendto()函數向服務器主機發送數據。
(6)關閉套接字,使用close()函數釋放資源。
UDP客戶端編程框架
UDP協議的客戶端流程分爲套接字建立、設置目的地址和端口、向服務器發送數據、從服務器接收數據、關閉套接字等5個部分。流程如下:
(1)建立套接字文件描述符,socket();
(2)設置服務器地址和端口,struct sockaddr_in;
(3)向服務器發送數據,sendto();
(4)接收服務器的數據,recvfrom();
(5)關閉套接字,close()。
UDP報文的正常發送過程
而在Internet上,由於要經過多個路由器,正常情況下一個數據報文從主機C經過路由器A、路由器B、路由器C到達主機S,數據報文的路徑如圖所示。主機C使用函數sendto()發送數據,主機S使用recvfrom()函數接收數據,主機S在沒有數據到來的時候,會一直等待。
UDP報文的丟失
路由器要對轉發的數據進行存儲、處理、合法性判定、轉發等操作,容易出現錯誤,所以很可能在路由器轉發的過程中出現數據丟失的現象,如圖所示。當UDP的數據報文丟失的時候,函數recvfrom()會一直阻塞,直到數據到來。
UDP報文丟失的對策
主機C發送的數據經過路由器,到達主機S後,主機S要發送一個接收到此數據報文的響應,主機C要對主機S的響應進行記錄,直到之前發送的數據報文1已經被主機S接收到。如果數據報文在經過路由器的時候,被路由器丟棄,則主機C和主機S會對超時的數據進行重發。
UDP報文亂序的對策
對於亂序的解決方法可以採用發送端在數據段中加入數據報序號的方法,這樣接收端對接收到數據的頭端進行簡單地處理就可以重新獲得原始順序的數據。
UDP缺乏流量控制
UDP協議沒有TCP協議所具有的滑動窗口概念,接收數據的時候直接將數據放到緩衝區中。如果用戶沒有及時地從緩衝區中將數據複製出來,後面到來的數據會接着向緩衝區中放入。當緩衝區滿的時候,後面到來的數據會覆蓋之前的數據造成數據的丟失。
解決UDP接收緩衝區溢出的現象需要根據實際情況確定,一般可以用增大接收數據緩衝區或接收方接收單獨處理的方法來解決局部的UDP數據接收緩衝區溢出問題。
第六章 客戶軟件設計中的算法
標識服務器位置的幾種方式
客戶服務器網絡編程中需要找到服務器的IP和端口:
1)在編譯程序時,將服務器的域名或者IP地址說明爲常量
執行快,但是服務器移動後不便
2)要求用戶在啓動程序時指定服務器
使用機器名,不必重新編譯客戶程序
3)從穩定的存儲設備中獲得關於服務器的信息
如果文件不存在,客戶軟件就不能執行
4)使用某個單獨的協議來找到服務器(如廣播或組播)
只能在本地小環境下應用
TCP客戶算法-面向連連接的客戶
1)分配套接字
2)找到期望與之通信的服務器IP地址和協議端口號
3)指明此連接需要在本地機器中的、任意的、未使用的協議端口,並允許TCP選擇一個這樣的端口
4)將這個套接字連接到服務器
5)使用應用級協議與服務器通信
6)關閉連接
分配套接字
<sys/types.h> //primitive system data types(包含 很多類型重定義,如pid_t、int8_t等)
<sys/socket.h> //與套接字相關的函數聲明和結構體定義, 如socket()、bind()、connect()及struct sockaddr的定義
<sys/ioctl.h> //I/O控制操作相關的函數聲明,如ioctl()
<stdlib.h> //某些結構體定義和宏定義,如 EXIT_FAILURE、EXIT_SUCCESS等
<netdb.h> //某些結構體定義、宏定義和函數聲明, 如struct hostent、struct servent、 gethostbyname()、gethostbyaddr()、herror()等
<arpa/inet.h> //某些函數聲明,如inet_ntop()、 inet_ntoa()等
<netinet/in.h> //某些結構體聲明、宏定義,如struct sockaddr_in、PROTO_ICMP、INADDR_ANY等
選擇本地協議端口號
服務器運行於熟知的端口上,客戶端並非某個預分配的端口上。
客戶使用端口的規則:
該端口不與該機器其他進程使用端口衝突
該端口沒有被分配給某個熟知服務
客戶允許TCP自動選擇本地端口
connect調用的一個效果就是所選擇的本地端口能滿足上述準則。
將TCP套接字連接到服務器
connect函數:允許TCP套接字發起連接
執行下層的三次握手
超時或者建立連接後返回
三個參數:
retcode = connect(s, remaddr, remaddrlen);
s: 套接字的描述符
remaddr:一個sockaddr_in類型結構的地址
remaddrlen:第二個參數的長度
connect的四項任務
對指明的套接字進行檢測:有效,還沒有連接
將第二個參數給出的端點地址填入套接字中
爲此套接字選擇一個本地端點地址
發起一個TCP連接,並返回一個值
UDP客戶端基本算法
- 分配套接字
- 找到期望與之通信的服務器IP地址和協議端口號
- 指明這種通信需要本地機器中的、任意的、未使用的協議端口,並允許UDP選擇一個這樣的端口
- 指明報文所要發往的服務器
- 使用應用級協議與服務器通信
- 關閉連接
使用UDP與客戶端通信
對於連接的UDP套接字
使用send發送報文
使用recv接收報文
每次send發送一個完整的報文
每次recv接受一個完整的報文,足夠大緩存
不需要重複使用recv獲得單個報文
對於非連接的UDP套接字
sendto: 發送報文,含有地址信息
recvfrom:接收一個含有源地址的數據報
DAYTIME獲取服務器的時間。
Time服務允許一臺機器從另外一臺機器獲得當前日期和時間。
由於存在不同的時區,所有的時間日期信息必須用國際標準時間:UCT或UT
服務器應答前將本地時間轉化爲標準時間
客戶收到應答時,將國際標準時間轉化爲本地時間。
TIME協議規定由32bit的整數來表示
從1900年1月1日午夜爲起點的秒數
用於一臺計算機使用另外一系統時鐘來設置日期時間
TIME服務使用端口37
可以使用TCP協議
使用TCP的TIME服務器利用連接的出現激活輸出,類似DAYTIME服務。
使用TCP的客戶不用發送任何數據
也可以使用UDP訪問TIME服務
客戶發出包含單個數據報的請求
服務器從傳入的數據報中取出地址和端口號
服務器將當前時間編碼爲一個整數,使用上述地址和端口號發回給客戶
ECHO服務器返回從客戶收到的所有數據
用戶網絡管理員測試可達性,調試協議軟件,識別選路問題等
TCP ECHO服務:接收連接請求,從連接中讀取數據,在該連接上將數據寫回。直到客戶終止傳送。
UDP ECHO服務:接收整個數據報,根據數據報指明的端口號和地址,返回整個數據報
Socket 是unsigned int的類型,2個字節,所以其範圍
0--65535
但0-1024,被系統分配,ftp、telent、http、daytime、time等等,都是系統已經分配固定端口,因此可以通過getservbyname來獲取其端口號的。
用戶自定義的端口號建議:1025--65535
第七章 服務器軟件設計算法
概念性的服務器算法
各個服務器遵循一種簡單的算法:
創建套接字
綁定到一個熟知端口
期望在這個端口上接收請求
進入無限循環,接受客戶請求並應答
併發服務器和循環服務器
循環服務器:一個時刻只處理一個請求,FIFO
併發服務器:一個時刻可以處理多請求
多數只提供表面併發:執行多個線程,每個線程處理一個請求
使用單線程的可能性:每次服務計算量小,主要是I/O複用, 便於同時使用多個通信信道
併發服務器指服務器是否併發處理多個請求,而不是指下層是否使用了多個併發線程
循環服務器容易構建,在服務端處理複雜的情況下性能差;併發服務器難以構建和設計,在服務端處理複雜的情況下但是性能好
TCP的語義
點到點通信
建立可靠連接
可靠交付
具有流控的傳輸
雙工傳輸
流模式
UDP的語義
多對多通信
不可靠服務
缺乏流控制
報文模式
面向連接的服務器的優缺點
面向連接服務的優點:
易於編程
自動處理分組丟失,分組失序
自動驗證數據差錯,處理連接狀態
面向連接服務的缺點:
對每個連接都有一個單獨的套接字,耗費更多的資源
在空閒的連接上不發送任何分組
始終運行的服務器會因爲客戶的崩潰,導致無用套接字的過多而耗盡資源,終止運行
無連接服務器優缺點
優點:沒有資源耗盡問題
缺陷:需要自己完成可靠通信問題
必要時,需要一種自適應重傳的複雜技術,需要程序員具有相當的專業知識
對於不可靠通信的場合,儘量使用tcp
是否需要組播或者廣播是考慮選擇何種傳輸方式的一個因素
支持組播或者廣播的服務器必須是無連接的,今後會不斷增加這樣的應用。
循環面向連接的服務器算法
通過TCP的面向連接的循環服務器算法
1、創建套接字並將其綁定到它所提供服務的熟知端口上;
2、將該端口設置爲被動模式,使其準備爲服務器所用;
3、從該套接字上接收下一個連接請求,獲得該連接的新的套接字;
4、在新套接字上重複地讀取來自客戶的數據,構造響應,按照應用協議向客戶發回響應;
5、當某個特定客戶完成交互時,關閉連接,並返回步驟3以接受新的連接。
無連接循環服務器的算法
循環服務器的設計,編程,排錯,修改很容易。往往使用無連接的協議。
循環服務器對於小的處理時間的服務工作很好。
無連接服務器算法如下:
1、創建套接字並將其綁定到所提供服務的熟知端口上;
2、重複讀取來自客戶的請求,構造響應,按照應用協議向客戶發回響應。
併發服務器
主線程和從線程
儘管可以使用一個單線程實現併發服務器,但是大多數使用多線程:
主線程先執行在熟知的端口上打開一個套接字,等待一個請求,併爲每個請求創建一個從線程(可能在一個新進程中)
主線程不與客戶直接通信,每個從線程處理一個客戶的通信。
從線程構成響應併發送給客戶後,這個從線程便退出
併發無連接服務器算法
最簡單的算法:
主1、創建套接字並將其綁定到所提供服務的熟知地址上。讓該套接字保持爲未連接的
主2、反覆調用recvfrom接收來自客戶的下一個請求,創建一個新的從線程來處理響應
從1、從來自主線程的特定請求以及到該套接字的訪問開始
從2、根據應用協議構造應答,並用sendto將該應答發回給客戶
從3、退出(即:從線程處理完一個請求後就終止)
由於創建進程或者線程是昂貴的,因此只有很少的無連接服務器採用併發實現
併發面向連接的服務器算法:
面向連接的服務器在多個連接之間實現併發
主1、創建套接字並將其綁定到所提供服務的熟知地址上。讓該套接字保持爲無連接的
主2、將該端口設置爲被動模式
主3、反覆調用accept以便接收來自客戶的下一個連接請求,並創建新的從線程或者進程來處理響應
從1、由主線程傳遞來的連接請求開始
從2、用該連接與客戶進行交互;讀取請求併發迴響應
從3、關閉連接並退出
單線程面向併發、面向連接的服務器算法
1、創建套接字並將其綁定到這個服務的熟知端口上,將該套接字加到一個表中,該表中的項是可以進行I/O的描述符。
2、使用select在已經有的套接字上等待I/O
3、如果最初的套接字準備就緒,使用accept獲得下一個連接,並將這個新的套接字加入到表中,該表中的項是可以進行I/O的描述符。
4、如果最初的套接字以外的套接字就緒,就使用recv或read獲得下一個請求,構造響應,用send或者write將響應發回給客戶
5、繼續按照以上的步驟2進行處理
各個服務器的適用場合
循環的和併發的:
如果循環方案產生的響應時間對應用來說足夠,就可以使用循環;否則需要併發
真正的和表面上的併發性:
創建線程或切換環境的開銷大,服務器需要在多個連接之間共享或者交換數據,用單線程;
使用線程開銷不大,服務器需要在多個連接之間共享或者交換數據,用多線程;
每個進程可以孤立運行或者要得到最大併發性,使用多進程
面向連接的和無連接的:
當應用協議處理了可靠性問題,或者應用在局域網環境內時,使用無連接的傳輸。否則使用有連接傳輸。
服務器類型小結
循環無連接服務器
對每個請求的處理少,通常爲無狀態的
循環的面向連接服務器
要求可靠傳輸的,對每個請求處理少的服務
併發的,無連接的服務器
不常見,爲每個請求創建一個新線程或進程
併發的面向連接的服務器
最一般的。可靠傳輸,併發處理多個請求
多進程可以是多個獨立的程序
多線程(進程)或者單線程方式