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,微软提供出的SocketAPI也比较多,比较难掌握,有同步的,还有异步的接收,发送API等。

(3)C# TcpClient

在.net中还提供了TcpClient TcpListener,这2个类对套接字Socket进行了封装,使得操作变得更为简单。使用TcpClient设计网络层会比Socket更方便,不需要了解那么多复杂的API。代码量也会少很多。在实际项目中更加偏向于选取TcpClient是实现网络层。

可以看到在实际项目中,如果需要快速开发的话,会选择TcpClient来实现我们的网络层设计。TcpClientSocket并没有性能上的区别,因为TcpClient只是对Socket进行了封装操作,假如我们选用Socket,我们自己的项目也会对Socket进行封装处理。关于TcpClientSocket的区别,这篇博客写的很清楚TcpClientSocket的概念和区别

参考链接:
http://www.cnblogs.com/fxair/articles/1534853.html

socket_tcp



2 选用C# TcpClient组件来设计客户端网络层

server_client_graphic

由这几个类来完成网络层

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键,客户端会发送一条登入协议到服务器。服务器收到这条协议后会把所有的字段进行转发到客户端。

client_server_example

这个项目有2个工程,一个是服务器工程,一个是客户端工程。本篇主要介绍的是客户端的网络层设计。
工程下载连接(Tcp_Server_Client)

原创粉丝点击