3D游戏中的网络层设计
来源:互联网 发布:java 国际化 编辑:程序博客网 时间:2024/05/30 20:07
note 目录
游戏开发中选用哪种组件来设计网络层
Tcp_Client
实际效果和工程实例子
1 选用哪种组件来设计网络层
在unity的引擎中,我们可以选用3种组件来实现网络层。
第一个是 unity本身自带的
NetWork
组件第二个是 C# 层的
socket
组件第三个是 C# 层的
TcpClient
组件
分析在实际项目中我们该如何选择。
(1)untiy NetWork
untiy本身自带有NetWork
组件,但是,局限性比较到,到了实际项目中基本上不会去unity本身提供的NetWork
组件来设计网络层。
(2)C# Socket
在OSI网络七层协议,知道TCP是属于传输层协议,,那么如何从应用程序中获取来自传输层的数据,就是通过套接字Socket
来实现的,套接字就像是传输层打开的一扇门,应用程序通过这扇门向远程发送和接受数据。Socket
属于比较底层的API,微软提供出的Socket
API也比较多,比较难掌握,有同步的,还有异步的接收,发送API等。
(3)C# TcpClient
在.net中还提供了TcpClient
TcpListener
,这2个类对套接字Socket
进行了封装,使得操作变得更为简单。使用TcpClient
设计网络层会比Socket
更方便,不需要了解那么多复杂的API。代码量也会少很多。在实际项目中更加偏向于选取TcpClient
是实现网络层。
可以看到在实际项目中,如果需要快速开发的话,会选择TcpClient来实现我们的网络层设计。TcpClient
和Socket
并没有性能上的区别,因为TcpClient
只是对Socket
进行了封装操作,假如我们选用Socket
,我们自己的项目也会对Socket
进行封装处理。关于TcpClient
和Socket
的区别,这篇博客写的很清楚TcpClient
和Socket
的概念和区别
参考链接:
http://www.cnblogs.com/fxair/articles/1534853.html
2 选用C# TcpClient
组件来设计客户端网络层
由这几个类来完成网络层
ByteBuffer
PackageIn
SocketClient
NetManager
BaseSocketHandler
2.1:NetManager
负责对外的接口,提供出4个接口
(1)Connect(string ip,int port)
连接服务器
(2)HandlePackage(PackageIn pkg)
收协议:处理服务器发过来的协议
(3)PackageOut GetPackageOut(int msgId)
获取一个发出去的包
(4)SendMessage(PackageOut pkg)
发协议:客户端发送一条协议出去
using System;using System.Collections.Generic;using System.Text;//对外层的接口//public class NetManager{ private static NetManager _instance = null; private SocketClient _socketClient; private Dictionary<int, BaseNetHandler> _netDic; public NetManager() { _netDic = new Dictionary<int, BaseNetHandler>(); _socketClient = new SocketClient(); AddNetHandler(new LoginNetHandler()); } public static NetManager Instance { get { if (_instance == null) _instance = new NetManager(); return _instance; } } public SocketClient socketClient { get { return _socketClient; } set { _socketClient = value; } } private void AddNetHandler(BaseNetHandler net) { int key = net.GetCode(); if (!_netDic.ContainsKey(key)) { _netDic.Add(key, net); } else { Console.WriteLine("Add same net handler"); } } public void Connect(string ip, int port) { if (_socketClient != null) { _socketClient.Connect(ip, port, ConnectCallback); } else { Console.WriteLine("SocketClient is null"); } } private void ConnectCallback(ConnectState connectState) { Console.WriteLine("connectState:" + connectState); } //处理服务器发过来的协议 // public void HandlePackage(PackageIn pkg) { int msgId = pkg.code; if (_netDic.ContainsKey(msgId)) { _netDic[msgId].Configure(pkg); _netDic[msgId].HandlePackage(); } else { Console.WriteLine("not exist protocal,msg id is:" + msgId); } } //根据协议ID获取一个发送出去的包 // public PackageOut GetPackageOut(int msgId) { PackageOut pkg = new PackageOut(msgId); return pkg; } //发送协议 // public void SendMessage(PackageOut pkg) { if (socketClient != null) { socketClient.WriteMessage(pkg.SetPackage()); } else { Console.WriteLine("socket client is null"); } }}
2.2:ByteBuffer
在字节流中负责读写操作。客户端和服务器2边进行通信发送的都是字节流,因为.net本身没有提供直接从字节流中读取int,ushort,long,float,double这些类型的方法。需要自己封装相应的接口出来。
using System;using System.Collections.Generic;using System.Text;using System.IO;public class ByteBuffer{ //内存流对象用来进行字节流的操作 protected MemoryStream stream = null; //写对象 protected BinaryWriter writer = null; //读对象 protected BinaryReader reader = null; public ByteBuffer() { stream = new MemoryStream(); writer = new BinaryWriter(stream); } public ByteBuffer(byte[] data) { if (data != null)//初始化ByteBuffer对象只负责读字节流 { stream = new MemoryStream(data); reader = new BinaryReader(stream); } else//初始化ByteBuffer对象只负责写字节流 { stream = new MemoryStream(); writer = new BinaryWriter(stream); } } public void Close() { if (writer != null) writer.Close(); if (reader != null) reader.Close(); if (stream != null) stream.Close(); writer = null; reader = null; stream = null; } //往字节流中写入一个字节 public void WriteByte(byte value) { writer.Write(value); } //往字节流中写入一个int值 public void WriteInt(int value) { writer.Write((int)value); } //往字节流中写入一个ushort值 public void WriteShort(ushort value) { writer.Write((ushort)value); } //往字节流中写入一个long值 public void WriteLong(long value) { writer.Write((long)value); } //往字节流中写入一个float值 public void WriteFloat(float value) { byte[] temp = BitConverter.GetBytes(value); Array.Reverse(temp); writer.Write(BitConverter.ToSingle(temp,0)); } //往字节流中写入一个double值 public void WriteDouble(double value) { byte[] temp = BitConverter.GetBytes(value); Array.Reverse(temp); writer.Write(BitConverter.ToDouble(temp,0)); } //往字节流中写入一个string值 public void WriteString(string value) { byte[] bytes = Encoding.UTF8.GetBytes(value); writer.Write((ushort)bytes.Length); writer.Write(bytes); } //往字节流中写入一个字节数组byte[] public void WriteBytes(byte[] value) { writer.Write((int)value.Length); writer.Write(value); } //在字节流中读取一个字节 public byte ReadByte() { return reader.ReadByte(); } //在字节流中读取一个int值 public int ReadInt() { return (int)reader.ReadInt32(); } //在字节流中读取一个ushort值 public ushort ReadShort() { return (ushort)reader.ReadInt16(); } //在字节流中读取一个long值 public long ReadLong() { return (long)reader.ReadInt64(); } //在字节流中读取一个float值 public float ReadFloat() { byte[] temp = BitConverter.GetBytes(reader.ReadSingle()); Array.Reverse(temp); return BitConverter.ToSingle(temp, 0); } //在字节流中读取一个double值 public double ReadDouble() { byte[] temp = BitConverter.GetBytes(reader.ReadDouble()); Array.Reverse(temp); return BitConverter.ToDouble(temp, 0); } //在字节流中读取一个string值 public string ReadString() { ushort len = ReadShort(); byte[] buffer = new byte[len]; buffer = reader.ReadBytes(len); return Encoding.UTF8.GetString(buffer); } //在字节流中读取一个字节数组 public byte[] ReadBytes() { int len = ReadInt(); return reader.ReadBytes(len); } //将写入到内存流中转换为字节数组 public byte[] ToBytes() { writer.Flush(); return stream.ToArray(); } public void Flush() { writer.Flush(); }}
2.3:SocketClient
SocketClient
这个类主要负责连接上服务器,收到服务器发来的包进行解包和组包。
Tcp每次发过来的包可能不是一条的协议包,需要自己对服务器发过来的字节流进行组织,先要读取出包头发过来的长度字段,根据这个长度,判断发过来的字节流是否够一条协议的包的字节流。
msgLength | msgId | 包内容 | 包内容 | 包内容 | ... | ... |
一般协议组成由包头和包内容组成。
一条完整协议 = 包头 + 协议内容
包头 = 这个协议的字节流长度 + 协议ID
using System;using System.IO;using System.Collections.Generic;using System.Text;using System.Net;using System.Net.Sockets;//连接服务器的状态public enum ConnectState{ Connected,//连接成功 Disconnect,//连接不上服务器}public class SocketClient{ //缓存中接收最大的字节 private const int BufferSize = 8192; //缓存中接收到的字节数组 private byte[] byteBuffer; //TcpClient对象 private TcpClient client; //客户端到服务器的流对象,用这个对象和远程服务器来接收和发送字节流 private NetworkStream streamToServer; //内存流对象,用MemoryStream对象可以很方便的对内存中的字节流进行操作 private MemoryStream memoStream; //从内存流对象memoStream中读取字节流的对象 private BinaryReader reader; //客户端的连接状态 private ConnectState _connectState; //连接服务器后的回调 private Action<ConnectState> _connectedCallback; public SocketClient() { } //连接服务器 //ip:可以是数字IP字段,也可以是域名 //port:服务器的端口 //callback:连接后的回调 public void Connect(string ip, int port,Action<ConnectState> callback) { _connectedCallback = callback; try { client = new TcpClient(); IPAddress[] addrIps = Dns.GetHostAddresses(ip); client.BeginConnect(addrIps, port, new AsyncCallback(OnConnect), null); } catch (Exception ex) { Console.WriteLine(ex.Message); return; } } void OnConnect(IAsyncResult asr) { //不管是否连接到服务器,把连接服务器的状态回调出去 if (_connectedCallback != null) { _connectedCallback(client.Connected ? ConnectState.Connected:ConnectState.Disconnect); } if (!client.Connected) { Console.WriteLine(" 连接服务器失败"); return; } byteBuffer = new byte[BufferSize]; memoStream = new MemoryStream(); reader = new BinaryReader(memoStream); //打印连接到服务器的信息 Console.WriteLine("Server Connected!{0} -- > {1}",client.Client.LocalEndPoint,client.Client.RemoteEndPoint); streamToServer = client.GetStream(); AsyncCallback callback = new AsyncCallback(ReadComplete); streamToServer.BeginRead(byteBuffer,0,BufferSize,callback,null); } private void ReadComplete(IAsyncResult ar) { int bytesRead = 0; try { lock (streamToServer) { bytesRead = streamToServer.EndRead(ar); } if (bytesRead == 0) throw new Exception("读取到0字节"); OnReceive(byteBuffer,bytesRead); //读取完后在进行异步读取操作,使得读的操作形成一个无限循环 lock (streamToServer) { Array.Clear(byteBuffer,0,byteBuffer.Length); AsyncCallback callback = new AsyncCallback(ReadComplete); streamToServer.BeginRead(byteBuffer,0,BufferSize,callback,null); } } catch(Exception ex) { if (streamToServer != null) streamToServer.Dispose(); client.Close(); Console.WriteLine(ex.Message); } } private void OnReceive(byte[] bytes, int length) { memoStream.Seek(0,SeekOrigin.End); memoStream.Write(bytes,0,length); memoStream.Seek(0,SeekOrigin.Begin); while (RemainingBytes() > ProtocalHead.HeadSize) { ushort messageLen = reader.ReadUInt16();//先读取出长度字段(定义为2个字节) if (RemainingBytes() >= ProtocalHead.ProtocalIdLength + messageLen)//读取了长度,剩下协议ID字段 + 协议本身内容 { MemoryStream ms = new MemoryStream(); BinaryWriter writer = new BinaryWriter(ms); writer.Write(reader.ReadBytes(ProtocalHead.ProtocalIdLength + messageLen)); ms.Seek(0, SeekOrigin.Begin); Console.WriteLine("receive length:" + messageLen); OnReceivedMessage(messageLen, ms); } else { memoStream.Position = memoStream.Position - ProtocalHead.HeadSize; break; } } //不够一条协议的时候,需要将服务器发过来的字节流缓存起来,等到下一次发包过来,组织成一个完整的包。 byte[] leftover = reader.ReadBytes((int)RemainingBytes()); memoStream.SetLength(0); memoStream.Write(leftover,0,leftover.Length); } private void OnReceivedMessage(ushort length, MemoryStream ms) { BinaryReader r = new BinaryReader(ms); byte[] message = r.ReadBytes((int)(ms.Length - ms.Position)); // PackageIn packageIn = new PackageIn(length,message); NetManager.Instance.HandlePackage(packageIn); } //客户端发送协议的唯一出口 //将字节流发送到服务器 public void WriteMessage(byte[] message) { MemoryStream ms = null; using (ms = new MemoryStream()) { ms.Position = 0; BinaryWriter writer = new BinaryWriter(ms); writer.Write(message); writer.Flush(); if (client != null && client.Connected) { byte[] payLoaded = ms.ToArray(); streamToServer.BeginWrite(payLoaded,0,payLoaded.Length,new AsyncCallback(OnWrite),null); } else { Console.WriteLine("client.connected -->> false"); } } } private void OnWrite(IAsyncResult r) { try { streamToServer.EndWrite(r); } catch (Exception ex) { Console.WriteLine(ex.Message); } } /// <summary> /// 剩余的字节 /// </summary> private long RemainingBytes() { return memoStream.Length - memoStream.Position; }}
2.4:PackageIn
PackageIn
继承ByteBuffer
,负责客户端的这边接收到服务器发送过来的字节流,判断得到一个完整的包的时候,对这个包的字节流进一步封装处理。得到它的协议ID,长度。
using System;using System.Collections.Generic;public class PackageIn : ByteBuffer{ private int _code; private int _packageLength; public PackageIn(int length, byte[] data) :base(data) { _code = ReadInt();//先读取出协议的ID _packageLength = length;//得到协议包内容的长度 } public int code { get { return _code; } } public int packageLength { get { return _packageLength; } }}
2.5:PackageOut
PackageOut
继承ByteBuffer
,负责客户端的这边的协议内容发送到服务器。要对协议的字节流进行处理,加上包头信息。
using System;using System.IO;using System.Collections.Generic;using System.Linq;using System.Text;public class PackageOut : ByteBuffer{ private int _code; private int _packageLength; public PackageOut(int code) : base() { _code = code; } public int code { get { return _code; } } public int packageLength { get { return _packageLength; } } //进行封包处理 //将协议的长度放在包头的前2个字节(1-2) //将协议的唯一ID放在包头的第3字节--到第6字节 //将协议的内容放在最后 public byte[] SetPackage() { byte[] message = stream.ToArray(); Console.WriteLine("send content:" + message.Length); MemoryStream ms = null; using (ms = new MemoryStream()) { ms.Position = 0; BinaryWriter writer = new BinaryWriter(ms); writer.Write((ushort)(message.Length));//协议包的长度 writer.Write((int)_code);//协议包的唯一id writer.Write(message);//协议内容 writer.Flush(); } return ms.ToArray(); }}
2.6:BaseSocketHandler
BaseSocketHandler
这个已经到了逻辑层的每条协议的一个基类。逻辑层的每条协议继承这个基类,重写这2
个方法就可以,int GetCode()
,HandlePackage()
using System;using System.Collections.Generic;using System.Text;public class BaseNetHandler{ protected PackageIn _data; //给每条协议顶一个唯一ID public virtual int GetCode() { return 0; } //给协议的字节流赋值 public void Configure(PackageIn data) { _data = data; } //每条协议处理发过来的包 public virtual void HandlePackage() { }}
3 实际效果和工程下载
上述可以构成完整的收发了。将服务器,客户端运行起来,都在同一台机器上,ip填写”localhost”,port填写”8500”。在客户端按下A键,客户端会发送一条登入协议到服务器。服务器收到这条协议后会把所有的字段进行转发到客户端。
这个项目有2个工程,一个是服务器工程,一个是客户端工程。本篇主要介绍的是客户端的网络层设计。
工程下载连接(Tcp_Server_Client)
- 3D游戏中的网络层设计
- 移动游戏的网络通信层设计
- AndroidApp应用中的网络层设计
- 3D游戏引擎的设计架构
- 3D游戏引擎特效系统设计
- 3d游戏的总体设计框架 .
- 3d游戏的总体设计框架
- 3d游戏的总体设计框架
- 3D游戏引擎技术架构设计
- 3D赛车游戏架构设计
- 3D游戏编程与设计 Week2
- 3D游戏编程与设计 Week3
- 3D游戏编程与设计 Week6
- 3D游戏编程与设计 Week8
- 3D游戏编程与设计 Week11
- 3D游戏中的公告牌技术
- 3D游戏中的碰撞检测
- 3D游戏中的数学运用
- ContentResolver获取系统短消息联系人等
- Web实验项目,网上银行系统
- 根据AQS推测CountDownLatch及源码分析
- Undo Segment Corruption and Recovery
- 101. Symmetric Tree
- 3D游戏中的网络层设计
- Jquery使用AJAX请求跨域解决方法
- bzoj 4917: Hash Killer IV 模拟
- C++入门基础知识
- VB.net
- 53. Maximum Subarray
- 11. Container With Most Water题解
- Linux网络端口
- 查验身份证