來源:wilsonke 發(fā)布時間:2018-11-21 15:40:50 閱讀量:1070
近日根據官方提供的通信例子自己寫了一個關于Unity(C#)和后臺通信的類,拿出來和大家分享一下。
具體請參考:
1.java服務端用的apach.mina框架搭建。java服務端請參考:http://blog.9tech.cn/?c=site&m=article&id=548
2.C#環(huán)境:.NET framework 2.0
3.C#幫組文檔,及Socket注解:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket(v=vs.85).aspx
4.官方例子:http://msdn.microsoft.com/zh-cn/library/bew39x2a(v=VS.85).aspx#CommunityContent
個人覺得,最難的地方在與以下幾個地方:
1.封裝數據,和后臺的編解碼格式保持一致
封裝數據,其實就是一個前后臺約定好一個通信格式。比如:獲得所以數據后并寫入字節(jié)數組后,在吧這個字節(jié)數組的長度讀出來(4個字節(jié)的整形數據),再封裝進一個字節(jié)數組中。
所以最終的數據字節(jié)數組內容是:4個字節(jié)的數據長度+實際數據的字節(jié)數組。
當然數據的加密加壓可以在吧實際數據存入字節(jié)數組后就進行,那么發(fā)送的長度就是加密加壓后的數據長度了。
實現(xiàn)方法:
1 2 3 4 5 6 7 8 9 | String message = "shit " ; //5個字節(jié) byte [] bytes = Encoding.UTF8.GetBytes(message); //未加密加壓的實際數據 byte [] prefixBytes =BitConverter.GetBytes(bytes.Length); //數據長度 Array.Reverse(prefixBytes); byte [] sendBytes = new byte [bytes.Length + 4]; //吧字節(jié)數組合并到sendBytes System.Buffer.BlockCopy(prefixBytes ,0,sendBytes ,0,4); System.Buffer.BlockCopy(bytes,0,sendBytes ,4,bytes.Length); //合并完畢,就可以使用socket吧sendBytes發(fā)送出去了,后臺也是有同樣的格式解碼就可以了。 |
2.異步通信的線程管理
異步通信的線程安全一直是一個難點,它不像ActionScript那樣,建立通信連接后注冊事件用來偵聽即可。但是在C#中就必須讓線程等待當前的異步操作完成后才可以繼續(xù)向下執(zhí)行。C#異步通信中可以使用ManualResetEvent類來處理這類問題。當需要暫停執(zhí)行后續(xù)代碼以完成異步操作時。使用ManualResetEvent的WaitOne();方法來阻塞這個線程(類似與java的ReentrantLock類),但是必須在異步操作完成后使線程恢復,否則就會出現(xiàn)線程被鎖死的情況。使用ManualResetEvent的Set()方法即可。
實現(xiàn)方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private static ManualResetEvent connectDone = new ManualResetEvent( false ); public void startConnect(){ Connect(); connectDone.WaitOne(); //阻塞線程, receive(); } public void Connect(){ // to do connection //socket.BeginConnect.... } public void connectCallback(IAsyncResult ar){ Socket socket = (Socket) ar.AsyncState; socket .EndConnect(ar); connectDone.Set(); //恢復線程,繼續(xù)向下執(zhí)行 } public void receive(){ // to do received message } |
ManualResetEvent類的幫組文檔及例子:http://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent_members(v=vs.85).aspx
3.關于數據的壓縮,和解壓.
對于數據的壓縮和解壓可以采用ICSharpCode.SharpZipLib這個動態(tài)鏈接庫。下載地址:http://www.icsharpcode.net/opensource/sharpziplib/
下載后再MonoDevelop里倒入引用就可以了,位置:Project->Edit References..
using ICSharpCode.SharpZipLib.Zip;
然后在代碼里就可以倒入類了
參考:http://blog.sina.com.cn/s/blog_62fda93c0101d51j.html
4.接收和讀取數據的操作
在接受服務端發(fā)送的數據時,也根據同樣的格式進行解讀;先讀取4個字節(jié)的數據長度,再跟進這個長度得到實際的數據,最后在解密和解壓就可以得到最終的數據了。
但是在這個操作過程中,會出現(xiàn)一些意想不到的麻煩。
大致流程是:
采取分段讀取的方式,第一次只讀取4個字節(jié)的長度信息。
取得長度后,根據設置的每次分段讀取數據長度來讀取,知道所得的數據和總長度相同;
每次分段讀取的數據的長度不一定都是設置的長度,所以將每次讀取的數據寫入內存流MemoryStream類中。特別重要的每次操作MemoryStream類時注意設置它的Position位置,不然會出現(xiàn)你本來已經成功的存入了數據,但是由于Position的原因沒有找準需要取出或者存入數據的準確位置而讀取數據失敗。
詳細代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using System.IO; using System.Threading; using System.Text; using System.Net; using System.Net.Sockets; using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.GZip; using LitJson; /** * 連接對象 * @author duwei * @version 1.0.0 * build time :2013.11.7 * */ public class BufferConnection{ public Socket socket = null ; public const int prefixSize = 4; public String ip = "192.168.1.105" ; public int port = 8005; private static ManualResetEvent connectDone = new ManualResetEvent( false ); private static ManualResetEvent sendDone = new ManualResetEvent( false ); private static ManualResetEvent receiveDone = new ManualResetEvent( false ); public BufferConnection(){ } // State object for receiving data from remote device. public class StateObject { // Client socket. public Socket workSocket = null ; // Size of receive buffer. public const int BufferSize = 1024; // Receive buffer. public byte [] buffer = new byte [BufferSize]; } /**開始建立socket連接*/ public void startConnect(){ try { Debug.Log( "starting connection..." ); IPAddress ipd = IPAddress.Parse(ip); EndPoint endPoint = new IPEndPoint(ipd,port); socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); socket.BeginConnect(endPoint, new AsyncCallback(connectCallback),socket); connectDone.WaitOne(); receive(socket); //receiveDone.WaitOne(); } catch (Exception e){ Console.WriteLine(e.ToString()); } } public void connectCallback(IAsyncResult ar){ try { Socket backSocket = (Socket)ar.AsyncState; backSocket.EndConnect(ar); connectDone.Set(); Debug.Log( "on connected" ); } catch (Exception e){ Console.WriteLine(e.ToString()); } } /**發(fā)送數據,目前只支持 String 類型數據*/ public void send(Socket client,String msg){ //封裝數據 byte [] byteData = Encoding.UTF8.GetBytes(msg); byte [] sendData = new byte [byteData.Length + prefixSize]; byte [] sizeData = BitConverter.GetBytes(byteData.Length); //反轉 Array.Reverse(sizeData); //合并 System.Buffer.BlockCopy(sizeData,0,sendData,0,prefixSize); System.Buffer.BlockCopy(byteData,0,sendData,prefixSize,byteData.Length); try { //socket.Send(sendData); client.BeginSend(sendData,0,sendData.Length,0, new AsyncCallback(sendCallback),client); Debug.Log( "data send finished, data size:" +sendData.Length); } catch (Exception e){ Console.WriteLine(e.ToString()); } } public void send(String msg){ if (socket != null ){ send(socket,msg); sendDone.WaitOne(); } } public void sendCallback(IAsyncResult ar){ try { Socket socket = (Socket)ar.AsyncState; if (ar.IsCompleted){ int endPoint = socket.EndSend(ar); Debug.Log( "send data finished endpoint: " +endPoint); sendDone.Set(); } } catch (Exception e){ Console.WriteLine(e.ToString()); } } public void receive(Socket socket){ try { StateObject so = new StateObject(); so.workSocket = socket; //第一次讀取數據的總長度 socket.BeginReceive(so.buffer,0,prefixSize,0, new AsyncCallback(receivedCallback),so); //測試用:數據在1024以內的數據,一次性讀取出來 //socket.BeginReceive(so.buffer,0,StateObject.BufferSize,0,new AsyncCallback(simpleCallback),so); } catch (Exception e){ Console.WriteLine(e.ToString()); } } public void simpleCallback(IAsyncResult ar){ StateObject so = (StateObject)ar.AsyncState; Socket socket = so.workSocket; byte [] presixBytes = new byte [prefixSize]; int presix = 0; Buffer.BlockCopy(so.buffer,0,presixBytes,0,prefixSize); Array.Reverse(presixBytes); presix = BitConverter.ToInt32(presixBytes,0); if (presix <= 0){ return ; } byte [] datas = new byte [presix]; Buffer.BlockCopy(so.buffer,prefixSize,datas,0,datas.Length); String str = Encoding.UTF8.GetString(datas); Debug.Log( "received message :" +str); } public MemoryStream receiveData = new MemoryStream(); private bool isPresix = true ; public int curPrefix = 0; //需要讀取的數據總長度 public void receivedCallback(IAsyncResult ar){ try { StateObject so = (StateObject)ar.AsyncState; Socket client = so.workSocket; int readSize = client.EndReceive (ar); //結束讀取,返回已讀取的緩沖區(qū)里的字節(jié)數組長度 //將每次讀取的數據,寫入內存流里 receiveData.Write(so.buffer,0,readSize); receiveData.Position = 0; //讀取前置長度,只讀取一次 if (( int )receiveData.Length >= prefixSize && isPresix){ byte [] presixBytes = new byte [prefixSize]; receiveData.Read(presixBytes,0,prefixSize); Array.Reverse(presixBytes); curPrefix = BitConverter.ToInt32(presixBytes,0); isPresix = false ; } if (receiveData.Length - ( long )prefixSize < ( long )curPrefix){ //如果數據沒有讀取完畢,調整Position到最后,接著讀取。 receiveData.Position = receiveData.Length; } else { //如果內存流中的實際數字總長度符合要求,則說明數據已經全部讀取完畢。 //將position位置調整到第4個節(jié)點,開始準備讀取數據。 receiveData.Position = prefixSize; //讀取數據 byte [] datas = new byte [curPrefix]; receiveData.Read(datas,0,datas.Length); //有壓縮的話需要先解壓,然后在操作。 byte [] finallyBytes = decompress(datas); String str = Encoding.UTF8.GetString(finallyBytes); Debug.Log( "the finally message is : " +str); } //重復讀取,每次讀取1024個字節(jié)數據 client.BeginReceive(so.buffer,0,StateObject.BufferSize,0, new AsyncCallback(receivedCallback), so); } catch (Exception e){ Console.WriteLine(e.ToString()); } } private byte [] temp = new byte [1024]; //解壓 public byte [] decompress( byte [] bytes){ MemoryStream memory = new MemoryStream (); ICSharpCode.SharpZipLib.Zip.Compression.Inflater inf = new ICSharpCode.SharpZipLib.Zip.Compression.Inflater (); inf.SetInput (bytes); while (!inf.IsFinished) { int extracted = inf.Inflate (temp); if (extracted > 0) { memory.Write (temp, 0, extracted); } else { break ; } } return memory.ToArray (); } } |