1. 程式人生 > 其它 >學習筆記11

學習筆記11

一、TCP/IP協議

什麼是TCP/IP協議?

計算機與網路裝置之間如果要相互通訊,雙方就必須基於相同的方法.比如如何探測到通訊目標.由哪一邊先發起通訊,使用哪種語言進行通訊,怎樣結束通訊等規則都需要事先確定.不同的硬體,作業系統之間的通訊,所有這一切都需要一種規則.而我們就將這種規則稱為協議 (protocol).也就是說,TCP/IP 是網際網路相關各類協議族的總稱。

TCP/IP 的分層管理

TCP/IP協議裡最重要的一點就是分層。TCP/IP協議族按層次分別為 應用層,傳輸層,網路層,資料鏈路層,物理層。當然也有按不同的模型分為4層或者7層的。

  • 分層的好處

把 TCP/IP 協議分層之後,如果後期某個地方設計修改,那麼就無需全部替換,只需要將變動的層替換。而且從設計上來說,也變得簡單了。處於應用層上的應用可以只考慮分派給自己的任務,而不需要弄清對方在地球上哪個地方,怎樣傳輸,如果確保到達率等問題。

五層介紹

  • 物理層

該層負責 位元流在節點之間的傳輸,即負責物理傳輸,這一層的協議既與鏈路有關,也與傳輸的介質有關。通俗來說就是把計算機連線起來的物理手段。

  • 資料鏈路層

控制網路層與物理層之間的通訊,主要功能是保證物理線路上進行可靠的資料傳遞。為了保證傳輸,從網路層接收到的資料被分割成特定的可被物理層傳輸的幀。幀是用來移動資料結構的結構包,他不僅包含原始資料,還包含傳送方和接收方的實體地址以及糾錯和控制資訊。其中的地址確定了幀將傳送到何處,而糾錯和控制資訊則確保幀無差錯到達。如果在傳達資料時,接收點檢測到所傳資料中有差錯,就要通知傳送方重發這一幀。

  • 網路層

決定如何將資料從傳送方路由到接收方。網路層通過綜合考慮傳送優先權,網路擁塞程度,服務質量以及可選路由的花費等來決定從網路中的A節點到B節點的最佳途徑。即建立主機到主機的通訊。

  • 傳輸層

該層為兩臺主機上的應用程式提供端到端的通訊。傳輸層有兩個傳輸協議:TCP(傳輸控制協議)和 UDP(使用者資料報協議)。其中,TCP是一個可靠的面向連線的協議,udp是不可靠的或者說無連線的協議

  • 應用層

應用程式收到傳輸層的資料後,接下來就要進行解讀。解讀必須事先規定好格式,而應用層就是規定應用程式的資料格式。主要的協議有:HTTP.FTP,Telent等。

TCP的三次握手與四次揮手

具體過程如下:

第一次握手:建立連線。客戶端傳送連線請求報文段,並將syn(標記位)設定為1,Squence Number(資料包序號)(seq)為x,接下來等待服務端確認,客戶端進入SYN_SENT狀態(請求連線);

第二次握手:服務端收到客戶端的 SYN 報文段,對 SYN 報文段進行確認,設定 ack(確認號)為 x+1(即seq+1 ; 同時自己還要傳送 SYN 請求資訊,將 SYN 設定為1, seq為 y。服務端將上述所有資訊放到 SYN+ACK 報文段中,一併傳送給客戶端,此時伺服器進入 SYN_RECV狀態。

SYN_RECV是指,服務端被動開啟後,接收到了客戶端的SYN並且傳送了ACK時的狀態。再進一步接收到客戶端的ACK就進入ESTABLISHED狀態。

第三次握手:客戶端收到服務端的 SYN+ACK(確認符) 報文段;然後將 ACK 設定為 y+1,向服務端傳送ACK報文段,這個報文段傳送完畢後,客戶端和服務端都進入ESTABLISHED(連線成功)狀態,完成TCP 的三次握手。

第一次揮手

客戶端設定seq和 ACK ,向伺服器傳送一個 FIN(終結)報文段。此時,客戶端進入 FIN_WAIT_1 狀態,表示客戶端沒有資料要傳送給服務端了。

第二次揮手

服務端收到了客戶端傳送的 FIN 報文段,向客戶端回了一個 ACK 報文段。

第三次揮手

服務端向客戶端傳送FIN 報文段,請求關閉連線,同時服務端進入 LAST_ACK 狀態。

第四次揮手

客戶端收到服務端傳送的 FIN 報文段後,向服務端傳送 ACK 報文段,然後客戶端進入 TIME_WAIT 狀態。服務端收到客戶端的 ACK 報文段以後,就關閉連線。此時,客戶端等待 2MSL(指一個片段在網路中最大的存活時間)後依然沒有收到回覆,則說明服務端已經正常關閉,這樣客戶端就可以關閉連線了。

如果有大量的連線,每次在連線,關閉都要經歷三次握手,四次揮手,這顯然會造成效能低下。因此。Http 有一種叫做 長連線(keepalive connections) 的機制。它可以在傳輸資料後仍保持連線,當客戶端需要再次獲取資料時,直接使用剛剛空閒下來的連線而無需再次握手。

網路套接字程式設計

網路層的IP可以惟一標識網路中的主機,而傳輸層的協議、埠這兩個東西可以表示主機中的程序(也就是網路應用程式)。

因此,通過IP、協議、埠號,可以標識網路的程序。

(1)伺服器根據地址的型別(屬於ipv4還是ipv6等)、socket型別(比如TCP、UDP)去建立socket,創建出的套接字socket本質上也是個檔案描述符。

(2)伺服器繫結IP地址和埠號到套接字socket

(3)伺服器socket監聽埠號請求,隨時準備接收客戶端發來的連線,但這個時候伺服器的socket並沒有被開啟。

(4)根據地址的型別(屬於ipv4還是ipv6等)、socket型別(比如TCP、UDP)去建立socket,創建出的套接字socket本質上也是個檔案描述符。

(5)客戶端根據伺服器的ip地址和埠號,試圖連線伺服器

(6)伺服器socket接收到客戶端的socket請求,被動開啟,開始接收客戶端的請求,並等待客戶端返回連線資訊。這個階段,伺服器的accept方法是阻塞的,即等到剛才試圖連線的客戶端返回連線資訊,accept方法才能返回,才能繼續接收下一個最新的客戶端連線請求。

(7)客戶端連線成功,向伺服器傳送連線狀態資訊

(8)伺服器accept方法返回,連線成功

(9)客戶端傳送訊息

(10)服務端接收訊息

(11)客戶端關閉

(12)服務端關閉

主機位元組序

就是我們平常說的大端和小端模式:不同的CPU有不同的位元組序型別,這些位元組序是指整數在記憶體中儲存的順序,這個叫做主機序。引用標準的Big-Endian和Little-Endian的定義如下:

a) Little-Endian就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。

b) Big-Endian就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。

網路位元組序:

4個位元組的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然後16~23bit,最後是24~31bit。這種傳輸次序稱作大端位元組序。

由於TCP/IP首部中所有的二進位制整數在網路中傳輸時都要求以這種次序,因此它又稱作網路位元組序。位元組序,顧名思義位元組的順序,就是大於一個位元組型別的資料在記憶體中的存放順序,一個位元組的資料沒有順序的問題了。

所以:在將一個地址繫結到socket的時候,請先將主機位元組序轉換成為網路位元組序,而不要假定主機位元組序跟網路位元組序一樣使用的是Big-Endian。由於這個問題曾引發過血案!公司專案程式碼中由於存在這個問題,導致了很多莫名其妙的問題,所以請謹記對主機位元組序不要做任何假定,務必將其轉化為網路位元組序再賦給socket。

TCP服務端例項

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>		  /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <netdb.h>  
#include <errno.h>
#include <unistd.h>
 
extern int errno;
 
int main()
{
	int domain = AF_INET;
	int type = SOCK_STREAM;
	int protocol = 0;
	int ret  = -1;
	int nListenFd = -1;
	int nNewClientFd = -1;
	short int  port = 2000; 
	struct sockaddr_in addr_in;
	int backlog = 128; // 預設是128
	int len = 0;
	char chBuffer[1024] = {0};
	int flags = 0;
	
	nListenFd = socket( domain,  type,  protocol);
	if(nListenFd < 0)
	{
		printf("\n socket failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
		return -1;
	}
 
	memset(&addr_in, 0, sizeof(struct sockaddr_in));
	addr_in.sin_family = AF_INET;
	addr_in.sin_port = htons(port);//htons的返回值是16位的網路位元組序整型數   htons尾的字母s代表short
	addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
 
	ret = bind(nListenFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
    if(ret < 0)
    {
    	printf("\n bind failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
    	close(nListenFd); //避免資源洩漏
		return -1;
	}
 
    ret = listen(nListenFd, backlog);
    if(ret < 0)
    {
		printf("\n listen failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
		close(nListenFd); //避免資源洩漏
		return -1;
	}
 
	nNewClientFd = accept(nListenFd, ( struct sockaddr *)NULL, NULL); //阻塞模式
	if(nNewClientFd < 0)
	{
		printf("\n accept failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
		close(nListenFd); //避免資源洩漏
		return -1;
	}
	len = recv(nNewClientFd, chBuffer, sizeof(chBuffer) , flags);//flags為0,阻塞模式
	if(len < 0)
	{
		printf("\n recv failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
		close(nListenFd); //避免資源洩漏
		close(nNewClientFd);
		return -1;
	}
 
	chBuffer[sizeof(chBuffer) - 1] = 0;
 
	printf("\n recv[%s]\n" , chBuffer);
 
	len = send(nNewClientFd, "Welcome", sizeof("Welcome"), flags);
	if(len < 0)
	{
		printf("\n send failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
		close(nListenFd); //避免資源洩漏
		close(nNewClientFd);
		return -1;
	}
 
	close(nNewClientFd);
	close(nListenFd);
 
	return 0;
}

TCP客戶端例項

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>		  /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <netdb.h>  
#include <errno.h>
#include <unistd.h>
 
extern int errno;
 
int main()
{
	int domain = AF_INET;//AF_INET
	int type = SOCK_STREAM;
	int protocol = 0;
	int ret  = -1;
	int nClientFd = -1;
	short int  port = 2000; 
	struct sockaddr_in addr_in;
	int len = 0;
	char chBuffer[1024] = {0};
	int flags = 0;
	char *pchServerIP = "192.168.1.211";
	
	nClientFd = socket( domain,  type,  protocol);
	if(nClientFd < 0)
	{
		printf("\n socket failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
		return -1;
	}
 
    memset(&addr_in, 0, sizeof(struct sockaddr_in));
	addr_in.sin_family = AF_INET;
	addr_in.sin_port = htons(port);//htons的返回值是16位的網路位元組序整型數   htons尾的字母s代表short
	//addr_in.sin_addr.s_addr = htonl(inet_addr(pchServerIP));//htonl的返回值是16位的網路位元組序整型數   htonl尾的字母l代表32位長整型
 
	addr_in.sin_addr.s_addr = inet_addr(pchServerIP); //htonl(inet_addr(pchServerIP));
	ret = connect(nClientFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
    if(ret < 0)
    {
    	printf("\n connect failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
    	close(nClientFd); //避免資源洩漏
		return -1;
	}
 
	len = send(nClientFd, "14.3", sizeof("14.3"), flags);
	if(len < 0)
	{
		printf("\n send failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
		close(nClientFd); //避免資源洩漏
		return -1;
	}
    len = recv(nClientFd, chBuffer, sizeof(chBuffer) , flags);//flags為0,阻塞模式
	if(len < 0)
	{
		printf("\n recv failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
		close(nClientFd); //避免資源洩漏
		return -1;
	}
 
	chBuffer[sizeof(chBuffer) - 1] = 0;
 
	printf("\n recv[%s]\n" , chBuffer);
 
	
	close(nClientFd);
 
	return 0;
}

程式碼練習

https://gitee.com/zhang_yu_peng/practice-code/blob/master/隨機數.c