網絡通訊做爲互聯網的技術支持,已被普遍應用在軟件開發中,不管是Web,服務端,客戶端仍是桌面應用,都是必須掌握的一門技術。web
在軟件開發層面實現遠程數據交換的編程技術。
複製代碼
要熟悉網絡編程,首先須要學習網絡協議的相關知識。面試
什麼網絡協議呢?網絡協議是爲網絡中進行數據交換定義的規則,以實現按此規範進行傳輸數據,就可在整個互聯網中進行數據交換的目標。因此說網絡協議是網絡通訊的基礎。編程
網絡協議中其中最著名就是TCP/IP協議族。TCP/IP協議族一般被認爲是一個四層的協議系統。從上到下依次爲:服務器
從TCP/IP協議族各層次的職責來看,網路數據的傳遞是從上到下依次傳遞。以Http協議爲爲例,具體的數據傳輸過程如圖所示:網絡
固然,這只是數據的流向,實際上網絡數據在傳輸過程當中須要封裝與分解,具體的過程如圖所示(圖片來源於百度圖片):socket
簡單來講,數據從本機上傳到網絡以前,會在TCP/IP協議族的每一層添加協議首部,到達目標主機後,目標主機再進行分解,從而獲取須要的數據。學習
在衆多的網絡協議中,使用最普遍的應該就是TCP協議(傳輸控制協議)了,它是一種面向鏈接,可靠的,基於字節流的傳輸層協議。其運行過程分爲三個階段:創建鏈接、交換數據、斷開鏈接。spa
在使用TCP協議交換數據前,需先創建一條鏈接,在不須要發送數據的時候,須要斷開鏈接,以釋放資源。操作系統
TCP協議創建鏈接時須要三次握手,其過程可以使用如下場景來描述:code
面試官:說一下TCP創建鏈接時三次握手的過程。
小明: 三次握手?
面試官: 嗯。
小明:握手完成了。
面試官:what?
複製代碼
沒錯,從小明開始問三次握手到說握手完成就是三次握手的過程,具體的過程以下:
爲何須要3次握手而不是2次呢?
由於客戶端收到服務端的應答後,知道鏈接已創建成功,可是服務端並不知道本身發送的確認報文客戶端是否收到,
因此須要客戶端對服務端的報文進行確認。
複製代碼
服務端必定能收到客戶端發送的確認報文麼?
不必定,若是收不到,那麼鏈接就不會創建,因此,3次握手只是理論上確保創建鏈接的次數。那可否經過4次握手呢?
不行,再握下去就是雞生蛋,蛋生雞的問題了。
複製代碼
TCP斷開鏈接時須要四次握手,爲何須要4次呢?這是因爲TCP半關閉的性質形成的。所謂半關閉,就是能夠發送數據,卻不能接收數據或只能接收數據,不能發送數據。
四次握手的過程:(因爲主動斷開鏈接可發送在客戶端,也可發送在服務端,因此下面以A,B來區分兩端)
B端在對A端進行確認的時候爲何不一樣時發一個FIN呢? 能夠同時發。分開發是爲了考慮B端在對A端確認後,可能還會給A繼續發送數據的狀況。
下面以一張圖來描述TCP的狀態變遷過程(圖片來自百度圖片)
圖片中的全部狀態對應於TCP的創建和鏈接過程,下面簡單介紹一下這幾種狀態:
LISTEN: 服務端狀態,表示服務端正在等待客戶端的鏈接請求,處於監聽狀態;
SYN收到:服務端狀態,服務端已收到客戶端的鏈接請求,並對客戶端的請求發送了ACK確認(第二次握手完成);
SYN_SENT: 客戶端狀態,客戶端發送SYN或數據後的狀態(第一次握手完成);
ESTABLISHED: 客戶端對服務端的SYN進行確認後處於ESTABLISHED,服務端收到客戶端發送的ACK後也會處於
ESTABLISHED狀態(三次握手完成後的狀態);
FIN_WAIT_1: 主動關閉的一端的狀態,發送FIN後的狀態(斷開鏈接時的第一次握手完成);
FIN_WAIT_2: 主動關閉的一端的狀態,收到另外一端的ACK確認後的狀態(斷開鏈接時的第二次握手完成);
CLOSING:主動關閉的一端的狀態,收到另外一端的FIN,並對其進行確認後的狀態(客戶端和服務端同時關閉的狀況);
TIME_WAIT: 主動關閉的一端的狀態,,收到另外一端的FIN或(FIN和ACK)後,對其進行確認後的狀態
(斷開鏈接時最後一次握手完成);
CLOSE_WAIT: 被動關閉的一端的狀態,收到另外一端發送的FIN並對其進行確認後的狀態(斷開鏈接時第二次握手完成);
LAST_ACK: 被動關閉的一端的狀態,發送FIN後的狀態(斷開鏈接時第三次握手完成);
CLOSED:鏈接完全斷開;
複製代碼
TCP/IP協議族誕生以後,各個平臺(Window, Unix)就按照此協議規範在系統層面爲開發網絡程序提供了統一的接口——Socket。經過這個面向傳輸層協議的系統接口,咱們可經過TCP/UDP協議快速實現網絡數據的交換,同時也可用來實現應用層協議,如HTTP, SSL等。
Socket是操做系統爲上層應用實現網絡數據交換提供的接口,咱們可經過如下場景來理解:
當你給別人打電話的時候首先要確認打給誰,其次確認打哪一個號碼,經過這兩個條件就可準確的聯繫到對方。那麼在網絡中傳輸數據也是一樣的道理,在網絡中定位主機是經過IP來實現的,一個IP表明了一臺主機,可是每臺主機有不少個端口號,因此要準確地與某個應用進行數據交換,除了IP地址外,還須要一個端口號。有了這兩個條件,就可經過Socket實現數據交換。因而可知,Socket其實就至關於一部手機,兩部手機之間創建一條通路便可實現通話。
代碼實現(Linux C編程):
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int sockfd = 0, n = 0;
char recvBuff[1024];
struct sockaddr_in serv_addr;
if(argc != 2)
{
printf("\n Usage: %s <ip of server> \n",argv[0]);
return 1;
}
memset(recvBuff, '0',sizeof(recvBuff));
// 建立socket
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Error : Could not create socket \n");
return 1;
}
// 設置IP和端口
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(5000);
if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
{
printf("\n inet_pton error occured\n");
return 1;
}
// 鏈接到指定的IP和端口 -> 鏈接成功後即三次握手完成
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
printf("\n Error : Connect Failed \n");
return 1;
}
// 讀數據
while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0)
{
recvBuff[n] = 0;
if(fputs(recvBuff, stdout) == EOF)
{
printf("\n Error : Fputs error\n");
}
}
if(n < 0)
{
printf("\n Read error \n");
}
close(scokfd);
return 0;
}
複製代碼
代碼實現:(Linux C編程)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
int main(int argc, char *argv[])
{
int listenfd = 0, connfd = 0;
struct sockaddr_in serv_addr;
char sendBuff[1025];
time_t ticks;
// 建立socket
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
memset(sendBuff, '0', sizeof(sendBuff));
// 綁定IP地址和端口
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(5000);
bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// 監聽客戶端口的鏈接請求 -> 對應於狀態圖中的LISTEN狀態
listen(listenfd, 10);
while(1)
{
// 接收客戶端的請求 -> 與客戶端三次握手完成
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
ticks = time(NULL);
snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks));
// 向客戶端發送數據
write(connfd, sendBuff, strlen(sendBuff));
// 關閉socket
close(connfd);
sleep(1);
}
}
複製代碼
代碼出處 https://www.thegeekstuff.com/2011/12/c-socket-programming/
經過以上代碼,咱們對socket有了一個簡單的認識,同時也瞭解了數據交換的基本流程。後面會對基於TCP協議的HTTP協議進行一個詳細的介紹。
《TCP/IP詳解 卷一》