unity3D学习之网络通讯层设计
- 格式:doc
- 大小:56.00 KB
- 文档页数:10
首先,请接触过unity中的Network网络层功能的童鞋注意,本文中出现的“Network”字样(如NetworkStream类),与Unity中的Network没有任何关系,请注意区分。
实际上,本系列除最后部分会将客户端网络层与Unity工程进行对接,其余部分都不会与Unity有任何联系。
其次,本人才疏学浅,虽添为某网游的服务器端主程,但其实也是赶鸭子上架,基础知识可能还没有各位看官扎实,所以文中难免有疏漏和贻误,写这篇文章也只是为印证心中所想的随手笔记,各位看官如果发现有错误或不规范之处,还望不吝赐教,我会马上修正,以免这些错误误导他人。
言归正传,首先是一些本文中可能会出现的基本概念的简单介绍。
C/S结构:C/S结构中的C,是Client(客户端)的缩写,S,即是Server(服务器端)的缩写,顾名思义,就是客户端发送请求,服务器端负责处理请求并将处理结果返回给客户端,客户端负责展示的一种结构。
短连接与长连接:短连接是指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送(百度百科)。
相对于短连接,长连接是在一次交互过程中一直保持连接,期间可能会完成多项业务的发送或接收。
长连接的好处是只建立一次连接,缺点是对服务器端压力比短连接要大(因为服务器端要维持所有的连接)。
一般网游都会使用长连接,少数与服务器端交互比较少,不需要太注重客户端的数据安全问题,以及不注重与其他玩家的交互功能的游戏会使用短连接。
文章来自【狗刨学习网】粘包与分包:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾(百度百科)。
粘包的出现原因有很多,大家不必在意这些细节,有兴趣的看官可以自行百度“socket的半包,粘包与分包的问题”。
协议:客户端与服务器端约定好的数据解析格式。
本文的最终目的,是完成一整套的客户端/服务器端的收发包,以及对Socket包的粘包和分包的处理,并包含一种本人实验性质的协议解析算法。
今天先为大家奉上第一部分,一套基于TcpListener的收发包范例。
我将会对代码中的重点部分单独提取出来进行简单的分析,帖子最下面是完整的工程,本人是使用的VS2013,如果下载下来用其他版本的VS无法加载,各位可以用自己的VS新建一个控制台项目工程,然后将Programs文件覆盖,将其他cs代码文件加入项目即可。
服务器端部分:public void OnConnect(string sip, int port){IPAddress adr = IPAddress.Parse(sip);tcpServer = new TcpListener(adr, port);tcpServer.Start();}启动TcpListener监听。
private void StartListen(){while (true){TcpClient tcpClient = tcpServer.AcceptTcpClient();ClientConnection cc = newClientConnection(tcpClient);Console.WriteLine(cc.connectionDesc + "connected.");cc.RawSendPacket("Hello client " +cc.connectionDesc);}}这里的while(true)的死循环的作用是在服务器端运行过程中一直保持监听是否有新的客户端连接来。
ClientConnection是一个我们自己封装的类,目的是暴露一些经常需要调用的接口,如connectionDesc(客户端连接的套接字,是一个类似192.168.1.100:7039这样的字符串),以及将读包和发包的逻辑封装起来。
public void BeginRead(){byte[] bytes = new byte[1024];clientStream.BeginRead(bytes, 0, 1024, ReadDone,bytes);}异步读包写法。
private void ReadDone(IAsyncResult ar){byte[] bytes = (byte[])ar.AsyncState;int iBytesAvalible = clientStream.EndRead(ar);if (iBytesAvalible <= 0){DoSetDisconnected();return;}else{MemoryStream ms = new MemoryStream(bytes);BinaryReader br = new BinaryReader(ms);int iType = br.ReadInt32();string sContent = br.ReadString();if (iType == 1){Console.WriteLine("Client says:" + " " + sContent);}BeginRead();}}这个函数中的else语句块中为本文的临时解包写法,之后将会进行扩展完善。
这个函数中的br.ReadInt32();以及br.ReadString();将会和客户端部分的构建发包的部分相对应,简单来说就是协商好,客户端将会先写入什么类型,服务器端解包时就需要先读出什么类型。
这个函数最后还要调用一下之前的BeginRead函数,继续异步读包。
这里相当于一个死循环的递归函数,不过这是正常的需要,只有收到客户端的发包后ReadDone才会被调用到。
public void RawSendPacket(string sContent){MemoryStream ms = new MemoryStream();BinaryWriter bw = new BinaryWriter(ms);bw.Write(sContent);clientStream.BeginWrite(ms.GetBuffer(), 0,ms.GetBuffer().Length, null, null);}这个函数为本文的临时构建包及发包写法,之后将会进行扩展完善。
这是服务器端的构建发包的部分,和上面的部分一样,客户端的解包部分要和这里的构建包部分相对应,服务器端先写入了什么类型,客户端就需要先读出什么类型。
服务器端部分到此结束,是不是很简单呢?哈哈让我们来简单整理一下,服务器端部分的功能其实分为两个部分,一部分是TcpListener的监听启动,另一部分就是收发包部分了,其中第二部分其实和客户端部分是重合的,因为客户端其实也是需要收发包的(好像是废话…),当然,客户端是用TcpClient来实现的,这是唯一的区别。
服务器端简单流程:启动监听–收包–解包–处理包–构建回包–发包–收包…接下来是客户端部分,这部分就更简单了。
public void OnConnect(string sip, int port){tcpClient = new TcpClient(sip, port);if (tcpClient == null|| !tcpClient.Connected){Console.WriteLine("Connect failed:" + sip + ":" +port);return;}Console.WriteLine("Connect success:" + sip + ":" + port);clientStream = tcpClient.GetStream();}连接服务器端,这个不必多说了吧?public void run(){while (tcpClient != null){byte[] bytes = new byte[byteLength];clientStream.Read(bytes, 0, byteLength);//这里在读不到包时是阻塞的MemoryStream ms = new MemoryStream(bytes);BinaryReader bw = new BinaryReader(ms);string str = bw.ReadString();Console.WriteLine("Received:" + str);}}啊,又见while死循环,不要惊慌,这是最后一个了。
这部分其实是为了方便,将收包和解包写到一起了,之后将会进行扩展完善。
就像上文提到的,这里的Read要和服务器端的构建包时的Write的一系列类型一一对应,顺序也不能改变(本例里只有一个string类型)。
public void SendPacket(int iType, stringsChatContent){MemoryStream ms = new MemoryStream();BinaryWriter bw = new BinaryWriter(ms);bw.Write(iType);bw.Write(sChatContent);clientStream.Write(ms.GetBuffer(), 0,ms.GetBuffer().Length);}构建包及发包部分,这里的Write要和服务器端收包之后的解析包部分的类型和顺序一一对应。
clientStream.Write为服务器端的clientStream.BeginWrite的同步调用方法,这两中调用方法都可以使用,但是个人推荐BeginWrite这样的异步调用方法。
简单整理一下客户端部分的流程,连接服务器端–发包–收包–处理包–发包…。