1. TCP/IP、UDP的基本概念
TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,他是一個工業標準的協議集,它是爲廣域網設計的。其中包含了很多其他的協議,不過以TCP和IP協議爲代表。UDP(User Data Protocl),即用戶數據報協議,是與TCP相對應的協議,它屬於TCP/IP協議族中的一個。TCP/IP進行數據傳輸主要分爲兩個過程:建立連接過程和數據傳輸過程。
- TCP/IP協議通過三次握手建立一個可靠的連接,步驟:
- 第一次握手:建立連接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
- 第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
- 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
- 數據傳輸過程:發送端發送數據,然後進入等待ACK確定信號狀態,隨後接收端接收到數據,發送ACK確認信號,發送端接收到ACK後才發送下一組數據,同時發送端有一個定時器,定時時間到了沒有接收到ACK,就認爲發送失敗了,進行重新發送。因爲發送端發送完數據後處於等待狀態,因此爲了提高效率,引入“滑動窗口”的概念,就是發送的時候一次發送多組數據,相當於窗口的大小,然後當接收到第一個ACK後,就將窗口向後移動一個數據,就形成了滑動窗口的情況。
下表顯示了協議間的關係:
2. Socket的基本概念
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口,在設計模式中,Socket就是一個門面模式,它把複雜的TCP/IP協議族隱藏在了Scoket接口的後面,讓Socket去組織數據,以符合指定的協議。
3. Socket的使用
舉個簡單的例子先:你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲後提取電話,這時你和你的朋友就建立起了連接,就可以講話了,等交流結束,掛斷電話結束此次交談,這個生活場景的例子就解釋了TCP/IP的工作原理和流程,其具體的流程圖如下所示:
- 使用WSAStartup()函數檢查系統協議棧安裝情況
- 使用socket()函數創建服務器端通信套接口
- 使用bind()函數將創建的套接口與服務器地址綁定
- 使用listen()函數使服務器套接口做好接收連接請求準備
- 使用accept()接收來自客戶端由connect()函數發出的連接請求
- 根據連接請求建立鏈接後,使用send()還唸書發送數據,或者使用recv()函數接收數據
- 使用closesocket()函數關閉套接口(可以先用shutdown()函數吸納關閉讀寫通道)
- 最後調用WSACleanup()函數結束Winsock Sockets API
- 使用WSAStartup()函數檢查系統協議棧安裝情況
- 使用socket()函數創建客戶端套接口
- 使用connect()函數發出與服務器建立連接的請求(調用前可以不用bind()端口號,由系統自動完成)
- 連接建立後使用send()函數發送數據,或使用recv()函數接收數據
- 是喲個closesocket()函數關閉套接口
- 最後掉壓迫能夠WSACleanup()函數,結束Winsock Sockets API
4. Socket的send和recv函數
socket的發送和接收緩存區是兩個相互獨立的緩存區。socket的send和recv函數分爲阻塞模式的和非阻塞模式的,在定義socket的時候,默認設置爲了阻塞模式,而後如果想要改變爲非阻塞模式,只需使用ioctlsocket函數進行設置即可。非阻塞模式一旦調用即刻返回,因此如果傳輸數據較爲稀疏,則會經常返回失敗信息,爲此一般使用循環來接收數據。而常用的方式是阻塞模式,send函數沒啥問題,而recv函數一般配合select函數,可以做到雖然recv是阻塞的,但是合起來的效果是阻塞一段時間的方式。下面給出send和recv函數的工作流程。
- 阻塞模式下的send和recv函數
int send( SOCKET s,const char FAR *buf,int len,int flags );
不論是客戶還是服務器應用程序都用send函數來向TCP連接的另一端發送數據。
客戶程序一般用send函數向服務器發送請求,而服務器則通常用send函數來向客戶程序發送應答。
該函數的第一個參數指定發送端套接字描述符;
第二個參數指明一個存放應用程序要發送數據的緩衝區;
第三個參數指明實際要發送的數據的字節數;
第四個參數一般置0。
這裏只描述同步Socket的send函數的執行流程。當調用該函數時,send先比較待發送數據的長度len和套接字s的發送緩衝的長度,如果len大於s的發送緩衝區的長度,該函數返回SOCKET_ERROR;如果len小於或者等於s的發送緩衝區的長度,那麼send先檢查協議 是否正在發送s的發送緩衝中的數據,如果是就等待協議把數據發送完,如果協議還沒有開始發送s的發送緩衝中的數據或者s的發送緩衝中沒有數據,那麼 send就比較s的發送緩衝區的剩餘空間和len,如果len大於剩餘空間大小send就一直等待協議把s的發送緩衝中的數據發送完,如果len小於剩餘 空間大小send就僅僅把buf中的數據copy到剩餘空間裏(注意並不是send把s的發送緩衝中的數據傳到連接的另一端的,而是協議傳的,send僅僅是把buf中的數據copy到s的發送緩衝區的剩餘空間裏)。如果send函數copy數據成功,就返回實際copy的字節數,如果send在copy數據時出現錯誤,那麼send就返回SOCKET_ERROR;如果send在等待協議傳送數據時網絡斷開的話,那麼send函數也返回SOCKET_ERROR。
要注意send函數把buf中的數據成功copy到s的發送緩衝的剩餘空間裏後它就返回了,但是此時這些數據並不一定馬上被傳到連接的另一端。
注意:在Unix系統下,如果send在等待協議傳送數據時網絡斷開的話,調用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
int recv( SOCKET s,char FAR *buf,int len,int flags);
不論是客戶還是服務器應用程序都用recv函數從TCP連接的另一端接收數據。
該函數的第一個參數指定接收端套接字描述符;
第二個參數指明一個緩衝區,該緩衝區用來存放recv函數接收到的數據;
第三個參數指明buf的長度;
第四個參數一般置0。
這裏只描述同步Socket的recv函數的執行流程。當應用程序調用recv函數時,recv先等待s的發送緩衝 中的數據被協議傳送完畢,如果協議在傳送s的發送緩衝中的數據時出現網絡錯誤,那麼recv函數返回SOCKET_ERROR,如果s的發送緩衝中沒有數 據或者數據被協議成功發送完畢後,recv先檢查套接字s的接收緩衝區,如果s接收緩衝區中沒有數據或者協議正在接收數據,那麼recv就一直等待,只到 協議把數據接收完畢。當協議把數據接收完畢,recv函數就把s的接收緩衝中的數據copy到buf中(注意協議接收到的數據可能大於buf的長度,所以 在這種情況下要調用幾次recv函數才能把s的接收緩衝中的數據copy完。recv函數僅僅是copy數據,真正的接收數據是協議來完成的),recv函數返回其實際copy的字節數。如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函數在等待協議接收數據時網絡中斷了,那麼它返回0。
2. 非阻塞模式
非阻塞模式就是一旦調用,如果不滿足copy條件,則函數馬上返回失敗信息。
5. Winsock編程流程(以服務器端爲例)
1. 使用WSAStartup()裝載檢查TCP/IP協議
2. 創建套接字,有三種類型的套接字供選擇:流式套接字(用於TCP),數據報套接字(UDP)和原始套接字(底層協議)
3. 將創建好的套接字和IP地址以及端口進行綁定
4. 使用getsockopt獲得套接口的參數,使用setsockopt設置套接口的參數(主要設置套接口的收發緩存大小),使用ioctlsocket
設置阻塞還是非阻塞的模式(這裏的阻塞和非阻塞模式指的是API函數是否調用完馬上返回)
5. 進行通信,在通信的部分,windows socket給我們提供了五種I/O模型用於收發通信:(他們都可以和阻塞或非阻塞函數一起
使用)
a. select模型,常用的模型,以select函數爲核心,可以監控套接口是否有數據收發。它是同步模型。
b. WSAAsyncSelect模型,異步I/O模型,利用windows的消息機制實現異步接收,需要有窗口接收消息,只有接收數據是
異步的。
c. WSAEventSelect 模型,異步I/O模型,通過事件觸發來實現異步接收,它不需要接收窗口。
d. 重疊模型:比較完美的模型。
e. 完成端口:最爲複雜的I/O模型,當需要一個進程管理很多的套接口的時候,很適合使用這種模型。
6. 使用closesocket()函數關閉套接口的收發。
7. 使用WSACleanup()函數卸載TCP/IP協議。