TCP/IP
TCP/IP協議是一 系列規則(協議)的統稱,他們定義了消息在網絡間進行傳輸的規則
是供已連接互聯網的設備進行通信的通信規則
OSI模型只是一個基本概念,而TCP/IP協議是基于這個概念的具體實現
TCP和UDP協議
TCP:傳輸控制協議,面向連接,更安全,效率較低,一對一
UDP:用戶數據報協議,無連接,不保證可靠性,效率較高,隨意組合
TCP
是面向連接的協議,也就是說,在收發數據前,必須和對方建立可靠的連接
并且在消息傳送過程中是有順序的,并且是不會丟包(丟棄消息)的
如果某一條消息在傳送過程中失敗了,會重新發送消息,直到成功
三次握手 四次揮手
三次握手建立連接
第一次握手(C->S)
TCP連接請求
第二次握手(S->C)
TCP授予連接
第三次握手(C->S)
TCP確認連接
四次揮手斷開連接
第一次揮手(C->S)
客戶端告訴服務器數據已經發完,如果服務器還有消息就請快發完
第二次揮手(S->C)
服務器告訴客戶端繼續等待服務器的消息
第三次揮手(S->C)
服務器告訴客戶端消息發送完成,可以正式斷開連接
第四次揮手(C- ->S)
客戶端告訴服務器等一會如果沒有收到服務器回復就斷開 了
提供可靠的服務,通過TCP連接傳送的數據,做到無差錯、不丟失、不重復、且按順序到達
UDP
是一種無需建立連接就可以發送封裝的IP數據包的方法,提供了面向事務的簡單不可靠信息傳送服務
具有資源消耗小,處理速度快的特點
UDP協議不像TCP協議需要建立連接有三次握手和四次揮手,當使用UDP協議發送信息時會直接把信息數據扔到網絡上,所以也就造成了UDP的不可靠性。信息在這個傳遞過程中是有可能丟失的雖然UDP是一個不靠譜的協議,但是由于它不需要建立連接。也不會像TCP協議那樣攜帶更多的信息,所以它具有更好的傳輸效率
?
網絡游戲通信方案
Socket\HTTP\FTP
Socket:網絡嵌套字
HTTP:超文本傳輸協議,主要完成短鏈接網絡游戲需求
FTP:主要用來完成資源的下載和上傳
byte [] ipAddress = new byte[]{118,102,111,11};
IPAddress ip1 =new IPAddress(ipAddress);
//用byte進行初始化//使用字符串進行初始化
IPAdress ip =IPAddress.Parse("118.102.111.11");//ipi27.0.0.1代表本機地址//命名空間System.NetIPEndPoint ipPoint =new IPEndPoint(0x79666F0B,8080);
IPEndPoint ipPoint2 =new IPEndPoint(IPAddress.Parse("118.102.111.11"),8080);
域名解析
也叫域名指向,服務器設置,域名配置
域名系統是互聯網上的一種服務,管理名字與IP的對應關系
//IPHostEntry類
//域名解析后的返回值,可以獲取該對象的IP地址主機名等信息//DNS類
print(Dns.GetHostName());
//獲取主機名字//獲取指定域名的ip地址
//可能會阻塞主線程
IPHostEntry entry = Dns.GetHostEntry("www.baidu.com");for(int i=0;i<entry.AddressList.Length;i++){print(entry.AddressList[i]);
}
Socket 套接字
c#用于提供網絡通信的一個類
類名:Socket? ????????命名空間:System.Net.Socket
Socket套接字時支持TCP/IP網絡通信的基本操作單位
一個套接字包括:本機地址IP和端口/對方主機的IP地址和端口/雙方通信的協議信息
一個Socket對象表示一個本地或者遠程嵌套字信息,可被視為一個數據通道,鏈接服務器和客戶端,可以接受數據的發送和接收
適合長連接的網絡游戲
AddressFamily 網絡尋址 枚舉類型,決定尋址方案
????????InterNetwork IPv4尋址
????????InterNetwork6 IPv6尋址
SocketType 嵌套字枚舉類型 決定使用的套接字類型
????????Dgram 支持數據報,最大長度固定,無連接,不可靠的消息(主要用于UDP通信)
????????Stream 支持可靠、雙向、基于連接的字節流(主要用于TCP通信)
ProtocolType 協議類型枚舉,決定套接字使用的通信協議
? ? ? ? TCP
? ? ? ? UDP
流套接字
主要實現TCP通信
數據報套接字
主要實現UDP通信
//TCP流套接字Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//UDPSocket socketUdp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
實現服務端基本邏輯
//創建套接字Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//用bind方法將套接字與本地地址綁定//端口號要大于1024,且不能被占用IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);try{socketTcp.Bind(ipPoint);}catch (Exception e){return;}//用LISTEN監聽socketTcp.Listen(1024);//accept等待客戶端連入//建立連接,返回套接字Socket socketClient =socketTcp.Accept();//收發數據socketClient.Send(Encoding.UTF8.GetBytes("HFUT"));byte[] result=new byte[1024];int receiveNum=socketClient.Receive(result);//返回值為接收到的數量socketClient.Shutdown(SocketShutdown.Both);socketClient.Close();
實現客戶端基本邏輯
//創建套接字Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//與服務端相連IPEndPoint ipPoint =new IPEndPoint(IPAddress.Parse("127.0.0.0"), 8080);//上述應填寫服務端的IP地址try{socket.Connect(ipPoint);}catch(SocketException e) {if(e.ErrorCode ==10061){}else{}}//收發數據byte[] data = new byte[1024];int num = socket.Receive(data);//發送數據socket.Send(Encoding.UTF8.GetBytes("HFUTER"));socket.Shutdown(SocketShutdown.Both);socket.Close();
區分消息類型
發送自定義類的二進制信息(需要繼承BaseData類)
區分不同消息:給發送的消息添加標識,比如添加消息ID
例如選擇int做消息ID,那么熱前四個字節為消息ID,后面為數據類的內容
實現:
????????1.創建一個消息基類,基類繼承BaseData,基類添加獲取消息的ID的方法或者屬性
? ? ? ? 2.讓想要被發送的消息繼承該類,實現序列化反序列化的方法
? ? ? ? 3.寫客戶端和服務端收發處理消息的邏輯
分包與黏包
網絡通信中由于各種因素造成的消息與消息之間出現的兩種狀態
分包:一個消息分成了多個消息進行發送
黏包:一個消息和另一個消息黏在一起
兩者可能同時發生
解決辦法:
可以通過消息的長度來判斷是否出現分包或者黏包
為消息添加長度,消息長度記錄消息的長度
心跳信息
在長連接中,客戶端和服務端之間定期發送的一種特殊數據包,通知對方自己還在線,以保證長連接的有效性
其發送時間間隔是固定的,且持續,因此稱之為心跳消息
心跳消息可以避免非正常關閉客戶端時,服務器無法正常收到關閉連接消息,同時避免客戶端長期不發消息,防火墻或者路由器會斷開連接
當客戶端主動斷開時,服務端無法得知客戶端已經主動斷開
在客戶端中可以使用Disconect方法,看是否因為之前直接Close()從而沒有調用Disconect造成服務端無法及時獲取狀態(仍然無法準確地讓服務端得知客戶端已經斷開連接)
可以考慮自定義退出消息
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class QuitMsg : BaseMsg
{//繼承自消息基類//主動發送一條斷開連接的消息給服務端//重寫函數public override int GetBytesNum(){return 8;}public override int Reading(byte[] bytes, int beginIndex = 0){return 0;}public override byte[] Writing(){int index = 0;byte[] bytes = new byte[GetBytesNum()];WriteInt(bytes, GetID(), ref index);WriteInt(bytes, 0, ref index);return bytes;}public override int GetID(){return 1003;}
}
實現心跳消息
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class HeartMsg : BaseMsg
{//繼承消息基類public override int GetBytesNum(){return 8;}public override int Reading(byte[] bytes, int beginIndex = 0){//不需要反序列化任何變量return 0;}public override byte[] Writing(){int index = 0;byte[] bytes = new byte[GetBytesNum()];WriteInt(bytes, GetID(), ref index);WriteInt(bytes, 0, ref index);return bytes;}public override int GetID(){return 999;}
}
客戶端:定時發送消息
服務端:收心跳消息 記錄收到消息的時間
異步通信
方法中的邏輯還沒執行完,便繼續執行后面的內容
UNITY中的協同程序中的某系異步方法,有的使用的是多線程有的使用的是迭代器分步執行
//線程回調
public void CountDownAsync(int second, UnityAction callBack){Thread t = new Thread(() =>{while (true){print(second);Thread.Sleep(1000);--second;if (second == 0)break;}
//?的目的是,如果是空的,則不會執行callBack?.Invoke();});t.Start();print("開始倒計時");}//讓函數分布執行public async void CountDownAsync(int second){print("倒計時開始");//await->Task通過線程池開啟一個線程
//本質上將函數分布執行await Task.Run(() =>{while (true){print(second);Thread.Sleep(1000);--second;if (second == 0)break;}});print("倒計時結束");}
UCP通信
udp不會對數據包進行合并發送,不會出現黏包問題
UDP是不可靠連接,消息傳遞過程中可能出現無序、丟包等情況
為了避免分包,建議控制消息的大小在MTU(最大傳輸單元)范圍內
MTU:
最大傳輸單元,用來通知對方所能接受數據服務單元的最大尺寸
局域網:1472字節以內? ? ?互聯網:548字節以內
如果想要發送的消息過大,可以進行手動分包,但是手動分包的前提是解決UDP的無序和丟包的問題
UDP流程更簡單,效率高,但是不可靠
FTP
是支持因特網文件傳輸的各種規則的集合,使得文件可以被從一臺主機拷貝到另一臺主機上。此外,FTP還提供登錄、文件查詢以及其他繪畫控制等功能
FTP本質上是TCP通信,通過FTP,雙發至少需要簡歷兩個TCP連接,一個稱為控制連接,一個成為數據連接
FTP的數據連接和控制連接方向一般是相反的
兩種傳輸方式:1.ASCII傳輸? ? 2.二進制傳輸
FTP關鍵類
1.NetworkCredential 通信憑證類?
命名空間:System.Net
用于Ftp文件傳輸,設置賬號密碼
NetworkCredrntial n =new NetworkCredrntial("HFUTER","hfuter");
2.FtpWebRequest Ftp文件傳輸客戶端操作類
命名空間:System.Net
用于上傳,下載,刪除服務器上的文件
3.FtpWebResponse類
FtpWebRequest req = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/Test.txt")) as FtpWebRequest;//創建req.Abort();//停止Stream s= req.GetRequestStream();//獲取流對象FtpWebResponse res= req.GetResponse() as FtpWebResponse;//返回FTP服務器響應
HTTP
HTTP超文本傳輸協議就是一個在網絡中上傳下載文件的一套規則
其本質也是TCP通信,因此不會丟包、不會亂序
1.以TCP方式工作:
HTTP/1.1支持持久連接(目前使用版本)
2.HTTP是無狀態的:
HTTP不會記錄客戶端請求過的狀態
3.元信息作為標頭
主要數據前添加一段額外信息
請求類型和相應狀態碼
HTTP/1.1:GET\POST\HEAD\PUT\......
響應狀態碼:1xx\2xx\3xx\4xx\5xx
GET:請求獲取特定的資源
POST:請求提交數據進行處理
HTTP常用狀態碼:
200 OK;404 NOT FOUND;405 不支持請求的方法;501 服務器不能識別請求揮著沒有實現指定的請求
關鍵類
HttpWebRequest類:
命名空間:System.Net
HttpWebRequest是主要用于發送客戶端請求的類
主要用于:發送HTTP客戶端請求給服務器,可以進行消息通信、上傳、下載等等操作
?
//創建新的WebRequest,用于進行HTTP相關操作HttpWebRequest req = HttpWebRequest.Create(new Uri("http://192.168.50.109:8000/Http_Server/")) as HttpWebRequest;//終止傳輸 req.Abort();//獲取用于上傳的流Stream s = req.GetRequestStream();//返回HTTP服務器響應HttpWebResponse res = req.GetResponse() as HttpWebResponse;//異步獲取用于上傳的流req.BeginGetRequestStream()異步獲取返回的HTTP服務器響應//req.BeginGetResponse()
HttpWebResponse類:
命名空間:System.Net
主要用于獲取服務器反饋信息的類,可以通過HttpWebRequest對象中的GetResponse()方法獲取。當使用完畢時,要使用Close釋放
POST
上傳文件到HTTP資源服務器需要遵守的規則:
1:ContentType = "multipart/form-data; boundary=邊界字符串";
2:上傳的數據必須按照格式寫入流中
3:保證服務器允許上傳
4:寫入流前需要先設置ContentLength內容長度
WWW類
WWW是Unity提供給程序員簡單的訪問網頁的類,可以通過該類下載和上傳一些數據,在使用http協議時,默認的請求類型是Get,如果想要Post上傳,需要配合WWWFrom類使用(該類在較新Unity版本中會提示過時,但是仍可以使用,新版本將其功能整合進了UnityWebRequest類)
主要支持的協議:
1.http://和https:// 超文本傳輸協議
2.ftp:// 文件傳輸協議(但僅限于匿名下載)
3.file:// 本地文件傳輸協議,可以使用該協議異步加載本地文件(PC、IOS、Android都支持)
//創建
WWW www = new WWW("");//從下載數據返回一個音效切片AudioClip對象
www.GetAudioClip();//用下載數據中的圖像來替換現有的一個Texture2D對象
Texture2D tex = new Texture2D(100, 100);//從緩存加載AB包對象,如果該包不在緩存則自動下載存儲到緩存中,以便以后直接從本地緩存中加載
WWW.LoadFromCacheOrDownload("", 1);
UnityWebRequest
是一個Unity提供的一個模塊化的系統類,用于構成HTTP請求和處理HTTP響應,主要目標是讓Unity游戲和Web服務端進行交互,將之前WWW的相關功能都集成在了其中(新版本中都建議使用UnityWebRequest類來代替WWW類)
注意:
1.UnityWebRequest和WWW一樣,需要配合協同程序使用
2.UnityWebRequest和WWW一樣,支持http、ftp、file協議下載或加載資源
3.UnityWebRequest能夠上傳文件到HTTP資源服務器
UnityWebRequest類的常用操作:
//1.獲取文本或2進制StartCoroutine(LoadText());//2.獲取紋理StartCoroutine(LoadTexture());//3.獲取AB包StartCoroutine(LoadAB());IEnumerator LoadText(){UnityWebRequest req = UnityWebRequest.Get("http://192.168.50.109:8000/Http_Server/test.txt");//就會等待 服務器端響應后 斷開連接后 再繼續執行后面的內容yield return req.SendWebRequest();//如果處理成功 結果就是成功枚舉if(req.result == UnityWebRequest.Result.Success){//獨立的處理對象print(req.downloadHandler.text);byte[] bytes = req.downloadHandler.data;print("字節數組長度" + bytes.Length);}else{print("獲取失敗:" + req.result + req.error + req.responseCode);}}IEnumerator LoadTexture(){//UnityWebRequest req = UnityWebRequestTexture.GetTexture("http://192.168.50.109:8000/Http_Server/HFUTER.jpg");//UnityWebRequest req = UnityWebRequestTexture.GetTexture("ftp://127.0.0.1/HFUTER.jpg");UnityWebRequest req = UnityWebRequestTexture.GetTexture("file://" + Application.streamingAssetsPath + "/test.png");yield return req.SendWebRequest();if (req.result == UnityWebRequest.Result.Success){//(req.downloadHandler as DownloadHandlerTexture).texture//DownloadHandlerTexture.GetContent(req)//image.texture = (req.downloadHandler as DownloadHandlerTexture).texture;image.texture = DownloadHandlerTexture.GetContent(req);}elseprint("獲取失敗" + req.error + req.result + req.responseCode);}IEnumerator LoadAB(){UnityWebRequest req = UnityWebRequestAssetBundle.GetAssetBundle("http://192.168.50.109:8000/Http_Server/lua");req.SendWebRequest();while (!req.isDone){print(req.downloadProgress);print(req.downloadedBytes);
//每幀執行yield return null;}//yield return req.SendWebRequest();print(req.downloadProgress);print(req.downloadedBytes);if (req.result == UnityWebRequest.Result.Success){//AssetBundle ab = (req.downloadHandler as DownloadHandlerAssetBundle).assetBundle;AssetBundle ab = DownloadHandlerAssetBundle.GetContent(req);print(ab.name);}elseprint("獲取失敗" + req.error + req.result + req.responseCode);}