Unity-网络开发(二)
網絡通信
網絡游戲通信方案概述
弱聯網和強聯網游戲
網絡游戲是以C/S模型為基礎進行開發的由客戶端和服務端組成
弱聯網游戲:
這種游戲不會頻繁的進行數據通信,客戶端和服務端之間每次連接只處理一次請求,服務端處理完客戶端的請求后返回數據后就斷開連接了
強聯網游戲:
這種游戲會頻繁的和服務端進行通信,會一直和服務端保持連接狀態,不停的和服務器之間交換數據通過之前的知識我們知道,網絡游戲是以C/S模型為基礎進行開發的由客戶端和服務端組成
弱聯網游戲代表:
一般的三消類休閑游戲、卡牌游戲等都會是弱聯網游戲,這些游戲的核心玩法都由客戶端完成,客戶端處理完成后只是告訴服務端一個結果,服務端驗證結果即可,不需要隨時通信
比如:開心消消樂、刀塔傳奇、我叫MT等等
強聯網游戲代表:
一般的MMORPG(角色扮演)、MOBA(多人在線競技游戲)、ACT(動作游戲)等等都會是強聯網游戲,這些游戲的部分核心邏輯是由服務端進行處理,客戶端和服務端之間不停的在同步信息
比如:王者榮耀、守望先鋒、和平精英等等
長連接和短連接游戲
長連接和短連接游戲是按照網絡游戲通信特點來劃分的
弱聯網游戲——>短連接游戲
強聯網游戲——>長連接游戲
短連接游戲:
需要傳輸數據時,建立連接,傳輸數據,獲得響應,斷開連接
通信特點:需要通信時再連接,通信完畢斷開連接
通信方式:HTTP超文本傳輸協議、HTTPS安全的超文本傳輸協議(他們本質上是TCP協議)
長連接游戲:不管是否需要傳輸數據,客戶端與服務器一直處于連接狀態,除非一端主動斷開,或
者出現意外情況(客戶端關閉或服務端崩潰等)
通信特點:連接一直建立,可以實時的傳輸數據
通信方式:TCP傳輸控制協議 或 UDP用戶數據報協議
Socket、HTTP、FTP
Socket:網絡套接字,是對網絡中不同主機上的應用進程之間進行雙向通信的端點的抽象,一個套接字就是網絡上進程通信的一端,提供了應用層進程利用網絡協議交換數據的機制
主要用于制作長連接游戲(強聯網游戲)
Http/Https:(安全的)超文本傳輸協議,是一個簡單的請求-響應協議,它通常運行在TCP協議之上,它指定了客戶端可能發送給服務端什么樣的信息以及得到什么樣的響應。
主要用于制作短連接游戲(弱聯網游戲),也可以用來進行資源下載
FTP:文件傳輸協議,是用于在網絡上進行文件傳輸的一套標準協議,可以利用它來進行網絡上資源的下載和上傳。它也是基于TCP的傳輸,是面向連接的,為文件傳輸提供了可靠的保證
網絡通信基礎
IP地址和端口類
#region IPAddress類//命名空間:System.Net;//類名:IPAddress//初始化IP信息的方式//1.用byte數組進行初始化byte[] ipAddress = new byte[] { 118, 102, 111, 11 };IPAddress ip1 = new IPAddress(ipAddress);//2.用long長整型進行初始化//4字節對應的長整型 一般不建議大家使用IPAddress ip2 = new IPAddress(0x79666F0B);//3.推薦使用的方式 使用字符串轉換IPAddress ip3 = IPAddress.Parse("118.102.111.11");//特殊IP地址//127.0.0.1代表本機地址//一些靜態成員//獲取可用的IPv6地址//IPAddress.IPv6Any#endregion#region IPEndPoint類//命名空間:System.Net;//類名:IPEndPoint//IPEndPoint類將網絡端點表示為IP地址和端口號,表現為IP地址和端口號的組合//初始化方式IPEndPoint ipPoint = new IPEndPoint(0x79666F0B, 8080);IPEndPoint ipPoint2 = new IPEndPoint(IPAddress.Parse("118.102.111.11"), 8080);#endregion#region 總結//程序表示IP信息IPAddress ip = IPAddress.Parse("IPv4地址");//程序表示通信目標IPEndPoint point = new IPEndPoint(ip, 8080);#endregion域名解析
域名解析也叫域名指向、服務器設置、域名配置以及反向IP登記等等,說得簡單點就是將好記的域名解析成IP,IP地址是網絡上標識站點的數字地址,但是IP地址相對來說記憶困難,所以為了方便記憶,采用域名來代替IP地址標識站點地址。
比如 我們要登錄一個網頁 www.baidu.com 這個就是域名 我們可以通過記憶域名來記憶一個遠端服務器的地址,而不是記錄一個復雜的IP地址
域名解析就是域名到IP地址的轉換過程。域名的解析工作由DNS服務器完成,我們在進行通信時有時會有需求通過域名獲取IP
域名系統(英文:Domain Name System,縮寫:DNS)是互聯網的一項服務,它作為將域名和IP地址相互映射的一個分布式數據庫,能夠使人更方便地訪問互聯網,是因特網上解決網上機器命名的一種系統,因為IP地址記憶不方便,就采用了域名系統來管理名字和IP的對應關系
#region IPHostEntry類//命名空間:System.Net//類名:IPHostEntry//主要作用:域名解析后的返回值 可以通過該對象獲取IP地址、主機名等等信息//該類不會自己聲明,都是作為某些方法的返回值返回信息,我們主要通過該類對象獲取返回的信息//獲取關聯IP 成員變量:AddressList//獲取主機別名列表 成員變量:Aliases//獲取DNS名稱 成員變量:HostName#endregion#region Dns類//命名空間:System.Net//類名:Dns//主要作用:Dns是一個靜態類,提供了很多靜態方法,可以使用它來根據域名獲取IP地址//常用方法//1.獲取本地系統的主機名print(Dns.GetHostName());//2.獲取指定域名的IP信息//根據域名獲取//同步獲取//注意:由于獲取遠程主機信息是需要進行網路通信,所以可能會阻塞主線程IPHostEntry entry = Dns.GetHostEntry("www.baidu.com");for (int i = 0; i < entry.AddressList.Length; i++){print("IP地址:" + entry.AddressList[i]);}for (int i = 0; i < entry.Aliases.Length; i++){print("主機別名" + entry.Aliases[i]);}print("DNS服務器名稱" + entry.HostName);//異步獲取GetHostEntry();#endregion如果不知道對方的IP地址,想通過域名和對方進行通信,可以通過Dns類通過域名得到IP地址后再和對方建立連接并通信
序列化和反序列化2進制數據
網絡通信中傳輸的數據
在網絡通信中
把想要傳遞的類對象信息序列化為2進制數據(一般為byte字節數組)
再將該2進制數據通過網絡傳輸給遠端設備
遠端設備獲取到該2進制數據后再將其反序列化為對應的類對象
序列化:
將類對象信息轉換為可保存或傳輸的格式的過程
反序列化:
與序列化相對,將保存或傳輸過來的格式轉換為類對象的過程
比如
將C#類對象序列化為xml、json、2進制三種格式的數據保存在本地,達到持久化的目的,再將保存在本地的持久化數據文件反序列化為C#類對象
字符編碼
字符編碼(英語:Character encoding)也稱字集碼
是把字符集中的字符,編碼為指定集合中某一對象,以便文本在計算機中存儲或通過網絡進行傳遞。
**說人話:計算機里只能存數字(2機制),所以如果文字字符想要進行存儲的話,就需要把對應的文字字符轉換為數字才能進行處理,而字符編碼就是文字字符在計算機中和數值的對應關系,是人為定義的一種映射規則。**比如
ASCII碼(一種字符編碼規則)中 數值65 用來映射 字符A
我們存儲 A 這個字符,本質上存在內存中的是數值65對應的2進制是0100 0001
常見的一些字符編碼規則有
每個國家針對自己國家語言制定的編碼規則(因為語言文字的數量各不相同)
ASCII碼(美國)、GB2312編碼(中國)、Shift_JIS編碼(日本)、Euc-kr(韓國)等等
世界通用的編碼規則(把所有語言統一到一套編碼里)
Unicode編碼 以及 基于Unicode實現的編碼規則
UTF-8、UTF-16、UTF-32
亂碼
假設我們的一個文件是采用中國制定的GB2312編碼進行編輯存儲的,而此時我們并不使用GB2312這個編碼規則去讀取文件,而是采用其它的規則,比如日本制定的Shift_JIS編碼讀取該文件,那么由于編碼格式的存讀不統一就會造成亂碼的出現。因為不同的編碼規則,字符和數值的映射關系是不同的。
比如:130這個數值在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (?),在中文編碼中又會代表另一個符號。
ASCII碼
在計算機內部,所有的信息最終都是一個二進制值。
一個二進制數就是一位(bit),有0和1兩種狀態。一個字節(byte)是8個二進制數組成的,所以1 byte = 8 bit。
也就是說一個字節一共可以用來表示256種不同的狀態,從00000000到11111111。如果每一個狀態都代表一個符號的話,那么一個字節可以用來表示256個符號。
上個世界60年代,美國制定了一套算是最早的字符編碼,制定了一套基于英文字符與二進制位之間的對應關系。這套標準被稱為ASCII碼,一直使用到今天。
ASCII碼一共規定了128個字符的編碼。比如:
字符 A = 65(十進制數)= 0100 0001(二進制數)
字符 1 = 49(十進制數)= 0011 0001(二進制數)等等
下圖為ASCII碼的對照表
這128個字符的編碼規則,只占用了一個字節的后面7位,最前面的一位統一規定為0。
英文國家使用128個字符編碼就足夠了,但是如果用來表示其它國家的語言,128個符號是遠遠不夠的,于是乎就出現了非ASCII碼們。
非ASCII碼
非ASCII碼基本都是基于ASCII碼進行的擴充,他們都保留了ASCII碼0~127這段編碼的規范。也就是說非ASCII碼的前面部分往往是和ASCII碼的規則是相同的。
對于一些歐洲國家,他們使用一個字節便可以表示完自己所有的文字,他們利用了字節中閑置的最高位編入新的符號,因為我們知道ASCII碼的編碼規則是:一個字節中的8位,只占用了一個字節的后面7位,最前面的一位統一規定為0。所以這些語言系統中文字較少的國家讓最前面的一位可以為1,他們就可以為自己的文字在128~255這一段加入新的對應規則。
比如:130這個數值在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (?)
這種一個字節就把字符表示完的做法只適用于語言系統中文字較少的國家,因為他們的語言的字母是有限的。
對于使用象形文字的國家來說,一個字節完全不夠用!比如中國,我們的漢字多達10萬左右,一個字節最多也只能表示256種符號,是遠遠不夠的。所以必須使用多個字節來表示一個符號。比如我們前面提到的中國的簡體中文GB2312編碼,是使用兩個字節表示一個漢字,所以理論上來說可以表示256x256=65536個符號。
所以所謂的非ASCII碼,就是指的除了ASCII碼以外的編碼格式,每個國家都至少有1種針對自己語言文字的編碼格式,每一個編碼格式中 數值和字符的對應關系都可能不相同。這也就造成了前面說到的亂碼問題。在全世界范圍內進行網絡通信時,如果每個國家都使用不統一的編碼格式,那么出現亂碼的情況將隨處可見。
因此隨著互聯網的發展,人們決定要制定一套全世界統一的的編碼規則,將世界上所有的符號都納入其中,為每一個符號賦予獨一無二的編碼(2進制數值)。那么這樣就不會出現亂碼問題,影響信息的傳遞了。
Unicode
Unicode可以理解為是 Unique Code 的簡寫,翻譯過來是“唯一的編碼”。
它出現的主要原因就是用來解決亂碼問題的,它將世界上所有的符號都納入其中,每一個符號都為其分配一個獨一無二的二進制數表示它,那么亂碼問題就會消失。
Unicode是一個很大的集合,現在的規模可以容納100多萬個符號,每個符號對應的二進制數都不一樣。這樣就確保了不同語言的字符不會再有沖突。
那么這樣可能就存在一個問題,就是有的符號用1個字節8位就可以表示了,有的符號可能需要使用2個字節16位甚至3個字節24位才能表示。就比如說ASCII碼,它的存儲規則就是一個字節存儲一個字符,那么當我們使用Unicode編碼時,到底用幾個字節來存儲字符呢?
因此我們需要注意:Unicode編碼只是一個符號集,它只規定符號和二進制的對應關系,并沒有規定這個二進制數值應該如何存儲。
而UTF-8、UTF-16、UTF-32三種編碼格式才是基于Unicode實現的具體編碼方案
UTF-8編碼:可變字節編碼方案,可以根據實際情況使用1個、2個、3個、4個字節來存儲字符
UTF-16編碼:可變字節編碼方案,可以根據實際情況使用2個、4個字節來存儲字符
UTF-32編碼:固定字節編碼方案,用4個字節來存儲字符
UTF-8
UTF-8是Unicode的實現方式之一,它的最大特點是:它是一種變長的編碼方式,可以使用1~4個字節表示一個字符,根據不同的符號而變化字節的長度。
Unicode是世界上所有符號對應二進制數據的關系集合
UTF-8是Unicode的實現方式之一:UTF-8 = Unicode符號集 + 變長的編碼規則
序列化
#region 非字符串類型轉字節數組//關鍵類:BitConverter//所在命名空間:System//主要作用:除字符串的其它常用類型和字節數組相互轉換byte[] bytes = BitConverter.GetBytes(1);#endregion#region 字符串類型轉字節數組//關鍵類:Encoding//所在命名空間:System.Text//主要作用:將字符串類型和字節數組相互轉換,并且決定轉換時使用的字符編碼類型,網絡通信時建議大家使用UTF-8類型byte[] byte2 = Encoding.UTF8.GetBytes("的卡薩福利卡決勝巔峰卡視角的副駕駛的");#endregion#region 如何將一個類對象轉換為二進制//注意:網絡通信中我們不能直接使用數據持久化2進制知識點中的//BinaryFormatter 2進制格式化類//因為客戶端和服務器使用的語言可能不一樣,BinaryFormatter是C#的序列化規則,和其它語言之間的兼容性不好//如果使用它,那么其它語言開發的服務器無法對其進行反序列化//我們需要自己來處理將類對象數據序列化為字節數組//單純的轉換一個變量為字節數組非常的簡單//但是我們如何將一個類對象攜帶的所有信息放入到一個字節數組中呢//我們需要做以下幾步//1.明確字節數組的容量(注意:在確定字符串字節長度時要考慮解析時如何處理)PlayerInfo info = new PlayerInfo();info.lev = 10;info.name = "劉英博";info.atk = 88;info.sex = false;//得到的 這個Info數據 如果轉換成 字節數組 那么字節數組容器需要的容量int indexNum = sizeof(int) + //lev int類型 4sizeof(int) + //代表 name字符串轉換成字節數組后 數組的長度 4Encoding.UTF8.GetBytes(info.name).Length + //字符串具體字節數組的長度sizeof(short) + //atk short類型 2sizeof(bool); //sex bool類型 1//2.申明一個裝載信息的字節數組容器byte[] playerBytes = new byte[indexNum];//3.將對象中的所有信息轉為字節數組并放入該容器當中(可以利用數組中的CopeTo方法轉存字節數組)//CopyTo方法的第二個參數代表 從容器的第幾個位置開始存儲int index = 0;//從 playerBytes數組中的第幾個位置去存儲數據//等級BitConverter.GetBytes(info.lev).CopyTo(playerBytes, index);index += sizeof(int);//姓名byte[] strBytes = Encoding.UTF8.GetBytes(info.name);int num = strBytes.Length;//存儲的是姓名轉換成字節數組后 字節數組的長度BitConverter.GetBytes(num).CopyTo(playerBytes, index);index += sizeof(int);//存儲字符串的字節數組strBytes.CopyTo(playerBytes, index);index += num;//攻擊力BitConverter.GetBytes(info.atk).CopyTo(playerBytes, index);index += sizeof(short);//性別BitConverter.GetBytes(info.sex).CopyTo(playerBytes, index);index += sizeof(bool);#endregionBitConverter轉換非字符串的類型的變量為字節數組
Encoding.UTF8轉換字符串類型的變量為字節數組(注意:為了考慮反序列化,我們在轉存2進制,序列化字符串之前,先序列化字符串字節數組的長度)
反序列化
#region 字節數組轉非字符串類型//關鍵類:BitConverter//所在命名空間:System//主要作用:除字符串的其它常用類型和字節數組相互轉換byte[] bytes = BitConverter.GetBytes(99);int i = BitConverter.ToInt32(bytes, 0);print(i);#endregion#region 字節數組轉字符串類型//關鍵類:Encoding//所在命名空間:System.Text//主要作用:將字符串類型和字節數組相互轉換,并且決定轉換時使用的字符編碼類型,網絡通信時建議大家使用UTF-8類型byte[] bytes2 = Encoding.UTF8.GetBytes("123123空間大撒了房間阿斯利康放大鏡");string str = Encoding.UTF8.GetString(bytes2, 0, bytes2.Length);print(str);#endregion#region 如何將二進制數據轉為一個類對象//1.獲取到對應的字節數組PlayerInfo info = new PlayerInfo();info.lev = 10;info.name = "劉英博";info.atk = 88;info.sex = false;byte[] playerBytes = info.GetBytes();//2.將字節數組按照序列化時的順序進行反序列化(將對應字節分組轉換為對應類型變量)PlayerInfo info2 = new PlayerInfo();//等級int index = 0;info2.lev = BitConverter.ToInt32(playerBytes, index);index += 4;print(info2.lev);//姓名的長度int length = BitConverter.ToInt32(playerBytes, index);index += 4;//姓名字符串info2.name = Encoding.UTF8.GetString(playerBytes, index, length);index += length;print(info2.name);//攻擊力info2.atk = BitConverter.ToInt16(playerBytes, index);index += 2;print(info2.atk);//性別info2.sex = BitConverter.ToBoolean(playerBytes, index);index += 1;print(info2.sex);#endregionBitConverter轉換字節數組為非字符串的類型的變量
Encoding.UTF8轉換字節數組為字符串類型的變量(注意:先讀長度,再讀字符串)
套接字Socket
Socket概述
#region Socket套接字的作用 //它是C#提供給我們用于網絡通信的一個類(在其它語言當中也有對應的Socket類) //類名:Socket //命名空間:System.Net.Sockets//Socket套接字是支持TCP/IP網絡通信的基本操作單位 //一個套接字對象包含以下關鍵信息 //1.本機的IP地址和端口 //2.對方主機的IP地址和端口 //3.雙方通信的協議信息//一個Sccket對象表示一個本地或者遠程套接字信息 //它可以被視為一個數據通道 //這個通道連接與客戶端和服務端之間 //數據的發送和接受均通過這個通道進行//一般在制作長連接游戲時,我們會使用Socket套接字作為我們的通信方案 //我們通過它連接客戶端和服務端,通過它來收發消息 //你可以把它抽象的想象成一根管子,插在客戶端和服務端應用程序上,通過這個管子來傳遞交換信息 #endregion#region Socket的類型 //Socket套接字有3種不同的類型 //1.流套接字 // 主要用于實現TCP通信,提供了面向連接、可靠的、有序的、數據無差錯且無重復的數據傳輸服務 //2.數據報套接字 // 主要用于實現UDP通信,提供了無連接的通信服務,數據包的長度不能大于32KB,不提供正確性檢查,不保證順序,可能出現重發、丟失等情況 //3.原始套接字(不常用,不深入講解) // 主要用于實現IP數據包通信,用于直接訪問協議的較低層,常用于偵聽和分析數據包//通過Socket的構造函數 我們可以申明不同類型的套接字 //Socket s = new Socket() //參數一:AddressFamily 網絡尋址 枚舉類型,決定尋址方案 // 常用: // 1.InterNetwork IPv4尋址 // 2.InterNetwork6 IPv6尋址 // 做了解: // 1.UNIX UNIX本地到主機地址 // 2.ImpLink ARPANETIMP地址 // 3.Ipx IPX或SPX地址 // 4.Iso ISO協議的地址 // 5.Osi OSI協議的地址 // 7.NetBios NetBios地址 // 9.Atm 本機ATM服務地址//參數二:SocketType 套接字枚舉類型,決定使用的套接字類型 // 常用: // 1.Dgram 支持數據報,最大長度固定的無連接、不可靠的消息(主要用于UDP通信) // 2.Stream 支持可靠、雙向、基于連接的字節流(主要用于TCP通信) // 做了解: // 1.Raw 支持對基礎傳輸協議的訪問 // 2.Rdm 支持無連接、面向消息、以可靠方式發送的消息 // 3.Seqpacket 提供排序字節流的面向連接且可靠的雙向傳輸//參數三:ProtocolType 協議類型枚舉類型,決定套接字使用的通信協議 // 常用: // 1.TCP TCP傳輸控制協議 // 2.UDP UDP用戶數據報協議 // 做了解: // 1.IP IP網際協議 // 2.Icmp Icmp網際消息控制協議 // 3.Igmp Igmp網際組管理協議 // 4.Ggp 網關到網關協議 // 5.IPv4 Internet協議版本4 // 6.Pup PARC通用數據包協議 // 7.Idp Internet數據報協議 // 8.Raw 原始IP數據包協議 // 9.Ipx Internet數據包交換協議 // 10.Spx 順序包交換協議 // 11.IcmpV6 用于IPv6的Internet控制消息協議//2、3參數的常用搭配: // SocketType.Dgram + ProtocolType.Udp = UDP協議通信(常用,主要學習) // SocketType.Stream + ProtocolType.Tcp = TCP協議通信(常用,主要學習) // SocketType.Raw + ProtocolType.Icmp = Internet控制報文協議(了解) // SocketType.Raw + ProtocolType.Raw = 簡單的IP包通信(了解)//我們必須掌握的 //TCP流套接字 Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//UDP數據報套接字 Socket socketUdp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); #endregion#region Socket的常用屬性 //1.套接字的連接狀態 if(socketTcp.Connected) {} //2.獲取套接字的類型 print(socketTcp.SocketType); //3.獲取套接字的協議類型 print(socketTcp.ProtocolType); //4.獲取套接字的尋址方案 print(socketTcp.AddressFamily);//5.從網絡中獲取準備讀取的數據數據量 print(socketTcp.Available);//6.獲取本機EndPoint對象(注意 :IPEndPoint繼承EndPoint) //socketTcp.LocalEndPoint as IPEndPoint//7.獲取遠程EndPoint對象 //socketTcp.RemoteEndPoint as IPEndPoint #endregion#region Socket的常用方法 //1.主要用于服務端 // 1-1:綁定IP和端口 IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); socketTcp.Bind(ipPoint); // 1-2:設置客戶端連接的最大數量 socketTcp.Listen(10); // 1-3:等待客戶端連入 socketTcp.Accept();//2.主要用于客戶端 // 1-1:連接遠程服務端 socketTcp.Connect(IPAddress.Parse("118.12.123.11"), 8080);//3.客戶端服務端都會用的 // 1-1:同步發送和接收數據 // 1-2:異步發送和接收數據 // 1-3:釋放連接并關閉Socket,先與Close調用 socketTcp.Shutdown(SocketShutdown.Both); // 1-4:關閉連接,釋放所有Socket關聯資源 socketTcp.Close(); #endregionTCP通信
服務端和客戶端需要做什么
TCP協議三次握手的體現
TCP協議四次揮手的體現
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NhJVXRCO-1655036079003)(/Users/liuyingbo/Library/Application Support/typora-user-images/image-20220521151714969.png)]
TCP基本API
#region 實現服務端基本邏輯 //1.創建套接字Socket(TCP) Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2.用Bind方法將套接字與本地地址綁定 try {IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);socketTcp.Bind(ipPoint); } catch (Exception e) {Console.WriteLine("綁定報錯" + e.Message);return; } //3.用Listen方法監聽 socketTcp.Listen(1024); Console.WriteLine("服務端綁定監聽結束,等待客戶端連入"); //4.用Accept方法等待客戶端連接 //5.建立連接,Accept返回新套接字 Socket socketClient = socketTcp.Accept(); Console.WriteLine("有客戶端連入了"); //6.用Send和Receive相關方法收發數據 //發送 socketClient.Send(Encoding.UTF8.GetBytes("歡迎連入服務端")); //接受 byte[] result = new byte[1024]; //返回值為接受到的字節數 int receiveNum = socketClient.Receive(result); Console.WriteLine("接受到了{0}發來的消息:{1}",socketClient.RemoteEndPoint.ToString(),Encoding.UTF8.GetString(result, 0, receiveNum));//7.用Shutdown方法釋放連接 socketClient.Shutdown(SocketShutdown.Both); //8.關閉套接字 socketClient.Close(); #endregion#region 實現客戶端基本邏輯 //1.創建套接字Socket Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2.用Connect方法與服務端相連 //確定服務端的IP和端口 IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); try {socket.Connect(ipPoint); } catch (SocketException e) {if (e.ErrorCode == 10061)print("服務器拒絕連接");elseprint("連接服務器失敗" + e.ErrorCode);return; } //3.用Send和Receive相關方法收發數據//接收數據 byte[] receiveBytes = new byte[1024]; int receiveNum = socket.Receive(receiveBytes); print("收到服務端發來的消息:" + Encoding.UTF8.GetString(receiveBytes, 0, receiveNum));//發送數據 socket.Send(Encoding.UTF8.GetBytes("你好,我是客戶端"));//4.用Shutdown方法釋放連接 socket.Shutdown(SocketShutdown.Both); //5.關閉套接字 socket.Close(); #endregion如何區分消息
為發送的信息添加標識,比如添加消息ID
在所有發送的消息的頭部加上消息ID(int、short、byte、long都可以,根據實際情況選擇)
舉例說明:
如果選用int類型作為消息ID的類型
前4個字節為消息ID
后面的字節為數據類的內容
####***************************
這樣每次收到消息時,先把前4個字節取出來解析為消息ID
再根據ID進行消息反序列化即可
分包、黏包
什么是分包、黏包?
分包、黏包指在網絡通信中由于各種因素(網絡環境、API規則等)造成的消息與消息之間出現的兩種狀態
分包:一個消息分成了多個消息進行發送
黏包:一個消息和另一個消息黏在了一起
注意:分包和黏包可能同時發生
如何解決?
為消息添加頭部,頭部記錄消息的長度
當我們接收到消息時,通過消息長度來判斷是否分包、黏包
對消息進行拆分處理、合并處理
心跳消息
客戶端主動斷開連接會調用socket的 ShutDown和Close方法,但是通過調用這兩個方法后 服務器端無法得知客戶端已經主動斷開
客戶端嘗試使用Disconnect方法主動斷開連接,Socket當中有一個專門在客戶端使用的方法,客戶端調用該方法和服務器端斷開連接
服務器端可以通過Conected屬性判斷連接狀態決定是否釋放Socket
但是由于服務器端Conected變量表示的是上一次收發消息是否成功,所以服務器端無法準確判斷客戶端的連接狀態,因此 我們需要自定義一條退出消息 用于準確斷開和客戶端之間的連接
什么是心跳消息?
所謂心跳消息,就是在長連接中,客戶端和服務端之間定期發送的一種特殊的數據包,用于通知對方自己還在線,以確保長連接的有效性
由于其發送的時間間隔往往是固定的持續的,就像是心跳一樣一直存在,所以我們稱之為心跳消息
為什么需要心跳消息?
1.避免非正常關閉客戶端時,服務器無法正常收到關閉連接消息,通過心跳消息我們可以自定義超時判斷,如果超時沒有收到客戶端消息,證明客戶端已經斷開連接
2.避免客戶端長期不發送消息,防火墻或者路由器會斷開連接,我們可以通過心跳消息一直保持活躍狀態
實現心跳消息
客戶端:定時發送消息
服務器:不停檢測上次收到某客戶端消息的時間,如果超時則認為連接已經斷開
心跳消息是長連接項目中必備的一套邏輯規則,通過它可以幫助我們在服務器端及時的釋放掉失效的socket,可以有效避免當客戶端非正常關閉時,服務器端不能及時判斷連接已斷開
UDP通信
服務端和客戶端需要做什么
UDP相對TCP的區別
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wAcEnqfT-1655036079004)(/Users/liuyingbo/Library/Application Support/typora-user-images/image-20220521160026008.png)]
UDP的分包、黏包問題
分包
UDP本身作為無連接的不可靠的傳輸協議(適合頻繁發送較小的數據包),不會對數據包進行合并發送,一端發送什么數據,直接就發出去了,他不會對數據合并,因此在UDP當中不會出現黏包問題(除非你手動進行黏包)
分包問題
由于UDP是不可靠的連接,消息傳遞過程中可能出現無序、丟包等情況,所以如果允許UDP進行分包,那后果將會是災難性的,比如分包的后半段丟包或者比上半段先發來,我們在處理消息時將會非常困難,因此為了避免其分包,我們建議在發送UDP消息時控制消息的大小在MTU(最大傳輸單元)范圍內
MTU(Maximum Transmission Unit)最大傳輸單元,用來通知對方所能接受數據服務單元的最大尺寸
不同操作系統會提供用戶一個默認值
以太網和802.3對數據幀的長度限制,其最大值分別是1500字節和1492字節
由于UDP包本身帶有一些信息,因此建議:
1.局域網環境下:1472字節以內(1500減去UDP頭部28為1472)
2.互聯網環境下:548字節以內(老的ISP撥號網絡的標準值為576減去UDP頭部28為548)
只要遵守這個規則,就不會出現自動分包的情況
如果想要發送的消息確實比較大,要大于548字節或1472字節這個限制呢?比如我們要發一個5000字節的數據,他是一條完整消息,我們可以進行手動分包,將5000拆分成多個消息,每個消息不超過限制,但是手動分包的前提是要解決UDP的丟包和無序問題,我們可以將不可靠的UDP通信實現為可靠的UDP通信
比如:在消息中加入序號、消息總包數、自己的包ID、長度等等信息,并且實現消息確認、消息重發等功能
UDP基本API
#region 實現UDP服務端通信 收發字符串 //1.創建套接字 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //2.綁定本機地址 IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081); socket.Bind(ipPoint); Console.WriteLine("服務器開啟"); //3.接受消息 byte[] bytes = new byte[512]; //這個變量主要是用來記錄 誰發的信息給你 傳入函數后 在內部 它會幫助我們進行賦值 EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0); int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2); Console.WriteLine("IP:" + (remoteIpPoint2 as IPEndPoint).Address.ToString() +"port:" + (remoteIpPoint2 as IPEndPoint).Port +"發來了" +Encoding.UTF8.GetString(bytes, 0, length));//4.發送到指定目標 //由于我們先收 所以 我們已經知道誰發了消息給我 我直接發給它就行了 socket.SendTo(Encoding.UTF8.GetBytes("歡迎發送消息給服務器"), remoteIpPoint2);//5.釋放關閉 socket.Shutdown(SocketShutdown.Both); socket.Close(); #endregion#region 實現UDP客戶端通信 收發字符串 //1.創建套接字 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//2.綁定本機地址 IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); socket.Bind(ipPoint);//3.發送到指定目標 IPEndPoint remoteIpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081); //指定要發送的字節數 和 遠程計算機的 IP和端口 socket.SendTo(Encoding.UTF8.GetBytes("我來了"), remoteIpPoint);//4.接受消息 byte[] bytes = new byte[512]; //這個變量主要是用來記錄 誰發的信息給你 傳入函數后 在內部 它會幫助我們進行賦值 EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0); int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2); print("IP:" + (remoteIpPoint2 as IPEndPoint).Address.ToString() +"port:" + (remoteIpPoint2 as IPEndPoint).Port +"發來了" +Encoding.UTF8.GetString(bytes, 0, length));//5.釋放關閉 socket.Shutdown(SocketShutdown.Both); socket.Close(); #endregion文件傳輸FTP
FTP工作原理
FTP是什么
FTP(File Transfer Protocol)
文件傳輸協議,是支持Internet文件傳輸的各種規則所組成的集合,這些規則使Internet用戶可以把文件從一臺主機拷貝到另一臺主機上,
除此之外,FTP還提供登錄、目錄查詢以及其他會話控制等功能
說人話:FTP文件傳輸協議就是一個在網絡中上傳下載文件的一套規則
FTP的工作原理
劃重點:FTP的本質是TCP通信
通過FTP傳輸文件,雙發至少需要建立兩個TCP連接
一個稱為控制連接,用于傳輸FTP命令
一個稱為數據連接,用于傳輸文件數據
FTP的數據連接和控制連接方向一般是相反的
舉例說明:
用戶使用FTP客戶端連接FTP服務區請求下載文件
控制連接方向:客戶端主動連接服務器告知其下載命令
數據連接方向:服務端主動連接客戶端下發數據
當客戶端和FTP服務器建立控制連接后
需要告訴服務器采用那種傳輸模式
1.主動模式(Port模式):服務器主動連接客戶端,然后傳輸文件
2.被動模式(Passive模式):客戶端主動連接服務器,即控制連接和數據連接都由客戶端發起
一般情況下主動模式會受到客戶端防火墻影響,所以被動模式使用較多
在使用FTP進行數據傳輸時,有兩種數據傳輸方式
1.ASCII傳輸方式
以ASCII編碼方式傳輸數據,適用于傳輸,僅包含英文的命令和參數或者英文文本文件
2.二進制傳輸方式(建議使用該方式)
可以指定采用哪種編碼傳輸命令和文件數據,如果傳輸的文件不是英文文件則應該采用該方式
一般情況下,使用FTP傳輸文件時,客戶端必須先登錄服務器,獲得相應權限后才能上傳或下載文件
服務器也可以允許用戶匿名登錄FTP,不需要都擁有一個合法賬號
在實際學習過程中,我們并不需要利用FTP原理來實現FTP通信,FTP工作原理相關知識點,主要做了解
C#中實現了FTP通信需要用到的相關類
FtpWebRequest、FtpWebResponse、NetworkCredential
搭建FTP服務器
在實際商業項目開發當中,如果需要用FTP來進行文件傳輸,那么FTP服務器的解決方案都是由后端程序員來完成的,不管它使用哪種方式來搭建FTP服務器,只要能正常上傳下載內容并且保證安全性即可
可以使用Serv-U,具體方式自行百度
FTP相關API
#region NetworkCredential類 //命名空間:System.Net //NetworkCredential通信憑證類 //用于在Ftp文件傳輸時,設置賬號密碼 NetworkCredential n = new NetworkCredential("liuyingbo", "liuyingbo123"); #endregion#region FtpWebRequest類 //命名空間:System.Net //Ftp文件傳輸協議客戶端操作類 //主要用于:上傳、下載、刪除服務器上的文件//重要方法 //1.Create 創建新的WebRequest,用于進行Ftp相關操作 FtpWebRequest req = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/Test.txt")) as FtpWebRequest; //2.Abort 如果正在進行文件傳輸,用此方法可以終止傳輸 req.Abort(); //3.GetRequestStream 獲取用于上傳的流 Stream s = req.GetRequestStream(); //4.GetResponse 返回FTP服務器響應 //FtpWebResponse res = req.GetResponse() as FtpWebResponse;//重要成員 //1.Credentials 通信憑證,設置為NetworkCredential對象 req.Credentials = n; //2.KeepAlive bool值,當完成請求時是否關閉到FTP服務器的控制連接(默認為true,不關閉) req.KeepAlive = false; //3.Method 操作命令設置 // WebRequestMethods.Ftp類中的操作命令屬性 // DeleteFile 刪除文件 // DownloadFile 下載文件 // ListDirectory 獲取文件簡短列表 // ListDirectoryDetails 獲取文件詳細列表 // MakeDirectory 創建目錄 // RemoveDirectory 刪除目錄 // UploadFile 上傳文件 req.Method = WebRequestMethods.Ftp.DownloadFile; //4.UseBinary 是否使用2進制傳輸 req.UseBinary = true; //5.RenameTo 重命名 //req.RenameTo = "myTest.txt"; #endregion#region FtpWebResponse類 //命名空間:System.Net //它是用于封裝FTP服務器對請求的響應 //它提供操作狀態以及從服務器下載數據 //我們可以通過FtpWebRequest對象中的GetResponse()方法獲取 //當使用完畢時,要使用Close釋放//通過它來真正的從服務器獲取內容 FtpWebResponse res = req.GetResponse() as FtpWebResponse;//重要方法: //1.Close:釋放所有資源 res.Close(); //2.GetResponseStream:返回從FTP服務器下載數據的流 Stream stream = res.GetResponseStream();//重要成員: //1.ContentLength:接受到數據的長度 print(res.ContentLength); //2.ContentType:接受數據的類型 print(res.ContentType); //3.StatusCode:FTP服務器下發的最新狀態碼 print(res.StatusCode); //4.StatusDescription:FTP服務器下發的狀態代碼的文本 print(res.StatusDescription); //5.BannerMessage:登錄前建立連接時FTP服務器發送的消息 print(res.BannerMessage); //6.ExitMessage:FTP會話結束時服務器發送的消息 //7.LastModified:FTP服務器上的文件的上次修改日期和時間 #endregion#region 總結 //通過C#提供的這3個類 //我們便可以完成客戶端向FTP服務器 //操作文件的需求,比如 //上傳、下載、刪除文件 #endregion超文本傳輸協議HTTP
HTTP工作原理
HTTP是什么
HTTP(HyperText Transfer Protocol)
超文本傳輸協議,是因特網上應用最為廣泛的一種網絡傳輸協議。最初設計HTTP的,目的是為了提供一種發布和接收由文本文件組成的HTML頁面的方法,后來發展到除了文本數據外,還可以傳輸圖片、音頻、視頻、壓縮文件以及各種程序文件等。
HTTP主要用于超文本傳輸,因此相對FTP顯得更簡單一些,目前常見的HTTP標準是HTTP/1.1.
說人話:HTTP超文本傳輸協議就是一個在網絡中上傳下載文件的一套規則
HTTP的工作原理
劃重點:HTTP的本質也是TCP通信
HTTP定義了Web客戶端(一般指瀏覽器)如何從Web服務器請求Web頁面,以及服務器如何把Web頁面傳送給客戶端。
HTTP客戶端首先與服務器建立TCP連接,然后客戶端通過套接字發送HTTP請求,并通過套接字接收HTTP響應,由于HTTP采用TCP傳輸數據,因此不會丟包、不會亂序。
HTTP的工作原理主要有以下三個特點
HTTP是以TCP方式工作
在HTTP/1.0中,客戶端和服務器建立TCP連接后,發送一個請求到服務器,服務器發送一個應答給客戶端,然后立即斷開TCP連接,他們的主要步驟為:
1.客戶端與服務端建立TCP連接
2.客戶端向服務端發出請求
3.若服務端接受請求,則回送響應碼和所需的信息
4.客戶端與服務端斷開TCP連接
需要注意,HTTP/1.1 支持持久連接,即客戶端和服務端建立連接后,可以發送請求和接收應答,然后迅速地發送另一個請求和接收另一個應答。
持久連接也使得在得到上一個請求的應答之前能夠發送多個請求,這就是HTTP/1.1與HTTP/1.0的明顯不同之處,除此之外,HTTP/1.1可以發送的請求類型也比HTTP/1.0多。
目前市面上的Web服務器軟件和瀏覽器軟件基本都是支持HTTP/1.1版本的,目前使用的基本上都是HTTP/1.1版本
HTTP是無狀態的
無狀態指:客戶端發送一次請求后,服務端并沒有存儲關于該客戶端的任何狀態信息,即使客戶端再次請求同一個對象,服務端仍會重新發送這個對象,不會在意之前是否已經向客戶端發送過這個對象
說人話:HTTP通信就是客戶端要什么來什么,想要多少來多少,服務端不會因為你要過了而不給你,不會記錄你要過的狀態
HTTP使用元信息作為標頭
HTTP通過添加標頭(header)的方式向服務端提供本次HTTP請求的相關信息,即在主要數據前添加一部分額外信息,稱為元信息(metainformation)元信息里主要包含:傳送的對象屬于哪種類型,采用的是哪種編碼等等
說人話:HTTP的元信息標頭,類似我們講解Socket通信時用于區分消息類型、處理分包黏包時,在消息體前方加的自定義信息。在HTTP協議中,它也定義了類似的規則,在頭部包含了一些額外信息
HTTP協議的請求類型和響應狀態碼
請求類型:
HTTP/1.0中:
GET、POST、HEAD
HTTP/1.1中:
GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT
響應狀態碼:
1xx、2xx、3xx、4xx、5xx
| GET | 請求獲取特定的資源,比如請求一個Web頁面或請求獲取一個資源 |
| POST | 請求提交數據進行處理,比如請求上傳一個文件 |
| HEAD | 請求獲取和GET一致的內容,但是不會返回具體內容,只會返回消息頭 |
| PUT | 向指定位置上傳最新內容 |
| DELETE | 刪除指定資源 |
| OPTIONS | 返回服務器針對特定資源支持的HTTP請求方法 |
| TRACE | 回顯服務端收到的請求 |
| CONNECT | 預留給能夠將連接改為管道方式的代理服務器 |
每一種請求方法,其實就是在HTTP請求的頭部信息包含的內容不同而已內容發送的格式為:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tfmseldm-1655036079005)(/Users/liuyingbo/Library/Application Support/typora-user-images/image-20220521220007663.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-d7ro56IB-1655036079005)(/Users/liuyingbo/Library/Application Support/typora-user-images/image-20220521215730598.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fEikb88M-1655036079005)(/Users/liuyingbo/Library/Application Support/typora-user-images/image-20220521215738537.png)]
客戶端向服務端發送請求后,服務端會返回HTTP響應
HTTP響應的一般格式為:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Te9a9AhZ-1655036079006)(/Users/liuyingbo/Library/Application Support/typora-user-images/image-20220521215825380.png)]
狀態行中主要內容有:
\1. HTTP版本號
\2. 3位數字組成的狀態碼
1xx消息:請求已被服務端接收,繼續處理
2xx成功:請求已成功被服務端理解并接收
3xx重定向:需要后續操作才能完成這一請求
4xx請求錯誤:請求含有語法錯誤或者無法被執行
5xx服務器錯誤:服務端在處理某個正確請求時發生錯誤
| 200 | OK | 找到資源,一切正常 |
| 304 | NOT MODIFIED | 資源在上次請求后沒有任何修改(常用語緩存機制) |
| 401 | UNAUTHORIZED | 客戶端無權訪問該資源,通常需要輸入用戶名和密碼 |
| 403 | FORBIDDEN | 客戶端未授權,通常是401后輸入了錯誤用戶名密碼 |
| 404 | NOT FOUND | 指定位置不存在申請的資源 |
| 405 | Method Not Allowed | 不支持請求的方法 |
| 501 | Not Implemented | 服務器不能識別請求或者沒有實現指定的請求 |
搭建HTTP服務器
www.baidu.com
C#中HTTP類
#region HttpWebRequest類 //命名空間:System.Net //HttpWebRequest是主要用于發送客戶端請求的類 //主要用于:發送HTTP客戶端請求給服務器,可以進行消息通信、上傳、下載等等操作//重要方法 //1.Create 創建新的WebRequest,用于進行HTTP相關操作 HttpWebRequest req = HttpWebRequest.Create(new Uri("http://192.168.50.109:8000/Http_Server/")) as HttpWebRequest; //2.Abort 如果正在進行文件傳輸,用此方法可以終止傳輸 req.Abort(); //3.GetRequestStream 獲取用于上傳的流 Stream s = req.GetRequestStream(); //4.GetResponse 返回HTTP服務器響應 HttpWebResponse res = req.GetResponse() as HttpWebResponse; //5.Begin/EndGetRequestStream 異步獲取用于上傳的流 //req.BeginGetRequestStream() //6.Begin/EndGetResponse 異步獲取返回的HTTP服務器響應 //req.BeginGetResponse()//重要成員 //1.Credentials 通信憑證,設置為NetworkCredential對象 req.Credentials = new NetworkCredential("", ""); //2.PreAuthenticate 是否隨請求發送一個身份驗證標頭,一般需要進行身份驗證時需要將其設置為true req.PreAuthenticate = true;//3.Headers 構成標頭的名稱/值對的集合 //req.Headers //4.ContentLength 發送信息的字節數 上傳信息時需要先設置該內容長度 req.ContentLength = 100; //5.ContentType 在進行POST請求時,需要對發送的內容進行內容類型的設置 //6.Method 操作命令設置 // WebRequestMethods.Http類中的操作命令屬性 // Get 獲取請求,一般用于獲取數據 // Post 提交請求,一般用于上傳數據,同時可以獲取 // Head 獲取和Get一致的內容,只是只會返回消息頭,不會返回具體內容 // Put 向指定位置上傳最新內容 // Connect 表示與代理一起使用的 HTTP CONNECT 協議方法,該代理可以動態切換到隧道 // MkCol 請求在請求 URI(統一資源標識符)指定的位置新建集合//了解該類的更多信息 //https://docs.microsoft.com/zh-cn/dotnet/api/system.net.httpwebrequest?view=net-6.0 #endregion#region HttpWebResponse類 //命名空間:System.Net //它主要用于獲取服務器反饋信息的類 //我們可以通過HttpWebRequest對象中的GetResponse()方法獲取 //當使用完畢時,要使用Close釋放//重要方法: //1.Close:釋放所有資源 //2.GetResponseStream:返回從FTP服務器下載數據的流//重要成員: //1.ContentLength:接受到數據的長度 //2.ContentType:接受數據的類型 //3.StatusCode:HTTP服務器下發的最新狀態碼 //4.StatusDescription:HTTP服務器下發的狀態代碼的文本 //5.BannerMessage:登錄前建立連接時HTTP服務器發送的消息 //6.ExitMessage:HTTP會話結束時服務器發送的消息 //7.LastModified:HTTP服務器上的文件的上次修改日期和時間//了解該類的更多信息 //https://docs.microsoft.com/zh-cn/dotnet/api/system.net.httpwebresponse?view=net-6.0 #endregion#region NetworkCredential、Uri、Stream、FileStream類 //這些類我們在學習Ftp時已經使用過了 //在HTTP通訊時使用方式不變 #endregion#region 總結 //Http相關通訊類的使用和Ftp非常類似 //只有一些細節上的區別 #endregionGet和Post的區別
Get — 一般從指定的資源請求數據,主要用于獲取數據
Post — 一般向指定的資源提交想要被處理的數據,主要用于上傳數據
相同點:
Get和Post都可以傳遞一些額外的參數數據給服務端
不同點:
1.在傳遞參數時,Post相對Get更加的安全,因為Post看不到參數
Get傳遞的參數都包含在連接中(URL資源定位地址),是暴露式的 ?參數名=參數值&參數名=參數值
Post傳遞的參數放在請求數據中,不會出現在URL中,是隱藏式的
2.Get在傳遞數據時有大小的限制,因為它主要是在連接中拼接參數,而URL的長度是有限制的(最大長度一般為2048個字符)
Post在傳遞數據時沒有限制
3.在瀏覽器中Get請求能被緩存,Post不能緩存
4.傳輸次數可能不同
Get: 建立連接——>請求行、請求頭、請求數據一次傳輸——>獲取響應——>斷開連接
Post: 建立連接——>傳輸可能分兩次——>請求行,請求頭第一次傳輸——>請求數據第二次傳輸——>獲取響應——>斷開
但是由于他們的這些特點
我們在實際使用時建議Get用于獲取,Post用于上傳
如果想要傳遞一些不想暴露在外部的參數信息,建議使用Post,它更加的安全
Post如何攜帶額外參數
#region Post如何攜帶額外參數 //關鍵點:將Content-Type設置為 application/x-www-form-urlencoded 鍵值對類型 HttpWebRequest req = HttpWebRequest.Create("http://192.168.50.109:8000/Http_Server/") as HttpWebRequest; req.Method = WebRequestMethods.Http.Post; req.Timeout = 2000; //設置上傳的內容的類型 req.ContentType = "application/x-www-form-urlencoded";//我們要上傳的數據 string str = "Name=liuyingbo&ID=2"; byte[] bytes = Encoding.UTF8.GetBytes(str); //我們在上傳之前一定要設置內容的長度 req.ContentLength = bytes.Length; //上傳數據 Stream stream = req.GetRequestStream(); stream.Write(bytes, 0, bytes.Length); stream.Close(); //發送數據 得到響應結果 HttpWebResponse res = req.GetResponse() as HttpWebResponse; print(res.StatusCode);#endregionContentType類型
#region ContentType的常用類型 //ContentType的構成: //內容類型;charset=編碼格式;boundary=邊界字符串 //text/html;charset=utf-8;boundary=自定義字符串//其中內容類型有: //文本類型text: //text/plain 沒有特定子類型就是它(重要) //text/html //text/css //text/javascript//圖片類型image: //image/gif //image/png //image/jpeg //image/bm //image/webp //image/x-icon //image/vnd.microsoft.icon//音頻類型audio: //audio/midi //audio/mpeg //audio/webm //audio/ogg //audio/wav//視頻類型video: //video/webm //video/ogg//二進制類型application: //application/octet-stream 沒有特定子類型就是它(重要) //application/x-www-form-urlencoded 傳遞參數時使用鍵值對形式(重要) //application/pkcs12 //application/xhtml+xml //application/xml //application/pdf //application/vnd.mspowerpoint//復合內容multipart: //multipart/form-data 復合內容,有多種內容組合(重要) //multipart/byteranges 特殊的復合文件//關于ContentType更多內容可以前往 //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type //關于媒體類型可以前往 //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types #endregion#region ContentType中對于我們來說重要的類型 //1.通用2進制類型 //application/octet-stream //2.通用文本類型 //text/plain //3.鍵值對參數 //application/x-www-form-urlencoded //4.復合類型(傳遞的信息有多種類型組成,比如有鍵值對參數,有文件信息等等,上傳資源服務器時需要用該類型) //multipart/form-data #endregionUnity中的HTTP類
WWW類
#region WWW類的作用 //WWW是Unity提供給我們簡單的訪問網頁的類 //我們可以通過該類下載和上傳一些數據 //在使用http協議時,默認的請求類型是Get,如果想要Post上傳,需要配合下節課學習的WWWFrom類使用 //它主要支持的協議 //1.http://和https:// 超文本傳輸協議 //2.ftp:// 文件傳輸協議(但僅限于匿名下載) //3.file:// 本地文件傳輸協議,可以使用該協議異步加載本地文件(PC、IOS、Android都支持) //我們本節課主要學習利用WWW來進行數據的下載或加載//注意: //1.該類一般配合協同程序使用 //2.該類在較新Unity版本中會提示過時,但是仍可以使用,新版本將其功能整合進了UnityWebRequest類(之后講解) #endregion#region WWW類的常用方法和變量 #region 常用方法 //1.WWW:構造函數,用于創建一個WWW請求 WWW www = new WWW("http://192.168.50.109:8000/Http_Server/test.jpg"); //2.GetAudioClip:從下載數據返回一個音效切片AudioClip對象 //www.GetAudioClip() //3.LoadImageIntoTexture:用下載數據中的圖像來替換現有的一個Texture2D對象 //Texture2D tex = new Texture2D(100, 100); //www.LoadImageIntoTexture(tex); //4.LoadFromCacheOrDownload:從緩存加載AB包對象,如果該包不在緩存則自動下載存儲到緩存中,以便以后直接從本地緩存中加載 //WWW.LoadFromCacheOrDownload("http://192.168.50.109:8000/Http_Server/test.assetbundle", 1); #endregion#region 常用變量 //1.assetBundle:如果加載的數據是AB包,可以通過該變量直接獲取加載結果 //www.assetBundle //2.audioClip:如果加載的數據是音效切片文件,可以通過該變量直接獲取加載結果 //www.GetAudioClip //3.bytes:以字節數組的形式獲取加載到的內容 //www.bytes //4.bytesDownloaded:過去已下載的字節數 //www.bytesDownloaded //5.error:返回一個錯誤消息,如果下載期間出現錯誤,可以通過它獲取錯誤信息 //www.error != null //6.isDone:判斷下載是否已經完成 //www.isDone //7.movie:如果下載的視頻,可以獲取一個MovieTexture類型結果 //www.GetMovieTexture() //8.progress:下載進度 //www.progress //9.text:如果下載的數據是字符串,以字符串的形式返回內容 //www.text //10.texture:如果下載的數據是圖片,以Texture2D的形式返回加載結果 //www.texture #endregion #endregion#region 利用WWW類來異步下載或加載文件 #region 1.下載HTTP服務器上的內容 StartCoroutine(DownLoadHttp()); #endregion#region 2.下載FTP服務器上的內容(FTP服務器一定要支持匿名賬戶) StartCoroutine(DownLoadFtp()); #endregion#region 3.本地內容加載(一般移動平臺加載數據都會使用該方式) StartCoroutine(DownLoadLocal()); #endregion #endregion#region 總結 //Unity中的WWW類比使用C#中的Http相關類更加的方便 //建議大家使用Unity當中為我們封裝好的類來處理下載、加載相關邏輯 #endregionIEnumerator DownLoadHttp() {//1.創建WWW對象WWW www = new WWW("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi2.hdslb.com%2Fbfs%2Farchive%2F8cc2b9a7868b266800f98d42fc5d257021e75103.jpg&refer=http%3A%2F%2Fi2.hdslb.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1654745686&t=b7691b6e546367610e4331039d1a10ec");//2.就是等待加載結束while (!www.isDone){print(www.bytesDownloaded);print(www.progress);yield return null;}print(www.bytesDownloaded);print(www.progress);//3.使用加載結束后的資源if (www.error == null){image.texture = www.texture;}elseprint(www.error); }IEnumerator DownLoadFtp() {//1.創建WWW對象WWW www = new WWW("ftp://127.0.0.1/test.jpg");//2.就是等待加載結束while (!www.isDone){print(www.bytesDownloaded);print(www.progress);yield return null;}print(www.bytesDownloaded);print(www.progress);//3.使用加載結束后的資源if (www.error == null){image.texture = www.texture;}elseprint(www.error); }IEnumerator DownLoadLocal() {//1.創建WWW對象WWW www = new WWW("file://" + Application.streamingAssetsPath + "/test.png");//2.就是等待加載結束while (!www.isDone){print(www.bytesDownloaded);print(www.progress);yield return null;}print(www.bytesDownloaded);print(www.progress);//3.使用加載結束后的資源if (www.error == null){image.texture = www.texture;}elseprint(www.error); }WWWForm類
#region WWWFrom類的作用 //上節課學習了使用WWW類來下載數據 //如果想要使用WWW上傳數據時,就需要配合WWWFrom類進行使用了 //而WWWFrom主要就是用于集成數據的,我們可以設置上傳的參數或者2進制數據 //當結合WWWFrom上傳數據時 //它主要用到的請求類型是Post //它使用Http協議進行上傳處理//注意: //使用WWW結合WWWFrom上傳數據一般需要配合后端程序制定上傳規則 #endregion#region WWWFrom類的常用方法和變量 //該類當中我們主要就使用方法,相關變量很少使用,我們主要就著重講解方法 //1.WWWForm:構造函數 WWWForm data = new WWWForm(); //2.AddBinaryData:添加二進制數據 //data.AddBinaryData() //3.AddField:添加字段 //data.AddField() #endregion#region WWW結合WWWFrom對象來異步上傳數據 StartCoroutine(UpLoadData()); #endregion#region 總結 //WWW結合WWWFrom上傳數據 //需要配合后端服務器來指定上傳規則 //也就是說我們上傳的數據,后端需要知道收到數據后應該如何處理 //通過這種方式我們沒辦法像C#類當中完成文件的上傳 //但是該方式非常適合用于制作短連接游戲的前端網絡層 //我們可以對WWW進行二次封裝,專門用于上傳自定義消息給對應的Web服務器 #endregionIEnumerator UpLoadData(){WWWForm data = new WWWForm();//上傳的數據 對應的后端程序 必須要有處理的規則 才能生效data.AddField("Name", "liuyingbo", Encoding.UTF8);data.AddField("Age", 99);data.AddBinaryData("file", File.ReadAllBytes(Application.streamingAssetsPath + "/test.png"), "testtest.png", "application/octet-stream");WWW www = new WWW("http://192.168.50.109:8000/Http_Server/", data);yield return www;if (www.error == null){print("上傳成功");//www.bytes}elseprint("上傳失敗" + www.error);}UnityWebRequest
#region UnityWebRequest是什么? //UnityWebRequest是一個Unity提供的一個模塊化的系統類 //用于構成HTTP請求和處理HTTP響應 //它主要目標是讓Unity游戲和Web服務端進行交互 //它將之前WWW的相關功能都集成在了其中 //所以新版本中都建議使用UnityWebRequest類來代替WWW類//它在使用上和WWW很類似 //主要的區別就是UnityWebRequest把下載下來的數據處理單獨提取出來了 //我們可以根據自己的需求選擇對應的數據處理對象來獲取數據//注意: //1.UnityWebRequest和WWW一樣,需要配合協同程序使用 //2.UnityWebRequest和WWW一樣,支持http、ftp、file協議下載或加載資源 //3.UnityWebRequest能夠上傳文件到HTTP資源服務器 #endregion#region UnityWebRequest類的常用操作 //1.使用Get請求獲取文本或二進制數據 //2.使用Get請求獲取紋理數據 //3.使用Get請求獲取AB包數據 //4.使用Post請求發送數據 //5.使用Put請求上傳數據 #endregionIEnumerator 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);} }#region 總結 //UnityWebRequest使用上和WWW類很類似 //我們需要注意的是 //1.獲取文本或二進制數據時 // 使用UnityWebRequest.Get //2.獲取紋理圖片數據時 // 使用UnityWebRequestTexture.GetTexture // 以及DownloadHandlerTexture.GetContent //3.獲取AB包數據時 // 使用UnityWebRequestAssetBundle.GetAssetBundle // 以及DownloadHandlerAssetBundle.GetContent #endregion#region 上傳相關數據類 //父接口 //IMultipartFormSection //數據相關類都繼承該接口 //我們可以用父類裝子類 List<IMultipartFormSection> dataList = new List<IMultipartFormSection>();//子類數據 //MultipartFormDataSection //1.二進制字節數組 dataList.Add(new MultipartFormDataSection(Encoding.UTF8.GetBytes("123123123123123"))); //2.字符串 dataList.Add(new MultipartFormDataSection("12312312312312312dsfasdf")); //3.參數名,參數值(字節數組,字符串),編碼類型,資源類型(常用) dataList.Add(new MultipartFormDataSection("Name", "liuyingbo", Encoding.UTF8, "application/....")); dataList.Add(new MultipartFormDataSection("Msg", new byte[1024], "appl....."));//MultipartFormFileSection //1.字節數組 dataList.Add(new MultipartFormFileSection(File.ReadAllBytes(Application.streamingAssetsPath + "/test.png")));//2.文件名,字節數組(常用) dataList.Add(new MultipartFormFileSection("上傳的文件.png", File.ReadAllBytes(Application.streamingAssetsPath + "/test.png"))); //3.字符串數據,文件名(常用) dataList.Add(new MultipartFormFileSection("12312313212312", "test.txt")); //4.字符串數據,編碼格式,文件名(常用) dataList.Add(new MultipartFormFileSection("12312313212312", Encoding.UTF8, "test.txt"));//5.表單名,字節數組,文件名,文件類型 dataList.Add(new MultipartFormFileSection("file", new byte[1024], "test.txt", "")); //6.表單名,字符串數據,編碼格式,文件名 dataList.Add(new MultipartFormFileSection("file", "123123123", Encoding.UTF8, "test.txt")); #endregion#region 知識點二 Post發送相關 StartCoroutine(Upload()); #endregion#region 知識點三 Put上傳相關 //注意:Put請求類型不是所有的web服務器都認,必須要服務器處理該請求類型那么才能有相應 #endregion#region 總結 //我們可以利用Post上傳數據或上傳文件 //Put主要用于上傳文件,但是必須資源服務器支持Put請求類型 //為了通用性,我們可以統一使用Post請求類型進行數據和資源的上傳 //它的使用和之前的WWW類似,只要前后端制定好規則就可以相互通信了 #endregionIEnumerator Upload() {//準備上傳的數據 List<IMultipartFormSection> data = new List<IMultipartFormSection>();//鍵值對相關的 信息 字段數據data.Add(new MultipartFormDataSection("Name", "liuyingbo"));//PlayerMsg msg = new PlayerMsg();//data.Add(new MultipartFormDataSection("Msg", msg.Writing()));//添加一些文件上傳文件//傳2進制文件data.Add(new MultipartFormFileSection("TestTest123.png", File.ReadAllBytes(Application.streamingAssetsPath + "/test.png")));//傳文本文件data.Add(new MultipartFormFileSection("123123123123123", "Test123.txt"));UnityWebRequest req = UnityWebRequest.Post("http://192.168.50.109:8000/Http_Server/", data);req.SendWebRequest();while (!req.isDone){print(req.uploadProgress);print(req.uploadedBytes);yield return null;}print(req.uploadProgress);print(req.uploadedBytes);if (req.result == UnityWebRequest.Result.Success){print("上傳成功");//req.downloadHandler.data}elseprint("上傳失敗" + req.error + req.responseCode + req.result); }#region 高級操作指什么? //在常用操作中我們使用的是Unity為我們封裝好的一些方法 //我們可以方便的進行一些指定類型的數據獲取//比如 //下載數據時: //1.文本和2進制 //2.圖片 //3.AB包 //如果我們想要獲取其它類型的數據應該如何處理呢?//上傳數據時: //1.可以指定參數和值 //2.可以上傳文件 //如果想要上傳一些基于HTTP規則的其它數據應該如何處理呢?//高級操作就是用來處理 常用操作不能完成的需求的 //它的核心思想就是:UnityWebRequest中可以將數據處理分離開 //比如常規操作中我們用到的 //DownloadHandlerTexture 和 DownloadHandlerAssetBundle兩個類 //就是用來將2進制字節數組轉換成對應類型進行處理的//所以高級操作時指 讓你按照規則來實現更多的數據獲取、上傳等功能 #endregion#region UnityWebRequest類的更多內容 //UnityWebRequest req = UnityWebRequest.Get(""); //UnityWebRequest req = UnityWebRequestTexture.GetTexture(""); //UnityWebRequest req = UnityWebRequestAssetBundle.GetAssetBundle(""); //UnityWebRequest req = UnityWebRequest.Put() //UnityWebRequest req = UnityWebRequest.Post//req.isDone //req.downloadProgress; //req.downloadedBytes; //req.uploadProgress; //req.uploadedBytes//req.SendWebRequest()//更多內容 //1.構造函數 //UnityWebRequest req = new UnityWebRequest();//2.請求地址 //req.url = "服務器地址";//3.請求類型 //req.method = UnityWebRequest.kHttpVerbPOST;//4.進度 //req.downloadProgress //req.uploadProgress//5.超時設置 //req.timeout = 2000;//6.上傳、下載的字節數 //req.downloadedBytes //req.uploadedBytes//7.重定向次數 設置為0表示不進行重定向 可以設置次數 //req.redirectLimit = 10;//8.狀態碼、結果、錯誤內容 //req.result //req.error //req.responseCode//9.下載、上傳處理對象 //req.downloadHandler //req.uploadHandler//更多內容 //https://docs.unity.cn/cn/2020.3/ScriptReference/Networking.UnityWebRequest.html #endregion#region 自定義獲取數據DownloadHandler相關類 //關鍵類: //1.DownloadHandlerBuffer 用于簡單的數據存儲,得到對應的2進制數據。 //2.DownloadHandlerFile 用于下載文件并將文件保存到磁盤(內存占用少)。 //3.DownloadHandlerTexture 用于下載圖像。 //4.DownloadHandlerAssetBundle 用于提取 AssetBundle。 //5.DownloadHandlerAudioClip 用于下載音頻文件。StartCoroutine(DownLoadTex());StartCoroutine(DownLoadAB());//以上的這些類,其實就是Unity幫助我們實現好的,用于解析下載下來的數據的類 //使用對應的類處理下載數據,他們就會在內部將下載的數據處理為對應的類型,方便我們使用//DownloadHandlerScript 是一個特殊類。就其本身而言,不會執行任何操作。 //但是,此類可由用戶定義的類繼承。此類接收來自 UnityWebRequest 系統的回調, //然后可以使用這些回調在數據從網絡到達時執行完全自定義的數據處理。StartCoroutine(DownLoadCustomHandler()); #endregion#region 總結 //我們可以自己設置UnityWebRequest當中的下載處理對象 //當設置后,下載數據后它會使用該對象中對應的函數處理數據 //讓我們更方便的獲取我們想要的數據 //方便我們對數據下載或獲取進行拓展 #endregion總結
以上是生活随笔為你收集整理的Unity-网络开发(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php中文日期转成date类型,php怎
- 下一篇: 经典RPG游戏的七个次重要要素