1. 程式人生 > >TCP網路程式設計框架函式

TCP網路程式設計框架函式

本文中,我們使用一個簡單的client/server模型來說明TCP通訊過程以及通訊過程使用使用函式。

首先我們應該知道客戶端和伺服器端的函式使用過程:

客戶端:呼叫socket函式--->呼叫connect函式請求連線--->呼叫send傳送資料--->呼叫recv接受資料

服務端:呼叫socket函式--->呼叫bind將sockfd與服務地址繫結--->呼叫listen在套接字處監聽請求--->迪呼叫accept處理一個連線請求--->recv接受資料--->send傳送資料

(1)socket函式(客戶端、伺服器端函式)

函式原型:int socket(int domain, int type, int protocol);

所在標頭檔案:<sys/types.h>和<sys/socket.h>

形參:int domain是指地址族,常用的由AF_INET(ipv4)和AF_INET6(ipv6)。注意:這裡AF_INET和PF_INET是相通的。

           int type是指定的此次socket的型別,一般取值SOCK_STREAM(TCP),SOCK_DGRAM(UDP)

           int protocol是指使用的協議,注意type與protocol要搭配才行,通常情況下我們將該引數設為0.

返回值:成功,返回一個套接字描述符,相當於一個open函式返回一個檔案描述符;

              失敗,返回-1;

功能:在呼叫socket的一方開啟一個套介面,相當於開啟一個檔案,返回值為檔案描述符。

注意:socket是在本地建立一個套介面,通訊雙方都需要建立各自的套介面,通訊雙方就是通過這個套介面進行資料交換的。

(2)bind函式(伺服器端函式)

函式原型:int bind(int sockfd, struct sockaddr *local_addr, int addrLength);

所在標頭檔案:<sys/types.h>和<sys/socket.h>

形參:int sockfd套介面的描述符

           struct sockaddr*local_addr

是本機的sockaddr結構體指標,通常先獲得sockaddr_in,然後在使用型別轉換為sockaddr

            int addrLength是sockaddr結構體大小,通常使用sizeof(sockaddr)獲得即可

返回值:成功,返回0;

              失敗,返回,-1;

功能:bind函式是將一個計算機上的套介面與自己的某個地址繫結起來,以使得在後期如果某個程式想要與這個地址所代表的服務通訊時直接使用通過與這個服務繫結的socket交換資料。也就是說通過bind函式將本機上的某個服務與socket描述符繫結起來,以後訪問該服務等都是通過這個套介面交換資料的。

注意:通常情況下TCP通訊的客戶端不是必須呼叫bind函式(可以呼叫也可以不呼叫),在TCP伺服器端必須呼叫bind函式。bind函式的功能是將本地的socket與本地標示某一個服務的地址繫結在一起,以便後續通過套介面來與服務交換資料。

因為一臺伺服器通常情況下會有多個網絡卡,每個網絡卡對應這個一個IP地址,這樣一個伺服器就會有很多個IP地址。通常情況下在伺服器端使用bind函式時是將xxx套介面與XXXXXX地址上的XX埠號提供的服務繫結在一起,通常XXXXXX地址上的XX埠是封裝在sockaddr_in結構體中的。當一個伺服器有多個IP時,我們使用sin_addr.s_addr = INET_ANY來表示將這個伺服器上的這個埠與所有IP繫結。

question1 客戶端沒有呼叫bind函式會怎樣?

答:在客戶端沒有呼叫bind函式,系統會自動核心會為我們隨機分配一個未使用的port(大於1024)將其與自己的IP構成sockaddr_in物件,系統會自動把這個sockaddr_in與socket描述符繫結。當然我們也可以在客戶端呼叫bind 函式將socket描述符與某個地址繫結,但是必須確保這個埠號沒有被佔用。

question 2為什麼客戶端不是必須呼叫bind 函式,而伺服器端必須呼叫bind 函式?

答:伺服器端存放著很多服務,每一個服務使用一個埠號來標記。當客戶端想要使用某個服務時,必須要知道該服務所在的主機IP和port,因此要將該這個IP和port與socket描述符繫結。

而客戶端只是不用顯示的呼叫bind函式,實際上核心中將客戶端IP和一個隨機的prot與socket描述符綁定了。因為客戶端並不會像伺服器端那樣使用具體的埠號來指明某種服務。(如http服務埠號為80)。如果不呼叫bind,執行兩次同一個客戶端程式,那麼中核心為我們分配的客戶端prot是不一樣的。

(3)connect函式(客戶端函式)

函式原型:int connect(int sockfd, struct sockaddr* server_addr, int addrLength);

所在標頭檔案:<sys/socket.h>和<sys/types.h>

形參:int sockfd一個套介面的描述符。

           socketaddr*server_addr伺服器端一個sockaddr結構體指標。通常情況下,首先獲得sockaddr_in然後將型別轉換為sockaddr*。

           int addrLength是結構體sockaddr的大小,一般使用sizeof(sockaddr)獲得。

返回值:成功,返回0;

              失敗,返回-1;

功能:bind函式將本地的sockfd與本地主機的地址繫結在一起了。connect函式是本地的sockfd與伺服器端的地址(server_addr)發起連線請求。connect就是三次握手與伺服器端建立TCP連線的一個過程。

connect函式是在客戶端呼叫的,伺服器端不用呼叫connect函式。

注意:如果connect函式呼叫失敗,隨之這個套介面(sockfd)也會失效,我們必須使用close函式將sockfd關閉。如果接下來我們想要重新呼叫connect函式就必須呼叫socket函式,重新開啟一個新的sockfd。

(4)listen函式(伺服器端函式)

函式原型:int listen(int sockfd, int backlog);

所在標頭檔案:<sys/socket.h>和<sys/types.h>

形參:int sockfd是之前使用socket函式開啟的套接字描述符,因為已經使用bind 函式將伺服器上的某個服務port和IP與這個套接字繫結在一起,那麼我們只需對這個套接字進行簡體即可。這個引數又叫做“監聽套接字”,只用監聽是否有客戶端請求到來。

           int backlog是指請求這個“監聽套接字”的請求佇列長度。因為可能會有很多客戶端想要使用伺服器上sockfd所繫結的服務,當伺服器正在處理一個請求時,將其他的請求放入到請求佇列中。

backlog = 未完成的TCP連線個數 + 已完成的TCP連線個數,其中未完成的TCP連線是指client已經發送SYN報文,但是沒有完成三次握手。已完成連線是指已經完成了三次握手。

返回值:成功,返回0;

              失敗,返回SOCKET_ERROR,可以使用WSAGetLastError獲取錯誤程式碼;

功能:listen函式的功能就是監聽想要使用套接字描述符sockfd所繫結服務的請求。因為我們在listen之前已經使用bind函式將套介面描述符與一個服務的地址繫結在一起,所以可以監聽這個套接字描述符就相當於對該服務的請求的監聽。

(5)accept函式(伺服器端函式)

函式原型:int accept(int sockfd, strcuct sockaddr * clientAddr, int * addrLength);

所在標頭檔案:<sys/socket.h>

形參:int sockfd是與某一個服務繫結的套介面描述符,與listen的sockfd一樣是一個監聽套接字。

          sockaddr* clientAddr是一個區域性結構體指標,用於獲取呼叫connect函式的客戶端的資訊。

          int *addrLength是clientAddr所指向的結構體大小。

返回值:返回一個新的套接字,使用該套接字向客戶端傳送、接受資料。這個套接字稱為是“連線套接字”,與之前的“監聽套接字”不同。accept函式執行完後,“監聽套接字”仍然監聽,後續資料接受與傳出的由“連線套接字”負責。

功能:從連線請求佇列中獲取第一個連線請求。(當佇列中沒有請求的話,如果套介面為阻塞方式的話,accpet函式會阻塞直到有新的連線請求到來;如果套介面為非阻塞方式的話,accept函式返回錯誤程式碼)並且處理這個請求返回一個新的連線套接字,後續使用這個新的套接字進行資料的傳送與接收。

注意:1. accept函式的返回值是一個新的套接字,建立連線以後使用這個新的套接字進行資料傳送與接收。以前的那個套接字仍然處於監聽狀態;

           2. accept函式的第二個和第三個引數使用來被填充的。用來獲取呼叫connect的客戶端的資訊。如果不需要這些資訊,可以把這個兩個引數設定為NULL;

           3. 如果請求連線佇列中沒有“連線請求”了,那麼accpet函式會議阻塞方式等待連線請求的到來;如果連線請求佇列為空且sockfd是非阻塞方式的話,accpet函式就會返回一個錯誤資訊。