學習筆記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