來源:wilsonke 發(fā)布時(shí)間:2018-11-21 15:40:50 閱讀量:1303
近日根據(jù)官方提供的通信例子自己寫了一個(gè)關(guān)于Unity(C#)和后臺(tái)通信的類,拿出來和大家分享一下。
具體請(qǐng)參考:
1.java服務(wù)端用的apach.mina框架搭建。java服務(wù)端請(qǐng)參考: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
個(gè)人覺得,最難的地方在與以下幾個(gè)地方:
1.封裝數(shù)據(jù),和后臺(tái)的編解碼格式保持一致
封裝數(shù)據(jù),其實(shí)就是一個(gè)前后臺(tái)約定好一個(gè)通信格式。比如:獲得所以數(shù)據(jù)后并寫入字節(jié)數(shù)組后,在吧這個(gè)字節(jié)數(shù)組的長(zhǎng)度讀出來(4個(gè)字節(jié)的整形數(shù)據(jù)),再封裝進(jìn)一個(gè)字節(jié)數(shù)組中。
所以最終的數(shù)據(jù)字節(jié)數(shù)組內(nèi)容是:4個(gè)字節(jié)的數(shù)據(jù)長(zhǎng)度+實(shí)際數(shù)據(jù)的字節(jié)數(shù)組。
當(dāng)然數(shù)據(jù)的加密加壓可以在吧實(shí)際數(shù)據(jù)存入字節(jié)數(shù)組后就進(jìn)行,那么發(fā)送的長(zhǎng)度就是加密加壓后的數(shù)據(jù)長(zhǎng)度了。
實(shí)現(xiàn)方法:
1 2 3 4 5 6 7 8 9 | String message = "shit " ; //5個(gè)字節(jié) byte [] bytes = Encoding.UTF8.GetBytes(message); //未加密加壓的實(shí)際數(shù)據(jù) byte [] prefixBytes =BitConverter.GetBytes(bytes.Length); //數(shù)據(jù)長(zhǎng)度 Array.Reverse(prefixBytes); byte [] sendBytes = new byte [bytes.Length + 4]; //吧字節(jié)數(shù)組合并到sendBytes System.Buffer.BlockCopy(prefixBytes ,0,sendBytes ,0,4); System.Buffer.BlockCopy(bytes,0,sendBytes ,4,bytes.Length); //合并完畢,就可以使用socket吧sendBytes發(fā)送出去了,后臺(tái)也是有同樣的格式解碼就可以了。 |
2.異步通信的線程管理
異步通信的線程安全一直是一個(gè)難點(diǎn),它不像ActionScript那樣,建立通信連接后注冊(cè)事件用來偵聽即可。但是在C#中就必須讓線程等待當(dāng)前的異步操作完成后才可以繼續(xù)向下執(zhí)行。C#異步通信中可以使用ManualResetEvent類來處理這類問題。當(dāng)需要暫停執(zhí)行后續(xù)代碼以完成異步操作時(shí)。使用ManualResetEvent的WaitOne();方法來阻塞這個(gè)線程(類似與java的ReentrantLock類),但是必須在異步操作完成后使線程恢復(fù),否則就會(huì)出現(xiàn)線程被鎖死的情況。使用ManualResetEvent的Set()方法即可。
實(shí)現(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(); //恢復(fù)線程,繼續(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.關(guān)于數(shù)據(jù)的壓縮,和解壓.
對(duì)于數(shù)據(jù)的壓縮和解壓可以采用ICSharpCode.SharpZipLib這個(gè)動(dòng)態(tài)鏈接庫(kù)。下載地址:http://www.icsharpcode.net/opensource/sharpziplib/
下載后再M(fèi)onoDevelop里倒入引用就可以了,位置:Project->Edit References..
using ICSharpCode.SharpZipLib.Zip;
然后在代碼里就可以倒入類了
參考:http://blog.sina.com.cn/s/blog_62fda93c0101d51j.html
4.接收和讀取數(shù)據(jù)的操作
在接受服務(wù)端發(fā)送的數(shù)據(jù)時(shí),也根據(jù)同樣的格式進(jìn)行解讀;先讀取4個(gè)字節(jié)的數(shù)據(jù)長(zhǎng)度,再跟進(jìn)這個(gè)長(zhǎng)度得到實(shí)際的數(shù)據(jù),最后在解密和解壓就可以得到最終的數(shù)據(jù)了。
但是在這個(gè)操作過程中,會(huì)出現(xiàn)一些意想不到的麻煩。
大致流程是:
采取分段讀取的方式,第一次只讀取4個(gè)字節(jié)的長(zhǎng)度信息。
取得長(zhǎng)度后,根據(jù)設(shè)置的每次分段讀取數(shù)據(jù)長(zhǎng)度來讀取,知道所得的數(shù)據(jù)和總長(zhǎng)度相同;
每次分段讀取的數(shù)據(jù)的長(zhǎng)度不一定都是設(shè)置的長(zhǎng)度,所以將每次讀取的數(shù)據(jù)寫入內(nèi)存流MemoryStream類中。特別重要的每次操作MemoryStream類時(shí)注意設(shè)置它的Position位置,不然會(huì)出現(xiàn)你本來已經(jīng)成功的存入了數(shù)據(jù),但是由于Position的原因沒有找準(zhǔn)需要取出或者存入數(shù)據(jù)的準(zhǔn)確位置而讀取數(shù)據(jù)失敗。
詳細(xì)代碼如下:
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; /** * 連接對(duì)象 * @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ā)送數(shù)據(jù),目前只支持 String 類型數(shù)據(jù)*/ public void send(Socket client,String msg){ //封裝數(shù)據(jù) byte [] byteData = Encoding.UTF8.GetBytes(msg); byte [] sendData = new byte [byteData.Length + prefixSize]; byte [] sizeData = BitConverter.GetBytes(byteData.Length); //反轉(zhuǎn) 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; //第一次讀取數(shù)據(jù)的總長(zhǎng)度 socket.BeginReceive(so.buffer,0,prefixSize,0, new AsyncCallback(receivedCallback),so); //測(cè)試用:數(shù)據(jù)在1024以內(nèi)的數(shù)據(jù),一次性讀取出來 //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; //需要讀取的數(shù)據(jù)總長(zhǎng)度 public void receivedCallback(IAsyncResult ar){ try { StateObject so = (StateObject)ar.AsyncState; Socket client = so.workSocket; int readSize = client.EndReceive (ar); //結(jié)束讀取,返回已讀取的緩沖區(qū)里的字節(jié)數(shù)組長(zhǎng)度 //將每次讀取的數(shù)據(jù),寫入內(nèi)存流里 receiveData.Write(so.buffer,0,readSize); receiveData.Position = 0; //讀取前置長(zhǎng)度,只讀取一次 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){ //如果數(shù)據(jù)沒有讀取完畢,調(diào)整Position到最后,接著讀取。 receiveData.Position = receiveData.Length; } else { //如果內(nèi)存流中的實(shí)際數(shù)字總長(zhǎng)度符合要求,則說明數(shù)據(jù)已經(jīng)全部讀取完畢。 //將position位置調(diào)整到第4個(gè)節(jié)點(diǎn),開始準(zhǔn)備讀取數(shù)據(jù)。 receiveData.Position = prefixSize; //讀取數(shù)據(jù) 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); } //重復(fù)讀取,每次讀取1024個(gè)字節(jié)數(shù)據(jù) 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 (); } } |
在線
客服
服務(wù)時(shí)間:周一至周日 08:30-18:00
選擇下列產(chǎn)品馬上在線溝通:
客服
熱線
7*24小時(shí)客服服務(wù)熱線
關(guān)注
微信
關(guān)注官方微信