版权表明:正文为原创文章,转载请宣示
unity探求者之socket传输protobuf字节流(三),unityprotobuf
版权申明:正文为原创小说,转发请宣示
上一篇讲到了多少的处理,这一篇首要讲使用十二线程收发音讯
1 //创建消息数据模型
2 //正式项目中,消息的结构一般是消息长度+消息id+消息主体内容
3 public class Message
4 {
5 public IExtensible protobuf;
6 public int messageId;
7 }
8
9 public class SocketClientTemp : MonoBehaviour
10 {
11 const int packageMaxLength = 1024;
12
13 Socket mSocket;
14 Thread threadSend;
15 Thread threadRecive;
16 Queue<Message> allMessages = new Queue<Message>();
17 Queue<byte[]> sendQueue = new Queue<byte[]>();
18
19 public bool Init()
20 {
21 //创建一个socket对象
22 mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
23 return SocketConnection("此处是ip", 1111);
24 }
25
26 void Update()
27 {
28 AnalysisMessage();
29 }
30
31 /// <summary>
32 /// 建立服务器连接
33 /// </summary>
34 /// <param name="ip">服务器的ip地址</param>
35 /// <param name="port">端口</param>
36 bool SocketConnection(string ip, int port)
37 {
38 try
39 {
40 IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(ip), port);
41 //同步连接服务器,实际使用时推荐使用异步连接,处理方式会在下一篇讲断线重连时讲到
42 mSocket.Connect(ipep);
43 //连接成功后,创建两个线程,分别用于发送和接收消息
44 threadSend = new Thread(new ThreadStart(SendMessage));
45 threadSend.Start();
46 threadRecive = new Thread(new ThreadStart(ReceiveMessage));
47 threadRecive.Start();
48 return true;
49 }
50 catch (Exception e)
51 {
52 Debug.Log(e.ToString());
53 Close();
54 return false;
55 }
56 }
57
58 #region ...发送消息
59 /// <summary>
60 /// 添加数据到发送队列
61 /// </summary>
62 /// <param name="protobufModel"></param>
63 /// <param name="messageId"></param>
64 public void AddSendMessageQueue(IExtensible protobufModel, int messageId)
65 {
66 sendQueue.Enqueue(BuildPackage(protobufModel, messageId));
67 }
68
69 void SendMessage()
70 {
71 //循环获取发送队列中第一个数据,然后发送到服务器
72 while (true)
73 {
74 if (sendQueue.Count == 0)
75 {
76 Thread.Sleep(100);
77 continue;
78 }
79 if (!mSocket.Connected)
80 {
81 Close();
82 break;
83 }
84 else
85 Send(sendQueue.Peek());//发送队列中第一条数据
86 }
87 }
88
89 void Send(byte[] bytes)
90 {
91 try
92 {
93 mSocket.Send(bytes, SocketFlags.None);
94 //发送成功后,从发送队列中移除已发送的消息
95 sendQueue.Dequeue();
96 }
97 catch (SocketException e)
98 {
99 //如果错误码为10035,说明服务器缓存区满了,所以等100毫秒再次发送
100 if (e.NativeErrorCode == 10035)
101 {
102 Thread.Sleep(100);
103 Send(bytes);
104 }
105 else
106 Debug.Log(e.ToString());
107 }
108 }
109 #endregion
110
111 #region ...接收消息
112 /// <summary>
113 /// 解析收到的消息
114 /// </summary>
115 void AnalysisMessage()
116 {
117 while (allMessages.Count > 0)
118 {
119 int id = allMessages.Dequeue().messageId;
120 switch (id)
121 {
122 //根据消息id做不同的处理
123 }
124 }
125 }
126
127 /// <summary>
128 /// 接收数据
129 /// </summary>
130 void ReceiveMessage()
131 {
132 while (true)
133 {
134 if (!mSocket.Connected)
135 break;
136 byte[] recvBytesHead = GetBytesReceive(4);
137 int bodyLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(recvBytesHead, 0));
138 byte[] recvBytesBody = GetBytesReceive(bodyLength);
139
140 byte[] messageId = new byte[4];
141 Array.Copy(recvBytesBody, 0, messageId, 0, 4);
142 byte[] messageBody = new byte[bodyLength - 4];
143 Array.Copy(recvBytesBody, 4, messageBody, 0, bodyLength - 4);
144
145 if (BitConverter.IsLittleEndian)
146 Array.Reverse(messageId);
147 FillAllPackages(BitConverter.ToInt32(messageId, 0), messageBody);
148 }
149 }
150
151 /// <summary>
152 /// 填充接收消息队列
153 /// </summary>
154 /// <param name="messageId"></param>
155 /// <param name="messageBody"></param>
156 void FillAllPackages(int messageId, byte[] messageBody)
157 {
158 switch (messageId)
159 {
160 //根据消息id处理消息,并添加到接收消息队列
161 case 1:
162 allMessages.Enqueue(new Message()
163 {
164 protobuf = ProtobufSerilizer.DeSerialize<TestTemp>(messageBody),
165 messageId = messageId
166 });
167 break;
168 }
169 }
170
171 /// <summary>
172 /// 接收数据并处理
173 /// </summary>
174 /// <param name="length"></param>
175 /// <returns></returns>
176 byte[] GetBytesReceive(int length)
177 {
178 byte[] recvBytes = new byte[length];
179 while (length > 0)
180 {
181 byte[] receiveBytes = new byte[length < packageMaxLength ? length : packageMaxLength];
182 int iBytesBody = 0;
183 if (length >= receiveBytes.Length)
184 iBytesBody = mSocket.Receive(receiveBytes, receiveBytes.Length, 0);
185 else
186 iBytesBody = mSocket.Receive(receiveBytes, length, 0);
187 receiveBytes.CopyTo(recvBytes, recvBytes.Length - length);
188 length -= iBytesBody;
189 }
190 return recvBytes;
191 }
192 #endregion
193
194 /// <summary>
195 /// 构建消息数据包
196 /// </summary>
197 /// <param name="protobufModel"></param>
198 /// <param name="messageId"></param>
199 byte[] BuildPackage(IExtensible protobufModel, int messageId)
200 {
201 byte[] b;
202 if (protobufModel != null)
203 b = ProtobufSerilizer.Serialize(protobufModel);
204 else
205 b = new byte[0];
206 //消息长度(int数据,长度4) + 消息id(int数据,长度4) + 消息主体内容
207 ByteBuffer buf = ByteBuffer.Allocate(b.Length + 4 + 4);
208 //消息长度 = 消息主体内容长度 + 消息id长度
209 buf.WriteInt(b.Length + 4);
210 buf.WriteInt(messageId);
211
212 if (protobufModel != null)
213 buf.WriteBytes(b);
214 return buf.GetBytes();
215 }
216
217 void OnDestroy()
218 {
219 //停止运行后,如果不关闭socket多线程,再次运行时,unity会卡死
220 Close();
221 }
222
223 /// <summary>
224 /// 关闭socket,终止线程
225 /// </summary>
226 public void Close()
227 {
228 if (mSocket != null)
229 {
230 //微软官方推荐在关闭socket前先shutdown,但是经过测试,发现网络断开后,shutdown会无法执行
231 if (mSocket.Connected)
232 mSocket.Shutdown(SocketShutdown.Both);
233 mSocket.Close();
234 mSocket = null;
235 }
236 //关闭线程
237 if (threadSend != null)
238 threadSend.Abort();
239 if (threadRecive != null)
240 threadRecive.Abort();
241 threadSend = null;
242 threadRecive = null;
243 }
244 }
到那边,使用socket管理音信的收发就大旨甘休了,可是,有个别品种为了增进体验,恐怕还会增多断线重连的职能,那一个功用会在下1篇讲到
【澳门葡京备用网址】unity查究者之socket传输protobuf字节流,学习Unet的一些历程。
版权评释:
本文为原创文章,转载请宣示
上…
上一篇讲到了数额的管理,那一篇首要讲使用八线程收发音信
结束学业设计的体系,须要用到手提式有线电话机作为调节端,那就须要用到Unity的网络模块。
1 //创建消息数据模型
2 //正式项目中,消息的结构一般是消息长度+消息id+消息主体内容
3 public class Message
4 {
5 public IExtensible protobuf;
6 public int messageId;
7 }
8
9 public class SocketClientTemp : MonoBehaviour
10 {
11 const int packageMaxLength = 1024;
12
13 Socket mSocket;
14 Thread threadSend;
15 Thread threadRecive;
16 Queue<Message> allMessages = new Queue<Message>();
17 Queue<byte[]> sendQueue = new Queue<byte[]>();
18
19 public bool Init()
20 {
21 //创建一个socket对象
22 mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
23 return SocketConnection("此处是ip", 1111);
24 }
25
26 void Update()
27 {
28 AnalysisMessage();
29 }
30
31 /// <summary>
32 /// 建立服务器连接
33 /// </summary>
34 /// <param name="ip">服务器的ip地址</param>
35 /// <param name="port">端口</param>
36 bool SocketConnection(string ip, int port)
37 {
38 try
39 {
40 IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(ip), port);
41 //同步连接服务器,实际使用时推荐使用异步连接,处理方式会在下一篇讲断线重连时讲到
42 mSocket.Connect(ipep);
43 //连接成功后,创建两个线程,分别用于发送和接收消息
44 threadSend = new Thread(new ThreadStart(SendMessage));
45 threadSend.Start();
46 threadRecive = new Thread(new ThreadStart(ReceiveMessage));
47 threadRecive.Start();
48 return true;
49 }
50 catch (Exception e)
51 {
52 Debug.Log(e.ToString());
53 Close();
54 return false;
55 }
56 }
57
58 #region ...发送消息
59 /// <summary>
60 /// 添加数据到发送队列
61 /// </summary>
62 /// <param name="protobufModel"></param>
63 /// <param name="messageId"></param>
64 public void AddSendMessageQueue(IExtensible protobufModel, int messageId)
65 {
66 sendQueue.Enqueue(BuildPackage(protobufModel, messageId));
67 }
68
69 void SendMessage()
70 {
71 //循环获取发送队列中第一个数据,然后发送到服务器
72 while (true)
73 {
74 if (sendQueue.Count == 0)
75 {
76 Thread.Sleep(100);
77 continue;
78 }
79 if (!mSocket.Connected)
80 {
81 Close();
82 break;
83 }
84 else
85 Send(sendQueue.Peek());//发送队列中第一条数据
86 }
87 }
88
89 void Send(byte[] bytes)
90 {
91 try
92 {
93 mSocket.Send(bytes, SocketFlags.None);
94 //发送成功后,从发送队列中移除已发送的消息
95 sendQueue.Dequeue();
96 }
97 catch (SocketException e)
98 {
99 //如果错误码为10035,说明服务器缓存区满了,所以等100毫秒再次发送
100 if (e.NativeErrorCode == 10035)
101 {
102 Thread.Sleep(100);
103 Send(bytes);
104 }
105 else
106 Debug.Log(e.ToString());
107 }
108 }
109 #endregion
110
111 #region ...接收消息
112 /// <summary>
113 /// 解析收到的消息
114 /// </summary>
115 void AnalysisMessage()
116 {
117 while (allMessages.Count > 0)
118 {
119 int id = allMessages.Dequeue().messageId;
120 switch (id)
121 {
122 //根据消息id做不同的处理
123 }
124 }
125 }
126
127 /// <summary>
128 /// 接收数据
129 /// </summary>
130 void ReceiveMessage()
131 {
132 while (true)
133 {
134 if (!mSocket.Connected)
135 break;
136 byte[] recvBytesHead = GetBytesReceive(4);
137 int bodyLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(recvBytesHead, 0));
138 byte[] recvBytesBody = GetBytesReceive(bodyLength);
139
140 byte[] messageId = new byte[4];
141 Array.Copy(recvBytesBody, 0, messageId, 0, 4);
142 byte[] messageBody = new byte[bodyLength - 4];
143 Array.Copy(recvBytesBody, 4, messageBody, 0, bodyLength - 4);
144
145 if (BitConverter.IsLittleEndian)
146 Array.Reverse(messageId);
147 FillAllPackages(BitConverter.ToInt32(messageId, 0), messageBody);
148 }
149 }
150
151 /// <summary>
152 /// 填充接收消息队列
153 /// </summary>
154 /// <param name="messageId"></param>
155 /// <param name="messageBody"></param>
156 void FillAllPackages(int messageId, byte[] messageBody)
157 {
158 switch (messageId)
159 {
160 //根据消息id处理消息,并添加到接收消息队列
161 case 1:
162 allMessages.Enqueue(new Message()
163 {
164 protobuf = ProtobufSerilizer.DeSerialize<TestTemp>(messageBody),
165 messageId = messageId
166 });
167 break;
168 }
169 }
170
171 /// <summary>
172 /// 接收数据并处理
173 /// </summary>
174 /// <param name="length"></param>
175 /// <returns></returns>
176 byte[] GetBytesReceive(int length)
177 {
178 byte[] recvBytes = new byte[length];
179 while (length > 0)
180 {
181 byte[] receiveBytes = new byte[length < packageMaxLength ? length : packageMaxLength];
182 int iBytesBody = 0;
183 if (length >= receiveBytes.Length)
184 iBytesBody = mSocket.Receive(receiveBytes, receiveBytes.Length, 0);
185 else
186 iBytesBody = mSocket.Receive(receiveBytes, length, 0);
187 receiveBytes.CopyTo(recvBytes, recvBytes.Length - length);
188 length -= iBytesBody;
189 }
190 return recvBytes;
191 }
192 #endregion
193
194 /// <summary>
195 /// 构建消息数据包
196 /// </summary>
197 /// <param name="protobufModel"></param>
198 /// <param name="messageId"></param>
199 byte[] BuildPackage(IExtensible protobufModel, int messageId)
200 {
201 byte[] b;
202 if (protobufModel != null)
203 b = ProtobufSerilizer.Serialize(protobufModel);
204 else
205 b = new byte[0];
206 //消息长度(int数据,长度4) + 消息id(int数据,长度4) + 消息主体内容
207 ByteBuffer buf = ByteBuffer.Allocate(b.Length + 4 + 4);
208 //消息长度 = 消息主体内容长度 + 消息id长度
209 buf.WriteInt(b.Length + 4);
210 buf.WriteInt(messageId);
211
212 if (protobufModel != null)
213 buf.WriteBytes(b);
214 return buf.GetBytes();
215 }
216
217 void OnDestroy()
218 {
219 //停止运行后,如果不关闭socket多线程,再次运行时,unity会卡死
220 Close();
221 }
222
223 /// <summary>
224 /// 关闭socket,终止线程
225 /// </summary>
226 public void Close()
227 {
228 if (mSocket != null)
229 {
230 //微软官方推荐在关闭socket前先shutdown,但是经过测试,发现网络断开后,shutdown会无法执行
231 if (mSocket.Connected)
232 mSocket.Shutdown(SocketShutdown.Both);
233 mSocket.Close();
234 mSocket = null;
235 }
236 //关闭线程
237 if (threadSend != null)
238 threadSend.Abort();
239 if (threadRecive != null)
240 threadRecive.Abort();
241 threadSend = null;
242 threadRecive = null;
243 }
244 }
因为只会计统计筹到多少个大致的开关命令,所以不打算做多么复杂的功能,1开头希图用C#的Socket编制程序,但是思虑到10贰线程的关联以为以后跟UI结合起来管理会比较麻烦,机智的本人说了算利用Unity提供的Unet互连网模块。无需太难为,只须求cs之间发送新闻就行。
到那边,使用socket管理音信的收发就着力结束了,但是,有些品种为了增加体验,可能还会加多断线重连的职能,这几个效应会在下一篇讲到
一齐头自己认为温馨掉进了3个坑,英特网关于Unet的例证不要太少,而且不少都以依照六人游玩的。最无语的少数正是个中最牛的Commands和ClientXC90pc的长途调用命令以及各样变量同步只援助同1对象的两样实例间调用,不难点来讲,正是客户端和服务器必须是同二个安装包,他俩只是打开药方式不等同。可是我只是想做一个调控器作为客户端而已呀。不过本人可能坚信,如此牛x的网络模块,断定会支撑笔者想要的这种傻x通信的。
小编仔细看了Unity全部的关于Network的零部件。最后将指标锁定在了NetworkManager上。接下来就是去看NetworkManager的API啦,果然,壹拉下来本人就通晓没错了
在Public
Functions里设有不少On起先的措施,对Unity稍微熟识一点的都了然以On初步的是Unity提供的回调函数,用于处监护人件。同时也找到了StartClient、Host、Server这一个主意。找到了常用的主宰方法和回调函数,接下去就是要找发送和吸收接纳音信的目的了。但是本身平昔不丝毫线索。小编说了算先将客户端和服务器连接成功再说。于是,小编先引用到NetworkManager的目的,在手提式有线电话机端调用StartClient方法,在计算机端调用StartSever方法。开采相互能再而三成功。
接下来正是拍卖部分接连事件了,在Scripting
API文书档案里看NetworkManager类的Description。这里用1段简洁的代码清楚的介绍了什么运用那一个连接事件
澳门葡京备用网址 ,毋庸置疑,只必要写三个类承接NetworkManager类。就可以很轻松的重写回调函数,自定义连接事件了。何止爽歪歪。
然后我将原来增加的NetworkManager组件删掉,新建1个类并承接自NetworkManager。重写了一些内需接纳的回调方法。完美化解。
以后能接2连十分三功,并且也能管理部分连连事件了,接下去正是cs之间收发音讯了。依照经验,cs之间收发信息应该是依附某种对象调用Send方法和Receive方法。继续翻看API发现有诸如此类八个或者会用到的类NetworkServer、NetworkClient、NetworkReader、NetworkWriter、NetworkConnection。因为自己在NetworkServer、NetworkClient、NetworkConnection都找到了与send有关的点子;而NetworkReader、NetworkWriter1看就清楚是一个读3个写。不过却不曾在NetworkServer、NetworkClient、NetworkConnection那两个类找到任何与Receive有关的措施,就认为很意外。按理说有了NetworkReader、NetworkWriter就可以兑现自身想要的效能了,但是笔者意识回调函数都会传递一个NetworkConnection对象,所以作者可能愿意一向运用NetworkConnection类,于是放任了Reader和Writer,把关键放在了NetworkConnection类。继续看API…
仔细查阅了NetworkConnection的所以措施的介绍,开采有这么3个办法public
void RegisterHandler(short msgType, Networking.NetworkMessageDelegate
handler)
才意识到原来这里运用的是观望者方式。再进NetworkMessageDelegate看看这几个委托的宣示格式。作者只需求自定义一个与NetworkMessageDelegate同样格式的措施。看看NetworkMessageDelegate的申明格式:
public delegate void NetworkMessageDelegate(Networking.NetworkMessage
netMsg)。这里又涉及到了四个类:
NetworkMessage,依稀中记得NetworkConnection的send方法也急需传递1个NetworkMessage对象。表达小编的大势没错,NetworkConnection的音信传递是因而NetworkMessage类来封装的。进度应该是特别明朗的。先构造多个NetworkMessage对象,将以此指标通过send发送出去,在另1端用注册的主意接收NetworkMessage对象,最终深入分析成本人索要的消息。那么接下去最终一步–驾驭NetworkMessage类,继续API…
接下去蒙受一个相比困难的主题材料—笔者找不到组织NetworkMessage对象的章程,API里不曾提供构造函数,也未尝找到任何有效的静态方法。计划将梦想依托到他的父类,在vs里双击选中NetworkMessage,F1贰转到定义。才开采她从未父类,几乎握草了,就像是走进了死胡同。那时只有拿出极端军火了,百度时而…
直接搜索NetworkMessage。在那篇文章中找到了章程。仔细斟酌了一下,开掘和自个儿的笔触大约如出壹辙。
好了,最终来计算一下历程,先说服务器端:
1、
写2个Sever类,承袭自NetworkManager。把这几个类挂在1个空物体上,并在Inspector面板设置好参数
2、 写按键点击三个轩然大波,调用StartServer方法
3、 使用override关键字重写一些on开始的网络事件措施
四、 写三个sendMsg的主意,在格局体内调用NetworkConnection的send方法
五、 写2个public void XXX(NetworkMessage
netMsg)这种申明格局的不贰诀窍,管理收到的消息
六、
找3个得当的机遇,调用NetworkConnection的RegisterHandler方法,将第4步定义的点子注册进来
客户端:
一、
写一个Client类,承袭自NetworkManager。把这些类挂在一个空物体上,并在Inspector面板设置好参数(注意,客户端的NetworkAddress应该填写服务器端的IP,注意保持端口一致)
二、 写开关点击八个风云,调用StartClient方法
三、 使用override关键字重写一些on开端的网络事件措施
4、 写两个sendMsg的不2诀要,在点子体内调用NetworkConnection的send方法
伍、 写三个public void XXX(NetworkMessage
netMsg)这种表明方式的办法,管理收到的音讯
陆、
找三个方便的空子,调用NetworkConnection的RegisterHandler方法,将第4步定义的措施注册进来