第一部分
先分別介紹IP/TCP協議族:
IP協議:
對於TCP/IP網絡來說,網絡層是其核心所在。該層的IP協議負責生成發往目的地的數據報以實現邏輯尋址,完成數據從網絡上一個節點向另一個節點的傳輸。
IP的主要目的是通過一個互聯的網絡傳輸數據報,涉及兩個最基本的功能。
●尋址(Addressing):IP協議根據數據報首部中包括的目的地址將數據報傳送到目的節點,這就要涉及傳送路徑的選擇,即路由功能。IP協議使用IP地址來實現路由。
●分片(Fragmentation): IP協議還提供對數據大小的分片和重組,以適應不同網絡對數據包大小的限制。如果網絡只能傳送小數據包,IP協議將對數據報進行分段並重新組成小塊再進行傳送。
IP是一個無連接的、不可靠的、點對點的協議,只能盡力(BestEffort)傳送數據,不能保證數據的到達。具體地講,主要有以下特性。
●IP協議提供無連接數據報服務,各個數據報獨立傳輸,可能沿着不同的路徑到達目的地,也可能不會按序到達目的地。
●IP協議不含錯誤檢測或錯誤恢復的編碼,屬於不可靠的協議。所謂不可靠,是從數據傳輸的可靠性不能保證的角度而言的,查詢的延誤及其他網絡通信故障都有可能導致所傳數據的丟失。對這種情況,IP協議本身不處理。它的不可靠並不能說明整個TCP/IP協議不可靠。如果要求數據傳輸具有可靠性,則要在IP的上面使用TCP協議加以保證。位於上一層的TCP協議則提供了錯誤檢測和恢復機制。
●作為一種點對點協議,雖然IP數據報攜帶源IP地址和目的IP地址,但進行數據傳輸時的對等實體一定是相鄰設備(同一網絡)中的對等實體。
IP協議的效率非常高,實現起來也較簡單。這是因為IP協議采用了盡力傳輸的思想,隨着底層網絡質量的日益提高,IP協議的盡力傳輸的優勢體現得更加明顯。
下圖是IP數據包的格式:
TCP/UDP協議:
傳輸層是TCP/IP協議中的非常重要的層次,提供了面向連接的傳輸控制協議(Transmission Control Protocol,TCP)和無連接的用戶數據報協議(User Datagram Protocol,UDP),負責提供端到端的數據傳輸服務,將任意數據通過網絡從發送方傳輸到接收方。TCP提供的是可靠的、可控制的傳輸服務,適用於各種網絡環境;UDP提供的服務輕便但不可靠,適用於可靠性較高的網絡環境。大部分Internet應用都使用TCP,因為它能夠確保數據不會丟失和被破壞。本章將對這兩種協議進行詳細分析。
在OSI模型中,傳輸層是介於網絡層和會話層之間的一個中間層次,彌補高層服務和網絡層服務之間的差距,並向高層用戶((應用程序)屏蔽通信子網的細節,使高層用戶看到的只是在兩個傳輸實體間的一條端到端的、用戶可控的、可靠的數據通路。在TCP/IP模型中,由於3個高層簡化為1個應用層,傳輸層是介於網絡層與應用層之間的一個層次。
網絡層協議提供網絡地址、路由、交付功能,而傳輸層協議提供了端到端數據傳輸的必要機制。傳輸層協議通常要負責以下幾項基本功能。
●創建進程到進程的通信,進程即正在運行的應用程序。進程之間通過傳輸層進行通信,發送進程向傳輸層發送數據,接收進程從傳輸層接收數據。
●提供控制機制,如流量控制、差錯控制。數據鏈路層定義相鄰節點的流量控制,而傳輸層定義端到端用戶之間的流量控制。
●提供連接機制。在數據傳輸開始時,通信雙方需要建立連接。在傳輸過程中,雙方還需要繼續通過協議來通信以驗證數據是否被正確接收。數據傳輸完成后,任一方都可關閉連接。
第二部分
下面就以王者榮耀為例說明TCP/IP協議族在網絡游戲中的應用。
2.1啟動、登錄游戲
首先我們啟動王者榮耀游戲APP。進人APP資頁,第一步會進行版本更新檢測。若檢查到資源包有更新則進行下載,若未檢查到更新的資源包,則本地加載游戲資源包及解壓資源包。這期間會跟圖片服務器(image.smoba.qq.com),用戶信息(game.eve.mdt.qq.com game.str.mdt.qq.com)、數據服務器(down.qq.com,dliedl.qq.com)進行交互,游戲界面加載過程主要是TCP傳輸。
通過對啟動階段進行抓包分析,游戲啟動過程中,客戶端通過DNS域名解析獲得王者榮耀游戲服務器的IP地址,建立TCP鏈接,進行數據交互來啟動游戲。
2.2游戲對戰階段
在實時對戰的過程中,客戶端與服務器間主要有兩個交互連接,一個為TCP連接,一個為UDP連接。開戰(玩家選擇自己的英雄角色及技能)及游戲分出勝負(水晶被毀)時,會觸發大量UDP包,包數量大於150;正式游戲過程中,終端與主服務器保持UDP和TCP連接。
(1)TCP長連接
游戲客戶端與服務器之間建立一個TCP長連接,由終端發起,通過這個TCP長連接進行心跳和其它信息交互,用以確認服務器狀態正常,心跳間隔3s,消息大小固定,流程如圖1所示:
(2)UDP報文
客戶端和服務器之間交互的報文,除了TCP連接報文以外,還有大量的UDP報文,分為兩類:
上行UDP報文:客戶端通過上行UDP報文將玩家所做的操作上報給服務器。
下行UDP報文:服務器匯總參加對戰的所有玩家的操作,通過下行UDP報文廣播給參加對戰的所有玩家的客戶端。
據騰訊消息,王者榮耀游戲采用的同步機制為幀同步(非狀態同步),主要流程如下:
1、玩家上報操作。
2、服務器收集各玩家上報的各自操作,進行匯總,以固定的時間間隔(例如60 ms)向參加對方的各玩家廣播所有玩家的操作。
令各客戶端接收到廣播,知道了所有玩家的操作,按照相同的游戲邏輯進行運算,得到相同的結果,呈現在游戲界面上。
通過上述幀同步機制,基本可以保證參加對戰的各玩家游戲步調是一致的,即游戲玩家間的顯示基本是相同的(因為網絡時延的不同會略有差異)。圖2王者榮耀游戲幀同步流程:
根據以上分析,總結如下:
一、王者榮耀游戲的啟動和登錄采用TCP連接,並且游戲交互過程中保持TcP連接通過心跳包(3s間隔)來檢測用戶是否在線。
二、王者榮耀游戲的客戶端操作與界面顯示是通過UDP數據流與服務器進行交互的,下行速率最小需80 kbps,上行速率需64 kbps。因此王者榮耀游戲對速率要求較小。
第三部分
推薦一個實現TCP/IP的簡單代碼。摘自(73條消息) C++:實現socket通信(TCP/IP)實例_Cche的博客-CSDN博客_c++ socket
(1)server端代碼
#include "pch.h" #include<iostream> #include<winsock.h> #pragma comment(lib,"ws2_32.lib") using namespace std; void initialization(); int main() { //定義長度變量 int send_len = 0; int recv_len = 0; int len = 0; //定義發送緩沖區和接受緩沖區 char send_buf[100]; char recv_buf[100]; //定義服務端套接字,接受請求套接字 SOCKET s_server; SOCKET s_accept; //服務端地址客戶端地址 SOCKADDR_IN server_addr; SOCKADDR_IN accept_addr; initialization(); //填充服務端信息 server_addr.sin_family = AF_INET; server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(5010); //創建套接字 s_server = socket(AF_INET, SOCK_STREAM, 0); if (bind(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) { cout << "套接字綁定失敗!" << endl; WSACleanup(); } else { cout << "套接字綁定成功!" << endl; } //設置套接字為監聽狀態 if (listen(s_server, SOMAXCONN) < 0) { cout << "設置監聽狀態失敗!" << endl; WSACleanup(); } else { cout << "設置監聽狀態成功!" << endl; } cout << "服務端正在監聽連接,請稍候...." << endl; //接受連接請求 len = sizeof(SOCKADDR); s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len); if (s_accept == SOCKET_ERROR) { cout << "連接失敗!" << endl; WSACleanup(); return 0; } cout << "連接建立,准備接受數據" << endl; //接收數據 while (1) { recv_len = recv(s_accept, recv_buf, 100, 0); if (recv_len < 0) { cout << "接受失敗!" << endl; break; } else { cout << "客戶端信息:" << recv_buf << endl; } cout << "請輸入回復信息:"; cin >> send_buf; send_len = send(s_accept, send_buf, 100, 0); if (send_len < 0) { cout << "發送失敗!" << endl; break; } } //關閉套接字 closesocket(s_server); closesocket(s_accept); //釋放DLL資源 WSACleanup(); return 0; } void initialization() { //初始化套接字庫 WORD w_req = MAKEWORD(2, 2);//版本號 WSADATA wsadata; int err; err = WSAStartup(w_req, &wsadata); if (err != 0) { cout << "初始化套接字庫失敗!" << endl; } else { cout << "初始化套接字庫成功!" << endl; } //檢測版本號 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) { cout << "套接字庫版本號不符!" << endl; WSACleanup(); } else { cout << "套接字庫版本正確!" << endl; } //填充服務端地址信息 }
(2)client端:
#include "pch.h" #include<iostream> #include<winsock.h> #pragma comment(lib,"ws2_32.lib") using namespace std; void initialization(); int main() { //定義長度變量 int send_len = 0; int recv_len = 0; //定義發送緩沖區和接受緩沖區 char send_buf[100]; char recv_buf[100]; //定義服務端套接字,接受請求套接字 SOCKET s_server; //服務端地址客戶端地址 SOCKADDR_IN server_addr; initialization(); //填充服務端信息 server_addr.sin_family = AF_INET; server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(1234); //創建套接字 s_server = socket(AF_INET, SOCK_STREAM, 0); if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) { cout << "服務器連接失敗!" << endl; WSACleanup(); } else { cout << "服務器連接成功!" << endl; } //發送,接收數據 while (1) { cout << "請輸入發送信息:"; cin >> send_buf; send_len = send(s_server, send_buf, 100, 0); if (send_len < 0) { cout << "發送失敗!" << endl; break; } recv_len = recv(s_server, recv_buf, 100, 0); if (recv_len < 0) { cout << "接受失敗!" << endl; break; } else { cout << "服務端信息:" << recv_buf << endl; } } //關閉套接字 closesocket(s_server); //釋放DLL資源 WSACleanup(); return 0; } void initialization() { //初始化套接字庫 WORD w_req = MAKEWORD(2, 2);//版本號 WSADATA wsadata; int err; err = WSAStartup(w_req, &wsadata); if (err != 0) { cout << "初始化套接字庫失敗!" << endl; } else { cout << "初始化套接字庫成功!" << endl; } //檢測版本號 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) { cout << "套接字庫版本號不符!" << endl; WSACleanup(); } else { cout << "套接字庫版本正確!" << endl; } //填充服務端地址信息 }