使用工具:VS2015

使用语言:c#

作者:Gemini_xujian

参考:siki老师-《丛林战争》视频教程

继上一篇文章内容,这节课讲解一下游戏服务器端的开发。

01-项目目录结构创建:

首先打开VS并创建一个c#控制台应用程序项目,起名为“游戏服务器端”,创建好后,右键项目->属性,将默认的命名空间改为GameServer(使用英文命名空间,对中文支持不好),然后创建几个文件夹,分别是:Model,Server,DAO,Tool,Controller。在之后的开发中将我们编写的代码脚本分别放到相应的文件夹中即可。另外,我们需要添加与MySQL数据库建立连接的引用库,引用方法在我之前的文章中讲到过(具体参考unity网络开发实战-011篇文章)。目录结构如图所示:

02-开启接收客户端连接,处理跟客户端的数据通信并对客户端消息进行解析,开发controller控制层

先上代码:

server类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Server
{//这个类用来启动我们的服务器端并进行监听class Server{private IPEndPoint ipEndPoint;private Socket serverSocket;private List<Client> clientList;//用来保存所有连接的客户端public Server() { }public Server(string ipStr, int port){SetIpAndPort(ipStr, port);}//设置ip和端口号public void SetIpAndPort(string ipStr, int port){ipEndPoint = new IPEndPoint(IPAddress.Parse(ipStr), port);}//建立连接public void Start(){serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);serverSocket.Bind(ipEndPoint);//绑定ipserverSocket.Listen(0);//设置监听,为0表示不限制连接数serverSocket.BeginAccept(AcceptCallBack, null);//开始接收客户端连接}//创建接收连接的回调函数private void AcceptCallBack(IAsyncResult ar){Socket clientSocket = serverSocket.EndAccept(ar);//接收到连接并将返回的客户端socket进行得到Client client = new Client(clientSocket, this);//创建一个client类,用来管理一个与客户端的连接client.Start();clientList.Add(client);//将此客户端添加到list集合中}//移除某个clientpublic void RemoveClient(Client client){lock (clientList){clientList.Remove(client);}}}
}

client类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Server
{//用来处理与客户端的通信问题class Client{private Socket clientSocket;private Server server;//持有一个server类的引用private Message msg = new Message();public Client() { }public Client(Socket clientSocket,Server server){this.clientSocket = clientSocket;this.server = server;}//开启监听public void Start(){clientSocket.BeginReceive(msg.Data,msg.StartIndex, msg.RemainSizs, SocketFlags.None,ReceiveCallBack, null);}//接收监听的回调函数private void ReceiveCallBack(IAsyncResult ar){//做异常捕捉try{int count = clientSocket.EndReceive(ar);//结束监听,并返回接收到的数据长度//如果count=0说明客户端已经断开连接,则直接关闭if (count == 0){Close();}msg.ReadMessage(count);//对消息的处理,进行消息的解析Start();//重新调用监听函数}catch (Exception e){Console.WriteLine(e);//出现异常则退出Close();}}private void Close(){if (clientSocket != null){clientSocket.Close();}server.RemoveClient(this);}}
}

message类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Server
{class Message{private byte[] data = new byte[1024];//用来存储现在的数据,需要足够大private int startIndex = 0;//用来保存当前已经存取的数据位置public byte[] Data{get{return data;}}public int StartIndex{get{return startIndex;}}public int RemainSizs{get{return data.Length - startIndex;}}更新索引//public void AddCount(int count)//{//    startIndex += count;//}/// <summary>/// 解析数据/// </summary>public void ReadMessage(int newDataAmount){startIndex += newDataAmount;while (true){if (startIndex <= 4) return;int count = BitConverter.ToInt32(data, 0);if (startIndex - 4 >= count){string s = Encoding.UTF8.GetString(data, 4, count);Array.Copy(data, count+4, data, 0, startIndex - 4 - count);startIndex -= count + 4;}else{break;}}}}
}

basecontroller类:

using Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Controller
{abstract class BaseController{RequestCode requestCode = RequestCode.None;//设置请求类型//默认的处理方法public virtual void DefaultHandle(){}}
}

requestcode枚举:

using System;
using System.Collections.Generic;
using System.Text;namespace Common
{public enum RequestCode{None,}
}

actioncode枚举:

using System;
using System.Collections.Generic;
using System.Text;namespace Common
{public enum ActionCode{None,}
}

步骤说明:在我们创建好项目后,首先创建一个server类用来启动服务器端并进行监听,在此类中,首先设置服务器ip和端口号,然后在start方法中创建socket对象绑定ip并设置监听,之后开始进行接收客户端的连接,每当有一个客户端连接时,创建一个client类,这个类专门用来管理客户端,一个客户端对应一个client类实例,创建一个client对象后,将此对象添加到list集合中进行存储,这样就完成了server类的基本任务和功能;在client类中,处理客户端消息的接收,当接收到客户端的消息数据时,则调用message类对数据消息进行解析,暂时实现到这里,接着创建一个basecontroller类,用来作为请求处理的基类,在此类中设置了一个请求类型requestcode,这个类型是我们要使用那个controller进行处理,actioncode是我们要使用哪个方法进行处理,这两中枚举类型是共享项目,也就是在客户端与服务器端要同时添加,并且内容要相同,所以在创建这两个枚举类型时,首先在VS中新建一个项目,项目类型选择类库,命名为common,创建好后,右键属性,将它的目标框架改为2.0,之后创建这两个枚举类型即可。关于message类的详细解释在前面的文章中有过讲解,这里就不过多叙述了。有疑问的朋友可以往上翻一翻我的文章。

03-管理控制器进行请求分发的处理,客户端请求响应的处理并完成客户端消息的解析和发送

先贴代码(高亮部分为修改或添加部分,新建类不做高亮处理):

server类:

using Common;
using GameServer.Controller;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Servers
{//这个类用来启动我们的服务器端并进行监听class Server{private IPEndPoint ipEndPoint;private Socket serverSocket;private List<Client> clientList;//用来保存所有连接的客户端private ControllerManager controllerManager;public Server() { }public Server(string ipStr, int port){controllerManager = new ControllerManager(this);SetIpAndPort(ipStr, port);}//设置ip和端口号public void SetIpAndPort(string ipStr, int port){ipEndPoint = new IPEndPoint(IPAddress.Parse(ipStr), port);}//建立连接public void Start(){serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);serverSocket.Bind(ipEndPoint);//绑定ipserverSocket.Listen(0);//设置监听,为0表示不限制连接数serverSocket.BeginAccept(AcceptCallBack, null);//开始接收客户端连接}//创建接收连接的回调函数private void AcceptCallBack(IAsyncResult ar){Socket clientSocket = serverSocket.EndAccept(ar);//接收到连接并将返回的客户端socket进行得到Client client = new Client(clientSocket, this);//创建一个client类,用来管理一个与客户端的连接client.Start();clientList.Add(client);//将此客户端添加到list集合中}//移除某个clientpublic void RemoveClient(Client client){lock (clientList){clientList.Remove(client);}}//向客户端发起响应public void SendResponse(Client client,ActionCode actionCode,string data){client.Send(actionCode,data);}public void HandleRequest(RequestCode requestCode,ActionCode actionCode,string data,Client client){controllerManager.HandleRequest(requestCode, actionCode, data, client);}}
}

client类:

using Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Servers
{//用来处理与客户端的通信问题class Client{private Socket clientSocket;private Server server;//持有一个server类的引用private Message msg = new Message();public Client() { }public Client(Socket clientSocket,Server server){this.clientSocket = clientSocket;this.server = server;}//开启监听public void Start(){clientSocket.BeginReceive(msg.Data,msg.StartIndex, msg.RemainSizs, SocketFlags.None,ReceiveCallBack, null);}//接收监听的回调函数private void ReceiveCallBack(IAsyncResult ar){//做异常捕捉try{int count = clientSocket.EndReceive(ar);//结束监听,并返回接收到的数据长度//如果count=0说明客户端已经断开连接,则直接关闭if (count == 0){Close();} msg.ReadMessage(count,OnProcessMessage);//对消息的处理,进行消息的解析Start();//重新调用监听函数}catch (Exception e){Console.WriteLine(e);//出现异常则退出Close();}} private void OnProcessMessage(RequestCode requestCode,ActionCode actionCode,string data){server.HandleRequest(requestCode, actionCode, data, this);}private void Close(){if (clientSocket != null){clientSocket.Close();}server.RemoveClient(this);}//向客户端发送数据public void Send(ActionCode actionCode,string data){byte[] bytes = Message.PackData(actionCode, data);clientSocket.Send(bytes);}}
}

message类:

using Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Servers
{class Message{private byte[] data = new byte[1024];//用来存储现在的数据,需要足够大private int startIndex = 0;//用来保存当前已经存取的数据位置public byte[] Data{get{return data;}}public int StartIndex{get{return startIndex;}}public int RemainSizs{get{return data.Length - startIndex;}} 更新索引//public void AddCount(int count)//{//    startIndex += count;//}/// <summary>/// 解析数据/// </summary>public void ReadMessage(int newDataAmount,Action<RequestCode,ActionCode,string> processDataCallback){startIndex += newDataAmount;while (true){if (startIndex <= 4) return;int count = BitConverter.ToInt32(data, 0);if (startIndex - 4 >= count){RequestCode requestCode= (RequestCode)BitConverter.ToInt32(data, 4);//得到requestcodeActionCode actionCode = (ActionCode)BitConverter.ToInt32(data,8);//得到actioncodestring s = Encoding.UTF8.GetString(data, 12, count-8);//得到数据processDataCallback(requestCode, actionCode, s);Array.Copy(data, count+4, data, 0, startIndex - 4 - count);startIndex -= count + 4;}else{break;}}}//数据包装public static byte[] PackData(ActionCode actionCode,string data){byte[] requestCodeBytes = BitConverter.GetBytes((int)actionCode);//将actioncode转换成字节数组byte[] dataBytes = Encoding.UTF8.GetBytes(data);//将数据转换成byte数组int dataAmount = requestCodeBytes.Length + dataBytes.Length;//得到数据长度byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount);//将数据长度转换成byte数组return dataAmountBytes.Concat(requestCodeBytes).ToArray().Concat(dataBytes).ToArray();}
}
}

basecontroller类:

using Common;
using GameServer.Servers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Controller
{abstract class BaseController{RequestCode requestCode = RequestCode.None;//设置请求类型 public RequestCode RequestCode{get{return requestCode;}} //默认的处理方法public virtual string DefaultHandle(string data,Client client,Server server){return null;}}
}

controllermanager类:

using Common;
using GameServer.Servers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Controller
{//用来管理controllerclass ControllerManager{private Dictionary<RequestCode, BaseController> controllerDict = new Dictionary<RequestCode, BaseController>();//使用字典存储有哪些controllerprivate Server server;//构造方法 public ControllerManager(Server server){this.server = server;InitController();}//初始化方法void InitController(){DefaultController defaultController = new DefaultController();controllerDict.Add(defaultController.RequestCode,defaultController);}//处理请求public void HandleRequest(RequestCode requestCode,ActionCode actionCode,string data,Client client){BaseController controller;bool isGet = controllerDict.TryGetValue(requestCode, out controller);if (isGet == false){Console.WriteLine("无法得到requestcode:"+requestCode+"所对应的controller,无法处理请求");return;}//通过反射得到string methodName = Enum.GetName(typeof(ActionCode),actionCode);//得到方法名MethodInfo mi= controller.GetType().GetMethod(methodName);//得到方法的信息if (mi == null){Console.WriteLine("[警告]在controller【"+controller.GetType()+"】"+"中没有对应的处理方法【"+methodName+"】");return;}object[] parameters = new object[] { data,client,server};object o= mi.Invoke(controller, parameters);//反射调用方法并将得到返回值//如果返回值为空,则表示没有得到,那么就结束请求的进一步处理if(string.IsNullOrEmpty(o as string)){return;}server.SendResponse(client,actionCode,data);//调用server类中的响应方法,给客户端响应}}
}

defaultcontroller类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Controller
{class DefaultController:BaseController{}
}

说明:首先 创建一个controllermanager类来管理所有的controller,在本类中,我们声明一个字典用来存储所有的controller类,并定义一个初始化方法init来进行初始化存储;之后,我们需要实现请求的分发处理,我们在controllermanager中创建一个handlerequest方法,既然是处理请求,那么就需要我们将需要处理的数据传入,所以方法的参数有requestcode,actioncode,data和client,参数分别表示请求的类型,调用方法的类型,数据内容,处理的客户端;在方法中,首先得到我们需要处理的controller是哪个,我们需要从字典中通过requestcode得到,得到之后,通过反射的方式得到方法名以及方法的信息,再然后,我们通过invoke方法来调用我们上面得到的对应controller中的方法并将相应的参数传递过去,此方法的返回值便是我们之前得到方法的返回值,对返回值进行判断,如果返回值为空则结束请求,完成上述操作后,调用server类中的响应方法对客户端进行响应。

在message类中,我们完成了数据的解析和数据的包装,其中的方法是用来解决粘包分包问题而实现的,根据注释就可以看明白逻辑思路。

客户端消息的接收和响应并不是在controllermanager类与client类中直接联系调用的,而是以server类作为中介,这样做的好处是降低了耦合度,有利于程序的健壮性。

04-数据库连接的创建和关闭

先上代码:

connhelper类:

using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Tool
{class ConnHelper{public const string CONNECTIONSTRING = "datasource=127.0.0.1;port=3306;database=丛林战争;user=root;pwd=root;";public static MySqlConnection Connect(){MySqlConnection conn = new MySqlConnection(CONNECTIONSTRING);try{conn.Open();return conn;}catch (Exception e){Console.WriteLine("连接数据库出现异常:"+e);return null;}}public static void CloseConnection(MySqlConnection conn){if(conn!=null)conn.Close();else{Console.WriteLine("mysqlconnection不能为空");}}}
}

client类:

using Common;
using GameServer.Tool;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace GameServer.Servers
{//用来处理与客户端的通信问题class Client{private Socket clientSocket;private Server server;//持有一个server类的引用private Message msg = new Message();private MySqlConnection mysqlConn;//持有一个对数据库的连接public Client() { }public Client(Socket clientSocket,Server server){this.clientSocket = clientSocket;this.server = server; mysqlConn = ConnHelper.Connect();//建立于数据库的连接}//开启监听public void Start(){clientSocket.BeginReceive(msg.Data,msg.StartIndex, msg.RemainSizs, SocketFlags.None,ReceiveCallBack, null);}//接收监听的回调函数private void ReceiveCallBack(IAsyncResult ar){//做异常捕捉try{int count = clientSocket.EndReceive(ar);//结束监听,并返回接收到的数据长度//如果count=0说明客户端已经断开连接,则直接关闭if (count == 0){Close();}msg.ReadMessage(count,OnProcessMessage);//对消息的处理,进行消息的解析Start();//重新调用监听函数}catch (Exception e){Console.WriteLine(e);//出现异常则退出Close();}}private void OnProcessMessage(RequestCode requestCode,ActionCode actionCode,string data){server.HandleRequest(requestCode, actionCode, data, this);}private void Close(){ ConnHelper.CloseConnection(mysqlConn);if (clientSocket != null){clientSocket.Close();}server.RemoveClient(this);}//向客户端发送数据public void Send(ActionCode actionCode,string data){byte[] bytes = Message.PackData(actionCode, data);clientSocket.Send(bytes);}}
}

说明:connhelper类是实现与数据库的连接功能,在类中,我们首先定义了一个常量的字符串,这个字符串相当于配置信息,里面的每一个字段分别是通过分好隔开的,datasource是数据库所在的ip,port是数据库的端口号,database是数据库的名字,user是数据库的账号,pwd是数据库的密码,在下面分别定义了两个方法,第一个是连接数据库,第二个是关闭与数据库的连接。我们在实现了connhelper类之后,就可以在client类中进行调用了,本着一个客户端有一个数据库连接的原则,我们在每一个client类创建的时候就得到一个数据库的连接,在client关闭的时候就将数据库也关闭掉,这样就完成了对数据库的连接和关闭操作。

unity网络实战开发(丛林战争)-正式开发阶段(013-游戏服务器端框架搭建)相关推荐

  1. 网络游戏《丛林战争》开发与学习之(一):网络编程的基础知识

    <丛林战争>是一款完整的网络游戏案例,运用U3D开发客户端,Socket开发服务端,其中涉及到了网络编程.数据库和Unity的功能实现,之前通过U3D开发了一个单机游戏<黑暗之光&g ...

  2. unity网络实战开发(丛林战争)-正式开发阶段(014-游戏客户端与服务器端连接搭建)

    使用工具:VS2017,unity3d 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 上一篇文章中,我已经把服务器端的框架进行了搭建,接下来, ...

  3. unity网络实战开发(丛林战争)-正式开发阶段(018-声音管理器模块的完善)

    使用工具:VS2017,unity3d 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 上一篇文章中,已经完成了注册事件的处理,接下来将完善声音 ...

  4. unity网络实战开发(丛林战争)-正式开发阶段(016-数据库设计以及登录处理)

    使用工具:VS2017,unity3d 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 上一篇文章中,已经完成了游戏场景与开始界面UI的搭建,接 ...

  5. unity网络实战开发(丛林战争)-前期知识准备(012-UI框架开发)

    使用工具:VS2017,Unity2017.3,DoTween插件 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解 ...

  6. unity网络实战开发(丛林战争)-前期知识准备(011-c#连接数据库并实现增删改查以及sql注入问题)

    使用工具:VS2015,Mysql 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下数据库的前期连接准备以及通过 ...

  7. unity网络实战开发(丛林战争)-前期知识准备(004-开发TCP客户端的接收数据和发送数据)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下客户端的接收数据和发送数据. 首先在现有 ...

  8. unity网络实战开发(丛林战争)-前期知识准备(010-在服务器端解析数据)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下在服务器端解析数据. 首先,同前文类同, ...

  9. unity网络实战开发(丛林战争)-前期知识准备(006-修改服务器端开启异步处理客户端连接请求)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下修改服务器开启一步处理客户端连接请求. ...

最新文章

  1. Android--Handler使用
  2. python的socket编程
  3. Windows phone 8 学习笔记(8) 定位地图导航
  4. Python多进程multiprocessing共享数据Value(包括常用的整数、字符串)、列表及字典以及Queue
  5. Qt / Moc 和信号 - 槽解析
  6. 错误学习:Java + OSGi
  7. 此表单只能填写一次_暴雪战网国服账号修改邮箱只能填写表单申请
  8. 10.Java 面试题整理(CORBA 方面)
  9. mysql子查询的语法,MySQL语法------13-----子查询(三)
  10. 移动应用发展现状_移动应用开发平台:现状和趋势
  11. 珍藏多年的技术资源搜索网站——程序员必备
  12. 计算机动画现状范文网,计算机动画教程
  13. Hive文件存储格式(建表stored as 的五种类型)
  14. ARM与高校联合研制基于机器学习的柔性传感器
  15. 事务四大特征:原子性,一致性,隔离性和持久性(ACID)
  16. html中不显示竖线边框代码,DIV用CSS定义边框为实线,但为什么预览的时候不显示。...
  17. flex 做的小相册+向上滚动字体
  18. 0085 开头的电话拦截方法(小米手机有效)
  19. C#三大迷宫生成算法
  20. 转豆瓣--梁海棠虽然死了,但却占据了陈少杰的身心。乔燕虽然还…

热门文章

  1. 2011年6月20日
  2. 1. CUDA安装失败解决方法
  3. 学校关于扩建计算机房的请示,申请装配机房的请示(模版)
  4. python image库保存图片_python PIL 打开\显示\保存图像
  5. php 长微博程序,一个简单的长微博生成器
  6. android左侧抽屉,Android控件之左侧抽屉菜单
  7. Hadoop实战视频分享_2013年完整版学习视频种子下载
  8. 面向自动驾驶的高精度地图
  9. 关于物联网、智慧消防/安全生产一点点知识理解
  10. excel表格如何转换成word表格_excel转化为word表格怎么做?