最近比较忙,有段时间没写博客拉。最近项目中需要使用HTTP与Socket,雨松MOMO把自己这段时间学习的资料整理一下。有关Socket与HTTP的基础知识MOMO就不赘述拉,不懂得朋友自己谷歌吧。我们项目的需求是在登录的时候使用HTTP请求,游戏中其它的请求都用Socket请求,比如人物移动同步坐标,同步关卡等等。
1.Socket
Socket不要写在脚本上,如果写在脚本上游戏场景一旦切换,那么这条脚本会被释放掉,Socket会断开连接。场景切换完毕后需要重新在与服务器建立Socket连接,这样会很麻烦。所以我们需要把Socket写在一个单例的类中,不用继承MonoBehaviour。这个例子我模拟一下,主角在游戏中移动,时时向服务端发送当前坐标,当服务器返回同步坐标时角色开始同步服务端新角色坐标。
Socket在发送消息的时候采用的是字节数组,也就是说无论你的数据是 int float short object 都会将这些数据类型先转换成byte[] , 目前在处理发送的地方我使用的是数据包,也就是把(角色坐标)结构体object转换成byte[]发送, 这就牵扯一个问题, 如何把结构体转成字节数组, 如何把字节数组回转成结构体。请大家接续阅读,答案就在后面,哇咔咔。
直接上代码
JFSocket.cs 该单例类不要绑定在任何对象上
002 | using System.Collections; |
004 | using System.Threading; |
007 | using System.Net.Sockets; |
008 | using System.Collections.Generic; |
010 | using System.Runtime.InteropServices; |
011 | using System.Runtime.Serialization; |
012 | using System.Runtime.Serialization.Formatters.Binary; |
014 | public class JFSocket |
018 | privateSocket clientSocket; |
022 | publicList<JFPackage.WorldPackage> worldpackage; |
024 | privatestatic JFSocket instance; |
025 | publicstatic JFSocket GetInstance() |
029 | instance =new JFSocket(); |
038 | clientSocket =new Socket (AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); |
040 | IPAddress ipAddress = IPAddress.Parse ("192.168.1.100"); |
042 | IPEndPoint ipEndpoint =new IPEndPoint (ipAddress, 10060); |
044 | IAsyncResult result = clientSocket.BeginConnect (ipEndpoint,newAsyncCallback (connectCallback),clientSocket); |
046 | boolsuccess = result.AsyncWaitHandle.WaitOne( 5000, true ); |
051 | Debug.Log("connect Time Out"); |
055 | worldpackage =new List<JFPackage.WorldPackage>(); |
056 | Thread thread =new Thread(newThreadStart(ReceiveSorket)); |
057 | thread.IsBackground =true; |
062 | privatevoid connectCallback(IAsyncResult asyncConnect) |
064 | Debug.Log("connectSuccess"); |
067 | privatevoid ReceiveSorket() |
073 | if(!clientSocket.Connected) |
076 | Debug.Log("Failed to clientSocket server."); |
077 | clientSocket.Close(); |
083 | byte[] bytes =new byte[4096]; |
086 | inti = clientSocket.Receive(bytes); |
089 | clientSocket.Close(); |
098 | SplitPackage(bytes,0); |
101 | Debug.Log("length is not > 2"); |
107 | Debug.Log("Failed to clientSocket error."+ e); |
108 | clientSocket.Close(); |
114 | privatevoid SplitPackage(byte[] bytes ,int index) |
121 | byte[] head =new byte[2]; |
122 | intheadLengthIndex = index + 2; |
124 | Array.Copy(bytes,index,head,0,2); |
126 | shortlength = BitConverter.ToInt16(head,0); |
130 | byte[] data =new byte[length]; |
132 | Array.Copy(bytes,headLengthIndex,data,0,length); |
136 | JFPackage.WorldPackage wp =new JFPackage.WorldPackage(); |
137 | wp = (JFPackage.WorldPackage)BytesToStruct(data,wp.GetType()); |
139 | worldpackage.Add(wp); |
141 | index = headLengthIndex + length; |
153 | publicvoid SendMessage(stringstr) |
155 | byte[] msg = Encoding.UTF8.GetBytes(str); |
157 | if(!clientSocket.Connected) |
159 | clientSocket.Close(); |
165 | IAsyncResult asyncSend = clientSocket.BeginSend (msg,0,msg.Length,SocketFlags.None,newAsyncCallback (sendCallback),clientSocket); |
166 | boolsuccess = asyncSend.AsyncWaitHandle.WaitOne( 5000, true ); |
169 | clientSocket.Close(); |
170 | Debug.Log("Failed to SendMessage server."); |
175 | Debug.Log("send message error"); |
180 | publicvoid SendMessage(objectobj) |
183 | if(!clientSocket.Connected) |
185 | clientSocket.Close(); |
191 | shortsize = (short)Marshal.SizeOf(obj); |
193 | byte[] head = BitConverter.GetBytes(size); |
195 | byte[] data = StructToBytes(obj); |
200 | byte[] newByte =new byte[head.Length + data.Length]; |
201 | Array.Copy(head,0,newByte,0,head.Length); |
202 | Array.Copy(data,0,newByte,head.Length, data.Length); |
205 | intlength = Marshal.SizeOf(size) + Marshal.SizeOf(obj); |
208 | IAsyncResult asyncSend = clientSocket.BeginSend (newByte,0,length,SocketFlags.None,newAsyncCallback (sendCallback),clientSocket); |
210 | boolsuccess = asyncSend.AsyncWaitHandle.WaitOne( 5000, true ); |
213 | clientSocket.Close(); |
214 | Debug.Log("Time Out !"); |
220 | Debug.Log("send message error: "+ e ); |
225 | publicbyte[] StructToBytes(objectstructObj) |
228 | intsize = Marshal.SizeOf(structObj); |
229 | IntPtr buffer = Marshal.AllocHGlobal(size); |
232 | Marshal.StructureToPtr(structObj,buffer,false); |
233 | byte[] bytes =new byte[size]; |
234 | Marshal.Copy(buffer, bytes,0,size); |
239 | Marshal.FreeHGlobal(buffer); |
243 | publicobject BytesToStruct(byte[] bytes, Type strcutType) |
245 | intsize = Marshal.SizeOf(strcutType); |
246 | IntPtr buffer = Marshal.AllocHGlobal(size); |
249 | Marshal.Copy(bytes,0,buffer,size); |
250 | returnMarshal.PtrToStructure(buffer, strcutType); |
254 | Marshal.FreeHGlobal(buffer); |
259 | privatevoid sendCallback (IAsyncResult asyncSend) |
268 | if(clientSocket !=null && clientSocket.Connected) |
270 | clientSocket.Shutdown(SocketShutdown.Both); |
271 | clientSocket.Close(); |
为了与服务端达成默契,判断数据包是否完成。我们需要在数据包中定义包头 ,包头一般是这个数据包的长度,也就是结构体对象的长度。正如代码中我们把两个数据类型 short 和 object 合并成一个新的字节数组。
然后是数据包结构体的定义,需要注意如果你在做IOS和Android的话数据包中不要包含数组,不然在结构体转换byte数组的时候会出错。
Marshal.StructureToPtr () error : Attempting to JIT compile method
JFPackage.cs
02 | using System.Collections; |
03 | using System.Runtime.InteropServices; |
05 | public class JFPackage |
10 | [StructLayout(LayoutKind.Sequential, Pack = 4)] |
11 | publicstruct WorldPackage |
14 | publicbyte mAnimationID; |
23 | publicWorldPackage(shortposx,short posy,short posz, short rosx, short rosy, short rosz,byte equipID,byteanimationID,bytehp) |
32 | mAnimationID = animationID; |
在脚本中执行发送数据包的动作,在Start方法中得到Socket对象。
1 | public JFSocket mJFsorket; |
4 | mJFsorket = JFSocket.GetInstance(); |
让角色发生移动的时候,调用该方法向服务端发送数据。
01 | void SendPlayerWorldMessage() |
04 | Vector3 PlayerTransform = transform.localPosition; |
05 | Vector3 PlayerRotation = transform.localRotation.eulerAngles; |
07 | shortpx = (short)(PlayerTransform.x*100); |
08 | shortpy = (short)(PlayerTransform.y*100); |
09 | shortpz = (short)(PlayerTransform.z*100); |
10 | shortrx = (short)(PlayerRotation.x*100); |
11 | shortry = (short)(PlayerRotation.y*100); |
12 | shortrz = (short)(PlayerRotation.z*100); |
16 | JFPackage.WorldPackage wordPackage =new JFPackage.WorldPackage(px,py,pz,rx,ry,rz,equipID,animationID,hp); |
18 | mJFsorket.SendMessage(wordPackage); |
接着就是客户端同步服务器的数据,目前是测试阶段所以写的比较简陋,不过原理都是一样的。哇咔咔!!
02 | privatefloat mSynchronous; |
05 | mSynchronous +=Time.deltaTime; |
07 | if(mSynchronous > 0.5f) |
09 | intcount = mJFsorket.worldpackage.Count; |
14 | foreach(JFPackage.WorldPackage wpin mJFsorket.worldpackage) |
16 | floatx = (float)(wp.mPosx / 100.0f); |
17 | floaty = (float)(wp.mPosy /100.0f); |
18 | floatz = (float)(wp.mPosz /100.0f); |
19 | Debug.Log("x = "+ x + " y = "+ y+" z = " + z); |
21 | mPlayer.transform.position =new Vector3 (x,y,z); |
24 | mJFsorket.worldpackage.Clear(); |
主角移动的同时,通过Socket时时同步坐标喔。。有没有感觉这个牛头人非常帅气 哈哈哈。

对于Socket的使用,我相信没有比MSDN更加详细的了。 有关Socket 同步请求异步请求的地方可以参照MSDN 链接地址给出来了,好好学习吧,嘿嘿。http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.aspx
上述代码中我使用的是Thread() 没有使用协同任务StartCoroutine() ,原因是协同任务必需要继承MonoBehaviour,并且该脚本要绑定在游戏对象身上。问题绑定在游戏对象身上切换场景的时候这个脚本必然会释放,那么Socket肯定会断开连接,所以我需要用Thread,并且协同任务它并不是严格意义上的多线程。
2.HTTP
HTTP请求在Unity我相信用的会更少一些,因为HTTP会比SOCKET慢很多,因为它每次请求完都会断开。废话不说了, 我用HTTP请求制作用户的登录。用HTTP请求直接使用Unity自带的www类就可以,因为HTTP请求只有登录才会有, 所以我就在脚本中来完成, 使用 www 类 和 协同任务StartCoroutine()。
02 | using System.Collections; |
03 | using System.Collections.Generic; |
04 | public class LoginGlobe : MonoBehaviour { |
24 | publicvoid LoginPressed() |
27 | Dictionary<string,string> dic =new Dictionary<string,string> (); |
29 | dic.Add("action","0"); |
30 | dic.Add("usrname","xys"); |
31 | dic.Add("psw","123456"); |
37 | publicvoid SingInPressed() |
40 | Dictionary<string,string> dic =new Dictionary<string,string> (); |
41 | dic.Add("action","1"); |
42 | dic.Add("usrname","xys"); |
43 | dic.Add("psw","123456"); |
49 | IEnumerator POST(stringurl, Dictionary<string,string> post) |
51 | WWWForm form =new WWWForm(); |
52 | foreach(KeyValuePair<string,string> post_arg in post) |
54 | form.AddField(post_arg.Key, post_arg.Value); |
57 | WWW www =new WWW(url, form); |
63 | Debug.Log("error is :"+ www.error); |
68 | Debug.Log("request ok : "+ www.text); |
73 | IEnumerator GET(stringurl) |
76 | WWW www =new WWW (url); |
82 | Debug.Log("error is :"+ www.error); |
87 | Debug.Log("request ok : "+ www.text); |
如果想通过HTTP传递二进制流的话 可以使用 下面的方法。
1 | WWWForm wwwForm = newWWWForm(); |
2 | byte[] byteStream = System.Text.Encoding.Default.GetBytes(stream); |
3 | wwwForm.AddBinaryData("post", byteStream); |
4 | www = new WWW(Address, wwwForm); |
目前Socket数据包还是没有进行加密算法,后期我会补上。欢迎讨论,互相学习互相进度 加油,蛤蛤。
转载http://www.xuanyusong.com/archives/1948