Linux-網絡編程-學習筆記(20):網絡基礎與編程實踐

Linux-網絡編程-學習筆記(20):網絡基礎與編程實踐

一、網絡基礎

1. 網絡通信概述

網絡是用物理鏈路將各個孤立的工作站或主機相連在一起,組成數據鏈路,從而達到資源共享和通信的目的。通信是人與人之間通過某種媒體進行的信息交流與傳遞。網絡通信是通過網絡將各個孤立的設備進行連接,通過信息交換實現人與人,人與計算機,計算機與計算機之間的通信。

站在進程的層面來說,網絡之間的通信其實就是位於網絡中不同主機上面的2個進程之間的通信
以2臺設備的QQ之間通信爲例:
在這裏插入圖片描述
從圖中可以看出,網絡通信是分層的:
(1)硬件部分:網卡(負責通過網線或者WIFI的方式與外部網絡進行通信)
(2)操作系統底層:網卡驅動(負責驅動網卡以及打包數據的操作)
(3)操作系統API:socket接口(應用軟件去操作網卡驅動的API)
(4)應用層:低級(直接基於socket接口編程)
(5)應用層:高級(基於網絡通信應用框架庫)
(6)應用層:更高級(http、網絡控件等)

2. 網絡通信基礎知識

2.1 OSI7層網絡模型
在這裏插入圖片描述
2.2 網卡
(1)網卡是計算機用來上網的必備硬件設備,CPU靠網卡來連接外部網絡。
(2)網卡是一個串轉並的設備。網卡芯片和CPU之間的通信是並行通信,網絡通信是一種串行、全雙工、差分的通信方式。
(3)網卡用來進行數據幀的封包和拆包。網絡的發送數據的時候不單純是發送數據,而是將一定大小的數據進行打包(64kb或其他大小)發送,包包含三部分,包頭、數據和包尾(其中包頭包含了IP信息等)。
(4)網卡能夠實現網絡數據緩存和速率適配(兩個電腦的傳輸速率不相同)。

2.3 集線器
(1)信號中繼放大,相當於中繼器。
(2)組成局域網絡,用廣播方式工作。(多臺設備插到一個集線器上)
(3)注意集線器是不能用來連接外網的。

2.4 交換機
(1)包含集線器功能,但更高級。
(2)交換機中有地址表,數據包查表後直達目的通信口而不是廣播。
(3)找不到目的口時廣播並學習。

2.5 路由器
在這裏插入圖片描述
(1)路由器是局域網和外部網絡通信的出入口。(跨局域網進行通信需要經過網關才能出去,路由器就相當於一個網關
(2)路由器將整個internet劃分成一個個的局域網,卻又互相聯通。(兩個路由器連接下的電腦構成了兩個局域網,通過路由器實現局域網間接連接
(3)路由器對內管理子網(局域網),可以在路由器中設置子網的網段,設置有線端口的IP地址,設置dhcp功能等,因此局域網的IP地址是路由器決定的。
(4)路由器對外實現聯網,聯網方式取決於外部網絡(如ADSL撥號上網、寬帶帳號、局域網等)。這時候路由器又相當於是更高層級網絡的其中一個節點而已。
路由器的WAN是對外的口,LAN是對內的口
(5)路由器相當於有2個網卡,一個對內做網關、一個對外做節點
(6)路由器的主要功能是經過路由器的每個數據包尋找一條最佳路徑(路由)並轉發出去。其實就是局域網內電腦要發到外網的數據包,和外網回覆給局域網內電腦的數據包。(路由器的好壞決定路徑選的好壞,即收發速度)
(7)路由器技術是網絡中最重要技術,決定了網絡的穩定性和速度

2.6 DNS(Domain Name Service 域名服務)
(1)網絡世界的門牌號:IP地址(例如百度的IP地址爲61.135.169.125)。IP地址的缺點:難記、不直觀。
(2)IP地址的替代品:域名(例如www.baidu.com)。
(3)DNS服務器就是專門提供域名和IP地址之間的轉換的服務的,因此域名要購買的.
(4)我們訪問一個網站的流程是:先使用IP地址(譬如谷歌的DNS服務器IP地址爲8.8.8.8)訪問DNS服務器(DNS服務器不能是域名,只能是直接的IP地址),查詢我們要訪問的域名的IP地址,然後再使用該IP地址訪問我們真正要訪問的網站。這個過程被瀏覽器封裝屏蔽,其中使用的就是DNS協議。
(5)瀏覽器需要DNS服務,而QQ這樣的客戶端卻不需要(因爲QQ軟件編程時已經知道了騰訊的服務器的IP地址,因此可以直接IP方式訪問服務器)

2.7 DHCP(dynamic host configuration protocl,動態主機配置協議)
(1)每臺計算機都需要一個IP地址,且局域網內各電腦IP地址不能重複,否則會地址衝突(同一個局域網內,兩臺設備的IP地址重複,比如都設爲了192.168.1.1)。
(2)計算機的IP地址可以靜態設定(自己制定IP地址,但是管理起來很麻煩),也可以動態分配(由管理員路由器給自動分配)
(3)動態分配是局域網內的DHCP服務器來協調的,很多設備都能提供DHCP功能,譬如路由器
(4)動態分配的優勢:方便接入和斷開、有限的IP地址得到充分利用。

2.8 NAT(network address translation,網絡地址轉換協議)
(1)IP地址分爲公網IP(internet範圍內唯一的IP地址)和私網IP(內網IP),局域網內的電腦使用的都是私網IP(常用的就是192.168.1.xx)
(2)網絡通信的數據包中包含有目的地址的IP地址。
這裏以獲取百度某張圖片爲例:首先子網中的某臺設備的IP地址爲192.168.1.1,它所連接的路由器的IP地址爲172.1.1.1,那麼設備想要從百度獲取一張圖片,一定是要向百度網站發送一個請求命令,這個命令就是一個數據,那麼在發送前網卡會將數據進行打包(包頭、數據、包尾),其中包頭保存了公網IP(路由器)和私網IP(設備)和目的地IP(百度服務器),然後通過路由器規劃路徑後發送到百度的服務器上,服務器進行數據的解析,得知是要獲取某張圖片,於是將圖片數據進行打包(仍然是3部分),其中包頭中的目的地址和本地地址取反(發送地變爲接收地,接收地變爲發送地),將圖片發送回網卡,路由器進行判斷,知道該圖片應該發送給192.168.1.1對應的設備,最終該設備收到了圖片。
(3)當局域網中的主機要發送數據包給外網時,路由器要負責將數據包頭中的局域網主機的內網IP替換爲當前局域網的對外外網IP。這個過程就叫NAT
(4)NAT的作用是緩解IPv4的IP地址不夠用問題,但只是類似於打補丁的形式,最終的解決方案還是要靠IPv6。
(6)NAT穿透:P2P下載方式叫做一種穿透,服務器作爲中介,讓兩臺內網相連的技術叫做穿透。
這裏以迅雷下載爲例:假設我要下載一張圖片(局域網A的設備1),如果從百度上下載這張圖片時,需要前一個例子那樣,走很長一段距離才能到達百度服務器,這無疑會浪費很多時間。但是P2P下載方式爲我們提供了一種點對點的方式下載,也就是如果這張圖片在另外一臺設備上有(局域網B的設備1),同時我的設備和那臺設備都連接了訓練服務器,那麼迅雷服務器會爲我們兩個局域網之間構成了一條通路(本來兩個局域網互相不知道IP
是無法進行連接的,但是如果2臺設備都連接到了迅雷服務器,那麼迅雷服務器會自動安排一種連接通道),這樣我就可以直接到那臺設備上下載圖片。通過縮短了距離來提高了下載速度。如果同時有100臺設備上都有這張圖片,那麼我的設備就與這100臺設備都構成了通路,從而實現了一種並行下載,極大程度地提高了下載速度。這就好像本來兩個局域網之間有一堵牆隔着,P2P的下載方式穿透了這堵牆,所以稱爲NAT穿透。

2.9 IP地址分類(IPv4)
(1)IP地址實際是一個32位二進制構成,在網絡通信數據包中就是32位二進制,而在人機交互中使用點分十進制方式顯示。

二進制方式 0xffffffff 0xC0A80166/0x6601A8C0 本質
點分十進制方式 255.255.255.255 192.168.1.102 方便人看的

(2)IP地址中32位實際包含2部分,分別爲:網絡地址主機地址子網掩碼用來說明網絡地址和主機地址各自佔多少位。
可以8位表示網絡,24位表示主機;也可以16位表示網絡,16位表示主機;14爲表示網絡,18位表示主機。下面表格爲用子網掩碼錶示的2種:

子網掩碼:255.255.255.0 前24位爲網絡地址,後8位爲主機地址
子網掩碼:255.255.0.0 前16位爲網絡地址,後16位爲主機地址

網絡地址決定了這種網絡中一定可以有多少個網絡,主機地址決定了該子網下最多能有多少主機。譬如子網掩碼爲255.255.255.0時表示我們這一種網絡一共最多可以有224個,每個這種網絡中可以有28個主機。
如果子網掩碼爲255.255.0.0時,表示我們這種網絡可以有216個網絡,每個這種網絡中最多可以有216個主機。
網絡地址用來表示子網,主機地址是用來表示子網中的具體某一臺主機的

(3)由網絡地址和主機地址分別佔多少位的不同,將IP地址分爲5類,最常用的有3類(A類、B類和C類)
(4)127.0.0.0用來做迴環測試loopback
(5)判斷2個IP地址是否在同一子網內的方法是:查看2個IP地址的網絡標識一樣,那麼就處於同一網絡。
網絡標識 = IP地址 & 子網掩碼
例如:192.168.1.4和192.168.12.5,如果子網掩碼是255.255.255.0那麼不在同一網段,如果子網掩碼是255.255.0.0那麼就在同一個網段。

二、網絡編程

1. 網絡編程框架

1.1 網絡的分層結構
因爲網絡是一種非常複雜的通信方式,所以要通過分層來進行開發難度的降低。因此我們在研究網絡通信時,一定要在同一個層次進行研究,不能跨層次研究,比如分析客戶端和服務器的收發時,要分析API層次時,兩部分都要統一在這個層次進行分析,而不能是一端分析API接口,另一端卻去分析驅動了。一般情況下,我們在網絡編程時最關注的是應用層,傳輸層只需要瞭解即可。

1.2 BS和CS
(1)CS架構介紹(client server,客戶端服務器架構)。比如QQ,360網盤之類的(在電腦上或手機上用軟件登錄的)。
(2)BS架構介紹(broswer server,瀏覽器服務器架構)。比如在線版的QQ,網頁版360網盤(用瀏覽器打開的)。

2. TCP協議

傳輸控制協議(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。

2.1 TCP協議的作用
(1)TCP協議工作在傳輸層對上服務socket接口(API),對下調用IP層。(我們只需要通過調用socket接口實現數據收發,其內部安排由TCP來完成)
(2)TCP協議面向連接,通信前必須先3次握手建立連接關係後才能開始通信。(比如打電話必須你撥號,對面接聽才能夠進行雙向的語音通信)
(3)TCP協議(像是一個快遞公司)提供可靠傳輸,不怕丟包、亂序等。

2.2 TCP如何保證可靠傳輸
(1)TCP在傳輸有效信息前要求通信雙方必須先握手,建立連接才能通信(比如通過打電話聯繫一個人時,通過收到了對方的迴應從而確定對方收到了消息;而通過QQ只是發送了消息過去,對方是否看到這裏不清楚)
(2)TCP的接收方收到數據包後會ack給發送方,若發送方未收到ack會丟包重傳(每一次發送都要有迴應,從而確保發送信息的被收到)
(3)TCP的有效數據內容會附帶校驗,以防止內容在傳遞過程中損壞(就好像快遞公司給包裹加上了某種保護措施)
(4)TCP會根據網絡帶寬來自動調節適配速率,自動調整發送包的大小和一次發多少個包等……(滑動窗口技術
(5)發送方會給各分割報文編號,接收方會校驗編號,一旦順序錯誤即會重傳(傳的數據順序不能亂)

2.3 TCP的三次握手
在這裏插入圖片描述
TCP建立連接需要三次握手,這是TCP協議內部自動完成的,我們只需要調用對應的API進行收發即可。
建立連接的條件:服務器listen時客戶端主動發起connect
建立過程:SYN是一個同步信號,客戶端發起完這個SYN信號 [第一次] 後就主動進入到了SYN-SENT(請求連接)狀態,服務器收到信號後,就會進入到SYN-RCVD(同步收到)狀態並且回覆一個SYN+ACK信號 [第二次] ,客戶端在SYN-SENT接收到SYN+ACK信號後會迴應一個ACK信號 [第三次] ,並且將自身狀態變爲ESTAB-LISHED(建立服務)。服務器收到了ACK信號,也會進入到ESTAB-LISHED,從而建立連接。客戶端和服務器之間可以進行雙向通信

2.4 TCP的四次揮手
在這裏插入圖片描述
TCP斷開連接需要四次揮手
斷開連接的條件:服務器或者客戶端都可以主動發起關閉
斷開過程:假設客戶端先向其TCP發出連接釋放報文段,並停止再發送數據,主動關閉TCP連接。客戶端發送釋放報文FIN [第一次] ,此時客戶端進入FIN-WAIT-1(終止等待1)狀態,等待服務器的確認。服務器收到釋放報文後發出確認報文ACK [第二次] ,然後服務器就進入CLOSE-WAIT(關閉等待)狀態。TCP服務器進程這時應通知高層進程,因而從客戶端到服務器這個方向的連接就釋放了,這時的TCP連接處於半關閉狀態,即客戶端已經沒有數據要發送了,但服務器若發送數據,客戶端仍要接收。也就是說,從服務器到客戶端這個方向的連接並未關閉。這個狀態可能會持續一些時間。客戶端收到來自服務器的確認後,就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發出的連接釋放報文段。若服務器已經沒有要向客戶端發送的數據,其應用進程就通知TCP釋放連接。這時服務器發出的連接釋放報文FIN,並且還附帶上次已發送過的確認號ACK [第三次] 。這時服務器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。客戶端在收到服務器的連接釋放報文段後,發送確認報文ACK [第四次] 。然後進入到TIME-WAIT(時間等待)狀態(請注意:現在TCP連接還沒有釋放掉。必須經過時間等待計時器設置的時間2MSL(MSL:最長報文段壽命)後,客戶端才進入到CLOSED狀態)。服務器收到ACK也就如到CLOSED狀態。

3. 根據TCP協議搭建網絡鏈接

3.1 基於TCP通信的服務模式
(1)首先搭建的網絡連接主要分爲兩部分:客戶端服務器
客戶端:搭建socket接口,通過connect去向服務器發起連接。
服務器:搭建socket接口,通過bind綁定IP,然後調用listen來進入監聽狀態。
(2)服務器收到並同意客戶端接入後會建立TCP連接,然後雙方開始收發數據,收發時是雙向的,而且雙方均可發起,同時雙方均可發起關閉連接
(3)常見的使用了TCP協議的網絡應用:http(相當於一個應用程序,用來傳輸文本信息)、ftp、QQ服務器和mail服務器。這些需要很高可靠的應用,底層都是基於TCP協議的。

3.2 常用的網絡編程函數
(1)socket:socket函數類似於open,用來打開一個網絡連接,如果成功則返回一個網絡文件描述符(int類型),之後我們操作這個網絡連接都通過這個網絡文件描述符。
(2)bind:用來進行綁定的函數,把本地的IP地址和socket進行綁定。功能類似於fctrl函數,是用來改變屬性的函數。
(3)listen:監聽一個端口,監聽的是在bind時綁定的那個地址。
PS:端口號,實質就是一個數字編號,用來在我們一臺主機中(主機的操作系統中)唯一的標識一個能上網的進程。端口號(精確到電腦中的某個進程)和IP地址(精確到某個電腦)一起會被打包到當前進程發出或者接收到的每一個數據包中。每一個數據包將來在網絡上傳遞的時候,內部都包含了發送方和接收方的信息(就是IP地址和端口號),所以IP地址和端口號這兩個往往是打包在一起不分家的。
(4)accept:返回值是一個fd,accept正確返回就表示我們已經和前來連接我的客戶端之間建立了一個TCP連接了,以後我們就要通過這個連接來和客戶端進行讀寫操作,讀寫操作就需要一個fd,這個fd就由accept來返回了。(阻塞的位置)
PS:socket返回的fd叫做監聽fd,是用來監聽客戶端的,不能用來和任何客戶端進行讀寫;accept返回的fd叫做連接fd,用來和連接那端的客戶端程序進行讀寫。
(5)connect:用來連接服務器的(客戶端那邊用)。
(6)send/write:發送數據。(send比write就多了一個flag,只有支持一些特殊協議時會用到flag,普通情況下用0即可)
(7)recv/read:接收數據。(在網絡中發送有點像是寫文件,在網絡中接收有點像是收文件)

(8)inet_aton:點分十進制轉換爲32位二進制形式。(inet_pton原理相同,只是支持IPv6)
(9)inet_ntoa:32位二進制轉換爲點分十進制。(inet_ntop原理相同,支持IPv6)
(10)inet_addr:先檢測本設備是大端還是小端,然後自動轉爲大端模式。(在網絡編程中,默認都使用大端模式

4. 實現網絡通信

(1)設計網絡通信主要包括2個部分,一個是客戶端:負責去連接服務器。另一個是服務器:負責監聽客戶端的連接並配合它。
(2)客戶端和服務器原則上都可以任意的發和收,但是實際上雙方必須配合:client發的時候server就收,而server發的時候client就收。但是client和server之間的通信是異步的,所以需要依靠應用層協議來解決。
(3)規定連接建立後由客戶端主動向服務器發出1個請求數據包,然後服務器收到數據包後回覆客戶端一個迴應數據包,這就是一個通信回合,整個連接的通信就是由N多個回合組成的。同時雙發發送的數據包格式也要有一定要求。

下面以一個例子展示網絡編程:客戶端向服務器註冊學生的基本信息(發送一個數據包),服務器迴應一個數據包表示接收完成(展示學生的基本信息)。

客戶端代碼:client.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define SERADDR		"192.168.1.104"		// 服務器開放給我們的IP地址和端口號
#define SERPORT		9003

//發送、接收緩衝區
char sendbuf[100];
char recvbuf[100];

#define CMD_REGISTER	1001	// 註冊學生信息
#define CMD_CHECK		1002	// 檢驗學生信息
#define CMD_GETINFO		1003	// 獲取學生信息

#define STAT_OK			30		// 回覆ok
#define STAT_ERR		31		// 回覆出錯了

typedef struct commu
{
	char name[20];		// 學生姓名
	int age;			// 學生年齡
	int cmd;			// 命令碼
	int stat;			// 狀態信息,用來回復
}info;

int main(void)
{
	int sockfd = -1, ret = -1;
	//這個結構體是網絡編程接口中用來表示一個IP地址的,
	//這個IP地址是兼容IPv4和IPv6的
	struct sockaddr_in seraddr = {0};
	struct sockaddr_in cliaddr = {0};
	
	// 第1步:創建socket(AF_INET:使用IPv4進行通信,SOCK_STREAM:TCP)
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
	{
		perror("socket");
		return -1;
	}
	printf("socketfd = %d.\n", sockfd);
	
	// 第2步:connect鏈接服務器,向結構體填充服務器信息
	seraddr.sin_family = AF_INET;		// 設置地址族爲IPv4
	seraddr.sin_port = htons(SERPORT);	// 設置地址的端口號信息(檢測大小端並調整)
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	// 設置IP地址
	ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("connect");
		return -1;
	}
	printf("成功建立連接\n");

	while (1)
	{
		// 回合中第1步:客戶端給服務器發送信息
		info st1;
		printf("請輸入學生姓名\n");
		scanf("%s", st1.name);
		printf("請輸入學生年齡");
		scanf("%d", &st1.age);
		st1.cmd = CMD_REGISTER;
		ret = send(sockfd, &st1, sizeof(info), 0);
		printf("已發送%s學生的信息\n",st1.name);
		
		// 回合中第2步:客戶端接收服務器的回覆
		memset(&st1, 0, sizeof(st1));
		ret = recv(sockfd, &st1, sizeof(st1), 0);
		
		// 回合中第3步:客戶端解析服務器的回覆,再做下一步定奪
		if (st1.stat == STAT_OK)
		{
			printf("註冊學生信息成功\n");
		}
		else
		{
			printf("註冊學生信息失敗\n");
		}

	}

	return 0;
}

服務器端代碼:server.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define SERPORT		9003
#define SERADDR		"192.168.1.104"		// ifconfig看到的
#define BACKLOG		100		//貌似是最大能容納的連接數

char recvbuf[100];

#define CMD_REGISTER	1001	// 註冊學生信息
#define CMD_CHECK		1002	// 檢驗學生信息
#define CMD_GETINFO		1003	// 獲取學生信息

#define STAT_OK			30		// 回覆ok
#define STAT_ERR		31		// 回覆出錯了

typedef struct commu
{
	char name[20];		// 學生姓名
	int age;			// 學生年齡
	int cmd;			// 命令碼
	int stat;			// 狀態信息,用來回復
}info;


int main(void)
{
	int sockfd = -1, ret = -1, clifd = -1;
	socklen_t len = 0;
	//這裏的結構爲sockaddr_in結構體包含sin_port和sin_addr結構體,sin_addr結構體包含s_addr
	struct sockaddr_in seraddr = {0};
	struct sockaddr_in cliaddr = {0};
	
	// 第1步:先socket打開文件描述符	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
	{
		perror("socket");
		return -1;
	}
	printf("socketfd = %d.\n", sockfd);
	
	// 第2步:bind綁定sockefd和當前電腦的ip地址&端口號
	seraddr.sin_family = AF_INET;		// 設置地址族爲IPv4
	seraddr.sin_port = htons(SERPORT);	// 設置地址的端口號信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	// 設置IP地址
	ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind");
		return -1;
	}
	printf("bind success.\n");
	
	// 第3步:listen監聽端口
	ret = listen(sockfd, BACKLOG);		// 阻塞等待客戶端來連接服務器
	if (ret < 0)
	{
		perror("listen");
		return -1;
	}
	
	// 第4步:accept阻塞等待客戶端接入
	clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
	printf("連接已經建立,client fd = %d.\n", clifd);

	// 客戶端反覆給服務器發
	while (1)
	{
		info st;
		// 回合中第1步:服務器收
		ret = recv(clifd, &st, sizeof(info), 0);

		// 回合中第2步:服務器解析客戶端數據包,然後幹活,
		if (st.cmd == CMD_REGISTER)
		{
			printf("用戶要註冊學生信息\n");
			printf("學生姓名:%s,學生年齡:%d\n", st.name, st.age);
			// 在這裏服務器要進行真正的註冊動作,一般是插入數據庫一條信息
			
			// 回合中第3步:回覆客戶端
			st.stat = STAT_OK;
			ret = send(clifd, &st, sizeof(info), 0);
		}
		
	}

	return 0;
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章