一套完整的unity的socket網路通訊模組
SocketHelper類
主要的通訊類,socket的管理放在這裡
下面說一下一些主要的方法
1.連線伺服器,這個都寫了比較詳細的註釋,一看就會明白
2.傳送資料/// <summary> /// 連線伺服器 /// </summary> /// <returns>The connect.</returns> /// <param name="serverIp">Server ip.</param> /// <param name="serverPort">Server port.</param> /// <param name="connectCallback">Connect callback.</param> /// <param name="connectFailedCallback">Connect failed callback.</param> public void Connect(string serverIp,int serverPort,ConnectCallback connectCallback,ConnectCallback connectFailedCallback){ connectDelegate = connectCallback; connectFailedDelegate = connectFailedCallback; //採用TCP方式連線 socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //伺服器IP地址 IPAddress address = IPAddress.Parse(serverIp); //伺服器埠 IPEndPoint endpoint = new IPEndPoint(address,serverPort); //非同步連線,連線成功呼叫connectCallback方法 IAsyncResult result = socket.BeginConnect(endpoint, new AsyncCallback(ConnectedCallback), socket); //這裡做一個超時的監測,當連線超過5秒還沒成功表示超時 bool success = result.AsyncWaitHandle.WaitOne(5000, true); if (!success) { //超時 Closed(); if(connectFailedDelegate != null){ connectFailedDelegate(); } } else { //與socket建立連線成功,開啟執行緒接受服務端資料。 isStopReceive = false; Thread thread = new Thread(new ThreadStart(ReceiveSorket)); thread.IsBackground = true; thread.Start(); } }
首先從傳送佇列中取出要傳送的資料,然後對資料進行序列化,轉為2進位制資料,然後在資料最前端加上資料的長度,以便於伺服器進行粘包的處理。
private void Send(){ if(socket == null){ return; } if (!socket.Connected) { Closed(); return; } try { Request req = sendDataQueue.Dequeue(); DataStream bufferWriter = new DataStream(true); req.Serialize(bufferWriter); byte[] msg = bufferWriter.ToByteArray(); byte[] buffer = new byte[msg.Length + 4]; DataStream writer = new DataStream(buffer, true); writer.WriteInt32((uint)msg.Length);//增加資料長度 writer.WriteRaw(msg); byte[] data = writer.ToByteArray(); IAsyncResult asyncSend = socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket); bool success = asyncSend.AsyncWaitHandle.WaitOne(5000, true); if (!success) { Closed(); } } catch (Exception e) { Debug.Log("send error : " + e.ToString()); } }
3.接收資料
接收資料是一個獨立的執行緒,連線伺服器成功後開啟的。如果接收到資料,會把資料放進DataHolder進行處理,然後根據DataHolder判斷是否接收到完整資料,以解決粘包等問題。
這裡由於接收到資料時並沒有在主執行緒中,所以我們先把資料放入接收佇列,在主執行緒的update方法中,對資料進行解析以及分發工作。
這裡處理接收和傳送佇列的資料private void ReceiveSorket(){ mDataHolder.Reset(); while(!isStopReceive){ if (!socket.Connected) { //與伺服器斷開連線跳出迴圈 Debug.Log("Failed to clientSocket server."); socket.Close(); break; } try { //接受資料儲存至bytes當中 byte[] bytes = new byte[4096]; //Receive方法中會一直等待服務端回發訊息 //如果沒有回發會一直在這裡等著。 int i = socket.Receive(bytes); if (i <= 0) { socket.Close(); break; } mDataHolder.PushData(bytes, i); while(mDataHolder.IsFinished()){ dataQueue.Enqueue(mDataHolder.mRecvData); mDataHolder.RemoveFromHead(); } } catch (Exception e) { Debug.Log("Failed to clientSocket error." + e); socket.Close(); break; } } }
//接收到資料放入資料佇列,按順序取出
void Update(){
if(dataQueue.Count > 0){
Resp resp = ProtoManager.Instance.TryDeserialize(dataQueue.Dequeue());
}
if(sendDataQueue.Count > 0){
Send();
}
}
DataHolder
此類用來管理接收到的資料,並處理粘包和丟包的情況,主要就是根據資料長度對資料進行裁切。
通過此方法儲存收到的伺服器資料,放入資料快取中
public void PushData(byte[] data, int length)
{
if (mRecvDataCache == null)
mRecvDataCache = new byte[length];
if (this.Count + length > this.Capacity)//current capacity is not enough, enlarge the cache
{
byte[] newArr = new byte[this.Count + length];
mRecvDataCache.CopyTo(newArr, 0);
mRecvDataCache = newArr;
}
Array.Copy(data, 0, mRecvDataCache, mTail + 1, length);
mTail += length;
}
此方法判斷接收到的資料是否完整,這裡需要伺服器在資料頭4位裝入資料長度,如果接收到完整資料,則將資料複製出一份供外部獲取。
public bool IsFinished()
{
if (this.Count == 0)
{
//skip if no data is currently in the cache
return false;
}
if (this.Count >= 4)
{
DataStream reader = new DataStream(mRecvDataCache, true);
packLen = (int)reader.ReadInt32();
if (packLen > 0)
{
if (this.Count - 4 >= packLen)
{
mRecvData = new byte[packLen];
Array.Copy(mRecvDataCache, 4, mRecvData, 0, packLen);
return true;
}
return false;
}
return false;
}
return false;
}
如果接收到了完整的資料,則呼叫此方法將完整資料從資料快取移除
public void RemoveFromHead()
{
int countToRemove = packLen + 4;
if (countToRemove > 0 && this.Count - countToRemove > 0)
{
Array.Copy(mRecvDataCache, countToRemove, mRecvDataCache, 0, this.Count - countToRemove);
}
mTail -= countToRemove;
}
ProtoManager
用來管理所有協議,進行訊息的解析以及分發。
所有Resp需要在這裡註冊,以便接收到資料時進行解析
public void AddProtocol<T>(int protocol) where T: Resp, new()
{
if (mProtocolMapping.ContainsKey(protocol))
{
mProtocolMapping.Remove(protocol);
}
mProtocolMapping.Add(protocol,
(stream) => {
T data = new T();
data.Deserialize(stream);
return data;
});
}
這裡註冊相應協議的代理,在接收到伺服器資料後,會呼叫這裡註冊過的相應代理/// <summary>
/// 新增代理,在接受到伺服器資料時會下發資料
/// </summary>
/// <param name="protocol">Protocol.</param>
/// <param name="d">D.</param>
public void AddRespDelegate(int protocol,responseDelegate d){
List<responseDelegate> dels ;
if (mDelegateMapping.ContainsKey(protocol))
{
dels = mDelegateMapping[protocol];
for(int i = 0 ; i < dels.Count ; i ++){
if(dels[i] == d){
return;
}
}
}else{
dels = new List<responseDelegate>();
mDelegateMapping.Add(protocol,dels);
}
dels.Add(d);
}
接收到伺服器資料後,伺服器會呼叫此方法對資料進行解析並呼叫相應的代理方法
public Resp TryDeserialize(byte[] buffer) { DataStream stream = new DataStream(buffer, true); int protocol = stream.ReadSInt32(); Resp ret = null; if (mProtocolMapping.ContainsKey(protocol)) { ret = mProtocolMapping[protocol](stream); if(ret != null){ if(mDelegateMapping.ContainsKey(protocol)){ List<responseDelegate> dels = mDelegateMapping[protocol]; for(int i = 0 ; i < dels.Count ; i ++){ dels[i](ret); } } } }else{ Debug.Log("no register protocol : " + protocol +"!please reg to RegisterResp."); } return ret; }
DataStream
此類進行傳送以及接收的資料流的處理,包含了資料大小端的設定以及資料流的讀取和寫入方法。
Request
基礎的請求訊息,所有要傳送的請求訊息繼承此類
子類必須實現協議獲取的方法,返回此請求所對應的協議
public virtual int GetProtocol(){
Debug.LogError("can't get Protocol");
return 0;
}
序列化方法
public virtual void Serialize(DataStream writer)
{
writer.WriteSInt32(GetProtocol());
writer.WriteByte(0);
}
Resp
基礎的返回訊息,所有伺服器返回的訊息繼承此類
子類必須實現協議獲取的方法,返回此請求所對應的協議
public virtual int GetProtocol(){
Debug.LogError("can't get Protocol");
return 0;
}
反序列化方法
public virtual void Deserialize(DataStream reader)
{
}
主要內容就這些,有哪些不明白的地方可以留言,有哪些不足或者可以改進的地方還請多多指教
之後我會更新一片實際應用的文章,以及伺服器端的相關內容。
相關推薦
如何快速搭建一套完整的網路直播平臺
網路直播在近兩年異常火熱,比如我們所熟知的秀場直播平臺(9158、6間房、YY);遊戲直播平臺(虎牙直播、龍珠直播、熊貓TV);移動直播平臺(映客);線上直播購物平臺(菠蘿蜜);線上教育平臺(百度傳課、淘寶同學)。 由於朋友公司的老總拿到了一大筆投資,準備搭建自己的旅遊直播
一套完整的unity的socket網路通訊模組
SocketHelper類 主要的通訊類,socket的管理放在這裡 下面說一下一些主要的方法 1.連線伺服器,這個都寫了比較詳細的註釋,一看就會明白 /// <summary> /// 連線伺服器 /// </summary
基於spring boot和mongodb打造一套完整的許可權架構(五)【整合使用者模組、選單模組、角色模組】
在第四章我們已經實現了對security的整合,我們已經實現了登陸到我們的系統中了,但是大家會發現我們登陸成功以後並沒有顯示左側的選單節點,本章我們將開始整合使用者模組、選單模組以及角色模組。 1、首先我們需要在sys的entity目錄底下建立Tree、
如何制作一套完整的VR全景房源?
酷雷曼 VR現在已經開始應用於各行各業之中,尤其是在遊戲和影視行業,然而,VR在房產行業的應用也是突飛猛進的,VR全景房源對於房源的展現也是整體提升了一個更高的檔次,當您戴上了VR眼鏡,更有身臨其境之感。 那麽,我們要如何制作一套完整的VR全景房源呢?今天酷雷曼小編在這裏帶大家看一下全景房源是
基於springboot+bootstrap+mysql+redis搭建一套完整的許可權架構【八】【完善整個專案】
上一章我們已經完成了選單模組的開發工作,那麼到了本章我們將完成我們角色管理模組的開發工作,在本章開始一個全新的模組進行開發的時候我們需要遵守一定的命名和開發規範如下: 1、我們的Controller的RequestMapping的命名要和我們的資料夾的命名一致,且增加的頁面要
送給大家一套完整的web前端開發學習路線
本文來源:千鋒web前端開發 近幾年IT業可謂是發展火熱,而且新生了很多的職業。在這眾多的新生職業中備受矚目的當屬web前端工程師了,web前端在IT行業真正受到重視的時間不超過五年,但是web前端的發展前景卻是非常的可觀,好前景高薪資吸引了眾多的人投入到前端開發的學習當中去,想學好web前端,
0基礎大資料程式設計怎麼學?三個步驟+加一套完整學習體系 教你入門
對於零基礎的朋友,一開始入門可能不會太簡單。大資料零基礎怎麼學?大資料零基礎學什麼?難嗎?要學習大資料你至少應該知道什麼是大資料,大資料一般運用在什麼領域。對大資料有一個大概的瞭解,你才能清楚自己對大資料究竟是否有興趣 你知道什麼是大資料嗎? 要學習大資料你首先應該知道什麼是大資料,大資料一般運用在什麼領
0基礎大數據編程怎麽學?三個步驟+加一套完整學習體系 教你入門
重載 適合 process 你會 prot 分析 國內 初學者 mark 對於零基礎的朋友,一開始入門可能不會太簡單。大數據零基礎怎麽學?大數據零基礎學什麽?難嗎?要學習大數據你至少應該知道什麽是大數據,大數據一般運用在什麽領域。對大數據有一個大概的了解,你才能清楚自己對大
一套完整的區塊鏈學習資源整理
入門篇 適合人群: 搞不懂什麼是區塊鏈的同學 希望向區塊鏈轉型的開發者 區塊鏈知識探索者 區塊鏈入門-零基礎搞懂區塊鏈視訊課程 進階篇 適合人群: 實戰篇 適合人群: 適用於想學習區塊鏈但無基礎的同學,區塊鏈程式設計師,金融領域程式設計師,solidity程式
基於springboot+bootstrap+mysql+redis搭建一套完整的許可權架構【五】【編寫基礎程式碼快速生成工具】
首先我們在我們的com.csdn包底下我們新建一個codeUtil包如下所示: 首先我們新建一個我們的列的模型(ColumnModel.java)如下: /** * 列模型 */ public class ColumnModel { private bool
網路程式設計懶人入門(一):快速理解網路通訊協議(上篇)
1、寫在前面 論壇和群裡常會有技術同行打算自已開發IM或者訊息推送系統,很多時候連基本的網路程式設計理論(如網路協議等)都不瞭解,就貿然定方案、寫程式碼,顯得非常盲目且充滿技術風險。即時通訊網論壇裡精心整理了《[通俗易懂]深入理解TCP協議》、《不為人知的網路程式設計》、《
高效能伺服器底層網路通訊模組的設計方法
1、概述 要設計與開發出一款高效能的伺服器(如網遊伺服器、Web伺服器和代理伺服器等),一般都採用高效率的網路I/O模型。Linux平臺上經常會採用epoll模型,而在Win32平臺上完成埠(以下簡稱IOCP)模型是設計與開發高效能的、具有可伸縮性的伺服器的最佳選擇,它可以支援海量併發客戶端請求。多
一套完整自定義工作流的實現
概述: 本工作流以一套金融軟體業務處理流程為例,實現功能包括:流程自定義、步驟自定義、步驟重複次數、步驟型別(順序/並行)、定義排序功能,完全使用資料庫實現,本文將詳細分析業務流程、系統設計及實現細節。 術語: 工作流(Workflow)[1],是對工作流程及其各操作步驟之間業務規則的抽象、概括、描述。工作
開源一套完整手機簡訊平臺程式碼
這套程式碼是07年時想做成共享軟體的,當時還特意寫了註冊碼生成器,但是後來因為不會營銷,所以沒弄起來。最近整理以前的老程式碼,感覺放著沒啥用,開源吧,或許對初學者有用。軟體本身比較完整,包括服務端和客戶端,也用到了挺多技術:Socket服務,AT指令和串列埠通訊,Access資料庫,基於WTL介面
一套完整的ARM交叉編譯環境的搭建過程——不使用現成的工具鏈
Glibc是交叉編譯環境的執行庫。首先將glibc-2.13.tar解壓至build-tools,然後將glibc-linuxthreads-2.3.6.tar解壓至glibc原始碼所在目錄,將glibc-ports-2.13.tar.gz解壓至glibc原始碼所在目錄,並重命名為ports。進入
NetWork——描述一次完整的網路請求過程
0. 前言從我們在瀏覽器的位址列輸入http://blog.csdn.net/seu_calvin後回車,到我們看到該部落格的主頁,這中間經歷了什麼呢?簡單地回答這個問題,大概是經歷了域名解析、TCP
linux常用命令-網路通訊模組
1、tcpdump–見 https://blog.csdn.net/baobingji/article/details/84111793; 2、ifconfig (1)ifconfig 不加任何引數,輸出當前網路介面的資訊。 ifconfig ifconfig -a UP(代
一套原創分散式即時通訊(IM)系統理論架構方案
一 典型的即時通訊架構可能是這樣的 無論是IM訊息通訊系統還是客戶訊息系統,其本質都是一套訊息傳送與投遞系統,或者說是一套網路通訊系統,其本質兩個詞:儲存與轉發。 1攜程非同步訊息系統初期架構 上圖所示顯示了攜程家的訊息系統的初期架構,圖中架構直接用mongodb作
一套完整的設計分析思路
很多互動設計師特別是新手設計師,常常會出現接到一個需求不知道該從何下手的情況。比如一下兩種常見場景。“需求是業務方的幾句話描述,只能管中窺象的瞭解他們一些零零碎碎的想法,沒理解到本質需求就開始畫介面,然後不斷的隨著他們想法的轉產而反覆被動修改互動稿…”“PM已經畫好完整的d
基於SpringBoot開發一套完整的專案(一)準備工作
1.1 SpringBoot簡介① 為所有Spring 開發提供一個更快更廣泛的人門體驗。② 零配置。無冗餘程式碼生成和XML 強制配置,遵循“約定大於配置” 。③ 集成了大量常用的第三方庫的配置, Spring Boot 應用為這些第三方庫提供了幾乎可以零配置的開箱即用的能