直接用Socket TCP开发网络游戏(二)
来源:互联网 发布:wow猎人 知乎 编辑:程序博客网 时间:2024/06/05 14:06
01,如何解决SQL的注入问题
用这种方法的话就不会出现那种恶意输入的情况,代码实现如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using MySql.Data.MySqlClient;namespace MySql数据库操作{ class Program { static void Main(string[] args) { string connStr = "Database=test007;Data Source=127.0.0.1;port=3306;User Id=root;Password=root"; MySqlConnection conn = new MySqlConnection(connStr); conn.Open(); #region 查询 #region 读取多条 //MySqlCommand cmd = new MySqlCommand("select * from user ", conn); #endregion #region 读取一条 // MySqlCommand cmd = new MySqlCommand("select * from user where id = 1", conn); #endregion //MySqlDataReader reader = cmd.ExecuteReader(); #region 读取一条 // if (reader.HasRows) //得到一个值指示是否MySqlDataReader包含一个或多个行。 // { //reader.Read(); //表示读取一条消息,多次读取多次调用 //string username = reader.GetString("username"); //string passwore = reader.GetString("password"); //Console.WriteLine(username + " " + passwore); // } #endregion #region 读取多条 //while (reader.Read()) //{ // string username = reader.GetString("username"); // string passwore = reader.GetString("password"); // Console.WriteLine(username + " " + passwore); //} #endregion //reader.Close(); #endregion #region 插入 string username = "zain"; string password = "lcker; delete form user"; ///用户组拼 有bug 用户可以恶意输入sql语句 下节课再解决 MySqlCommand cmd = new MySqlCommand("insert into user set username=@un, password=@pwd", conn); cmd.Parameters.AddWithValue("un", username); //添加参数 。。根据字符查找 cmd中的 字符 并替换成vaule cmd.Parameters.AddWithValue("pwd", password); cmd.ExecuteNonQuery();//执行插入 并返回插入的行数 #endregion conn.Close(); Console.ReadKey(); } }}
02,数据库数据的更新和删除
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using MySql.Data.MySqlClient;namespace MySql数据库操作{ class Program { static void Main(string[] args) { string connStr = "Database=test007;Data Source=127.0.0.1;port=3306;User Id=root;Password=root"; MySqlConnection conn = new MySqlConnection(connStr); conn.Open(); #region 查询 #region 读取多条 //MySqlCommand cmd = new MySqlCommand("select * from user ", conn); #endregion #region 读取一条 // MySqlCommand cmd = new MySqlCommand("select * from user where id = 1", conn); #endregion //MySqlDataReader reader = cmd.ExecuteReader(); #region 读取一条 // if (reader.HasRows) //得到一个值指示是否MySqlDataReader包含一个或多个行。 // { //reader.Read(); //表示读取一条消息,多次读取多次调用 //string username = reader.GetString("username"); //string passwore = reader.GetString("password"); //Console.WriteLine(username + " " + passwore); // } #endregion #region 读取多条 //while (reader.Read()) //{ // string username = reader.GetString("username"); // string passwore = reader.GetString("password"); // Console.WriteLine(username + " " + passwore); //} #endregion //reader.Close(); #endregion #region 插入 //string username = "zain"; string password = "lcker; delete form user"; ///用户组拼 有bug 用户可以恶意输入sql语句 下节课再解决 //MySqlCommand cmd = new MySqlCommand("insert into user set username=@un, password=@pwd", conn); //cmd.Parameters.AddWithValue("un", username); //根据字符查找 cmd中的 字符 并替换成vaule //cmd.Parameters.AddWithValue("pwd", password); //cmd.ExecuteNonQuery();//执行插入 并返回插入的行数 ////Console.WriteLine(cmd.ExecuteNonQuery()); #endregion #region 删除 //MySqlCommand cmd = new MySqlCommand("delete from user where id =@id", conn); //cmd.Parameters.AddWithValue("id", 2); //cmd.ExecuteNonQuery(); //执行 #endregion #region 更新 MySqlCommand cmd = new MySqlCommand("update user set password=@pwd where id=3", conn); cmd.Parameters.AddWithValue("pwd", "a"); cmd.ExecuteNonQuery(); //执行 #endregion conn.Close(); Console.ReadKey(); } }}
03,服务器端分成架构
Server : 用来创建Socket 监听客户端的连接
ConnHelper: 工具类 用来连接数据库 建立MySqlConnection
Controller: 处理客户端的请求,客户端的请求发送到server端 ,server会调用相应的controller进行处理
Model: 是和数据库中的表是对应着的 一个Model类对应一个数据库表
DAO : 用来操作数据库的
04,学习小提示和项目的目录结构的创建
架构的话大概就是这样
05,创建Server类 开启接收客户端的连接
代码如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;namespace GameServer.Server{ class Server { private IPEndPoint ipEndPoint; private Socket serverSocket; public Server() { } public Server(string IpStr, int port) { SetIpAndPort(IpStr, port); } 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); serverSocket.Listen(0); serverSocket.BeginAccept(AcceptCallBack, null); } public void AcceptCallBack(IAsyncResult ar) { Socket clientSocket = serverSocket.EndAccept(ar); } }}
06,创建Client类 处理跟客户端的数据通信
准备用message做桥梁: 创建客户端 服务器也需要改一下代码
客户端代码实现如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;namespace GameServer.Server{ class Client { private Socket clientSocket; private Server server; public Client() { } public Client(Socket clientSocket, Server server) { this.clientSocket = clientSocket; this.server = server; } public void Start() { clientSocket.BeginReceive(null, 0, 0,SocketFlags.None,AsyncCallback,null); } public void AsyncCallback(IAsyncResult ar){ try { int count = clientSocket.EndReceive(ar); if (count == 0) { Close(); } clientSocket.BeginReceive(null, 0, 0, SocketFlags.None, AsyncCallback, null); //TODO 处理接收到的数据 } catch (Exception e) { Console.WriteLine(e); Close(); } } private void Close() { if (clientSocket!=null) clientSocket.Close(); server.RemoveClient(this); } }}
服务器代码如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;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); } 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); serverSocket.Listen(0); serverSocket.BeginAccept(AcceptCallBack, null); } public void AcceptCallBack(IAsyncResult ar) { Socket clientSocket = serverSocket.EndAccept(ar); Client client = new Client(clientSocket,this); client.Start(); clientList.Add(client);//添加到list集合 } public void RemoveClient(Client client) { lock (clientList) //锁定移除 { clientList.Remove(client); } } }}
07,创建Message处理客户端的消息解析;
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]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了 //如果消息不完整 我们不处理。我们会读取下一条 private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10; //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。 /// <summary> /// 这个数组用来存取我们读取到的数据 /// </summary> public byte[] Data { get { return data; } } /// <summary> /// 代表存储了多少个字节的数据 开始索引 /// </summary> public int StartIndex { get { return startIndex; } } /// <summary> /// 剩余的空间 /// </summary> public int RemaniSize { get { return data.Length - startIndex; } } /// <summary> /// 更新了多少数据 /// </summary> /// <param name="count"></param> //public void AddCount(int count) //{ // startIndex += count; //} /// <summary> /// 解析数据或者叫做读取数据 newDataAmount数量 /// </summary> public void ReadMessage(int newDataAmount) { startIndex += newDataAmount; while (true) { if (startIndex <= 4) return; int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节 ,变成int 就是数据长度 if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的 { string s = Encoding.UTF8.GetString(data, 4, count); Console.WriteLine("解析出来一条数据 :" + s); Array.Copy(data, count + 4, data, 0, startIndex - 4 - count); startIndex -= (count + 4); //移动完了之后更新startIndex } else { break; } } } }}
Client类代码稍微做了修改:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;namespace GameServer.Server{ class Client { private Socket clientSocket; private Server server; private Message messgae = new Message(); public Client() { } public Client(Socket clientSocket, Server server) { this.clientSocket = clientSocket; this.server = server; } public void Start() { clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null); } public void AsyncCallback(IAsyncResult ar){ try { int count = clientSocket.EndReceive(ar); if (count == 0) { Close(); } messgae.ReadMessage(count); Start(); //TODO 处理接收到的数据 } catch (Exception e) { Console.WriteLine(e); Close(); } } private void Close() { if (clientSocket!=null) clientSocket.Close(); server.RemoveClient(this); } }}
服务器类没做变化:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;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); } 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); serverSocket.Listen(0); serverSocket.BeginAccept(AcceptCallBack, null); } public void AcceptCallBack(IAsyncResult ar) { Socket clientSocket = serverSocket.EndAccept(ar); // Client client = new Client(clientSocket,this); client.Start(); clientList.Add(client); //添加到list集合 } public void RemoveClient(Client client) { lock (clientList) //锁定移除 { clientList.Remove(client); } } }}
08,开发Controller控制层
代码如下:
然后 再新建一个类库 Common 新建两个类 每个类都改为枚举类型 分别为ActionCode,RequesCode。
using System;using System.Collections.Generic;using System.Text;namespace Common{ public enum ActionCode { None, }}
using System;using System.Collections.Generic;using System.Text;namespace Common{ public enum RequesCode { None, }}
在Controller文件夹下创建一个新的类 抽象类 BaseController 把刚才的类库添加引用到BaseController,和命名空间
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;namespace GameServer.Controller{ abstract class BaseController { RequesCode requesCode = RequesCode.None; public virtual void DefaultHandle() { } }}
09,客户端和服务器端的请求发起处理流程
10,创建ControllerMessager管理所有的控制器
Clinet 直接和controllerManager直接打交道的话 耦合性太高,所以要通过中介(Server)来打交道;ControllerManager只让Server使用,其他都跟server交互;server就相当于一个中介
服务器端要持有一个ControllerMessager
ControllerManager 代码:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;namespace GameServer.Controller{ /// <summary> /// 用来管理当前服务器端有哪些COntroller /// </summary> class ControllerManager { private Dictionary<RequesCode, BaseController> controllerDic = new Dictionary<RequesCode, BaseController>(); public ControllerManager() { Init(); } /// <summary> /// 初始化所有的Controller /// </summary> void Init() { } }}
Server服务器代码:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;using GameServer.Controller;namespace GameServer.Server{ class Server { private IPEndPoint ipEndPoint; private Socket serverSocket; private List<Client> clientList; private ControllerManager controllerManager = new ControllerManager(); public Server() { } public Server(string IpStr, int port) { SetIpAndPort(IpStr, port); } 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); serverSocket.Listen(0); serverSocket.BeginAccept(AcceptCallBack, null); } public void AcceptCallBack(IAsyncResult ar) { Socket clientSocket = serverSocket.EndAccept(ar); // Client client = new Client(clientSocket,this); client.Start(); clientList.Add(client); //添加到list集合 } public void RemoveClient(Client client) { lock (clientList) //锁定移除 { clientList.Remove(client); } } }}
11,通过ControllerManager进行请求的分发处理
先完善BaseController :
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;namespace GameServer.Controller{ abstract class BaseController { RequesCode requesCode = RequesCode.None; public RequesCode RequestCode { get { return requesCode; } } /// <summary> /// 用来处理默认的请求 当我们没有指定ActionCode的时候会执行 /// </summary>data 是发送的数据 public virtual string DefaultHandle(string data) { return null; } }}
修改ControllerManager的代码:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;using System.Reflection;namespace GameServer.Controller{ /// <summary> /// 用来管理当前服务器端有哪些COntroller /// </summary> class ControllerManager { private Dictionary<RequesCode, BaseController> controllerDic = new Dictionary<RequesCode, BaseController>(); public ControllerManager() { Init(); } /// <summary> /// 初始化所有的Controller /// </summary> void Init() { //TODO DefaultController defaultController = new DefaultController(); controllerDic.Add(defaultController.RequestCode, defaultController); } /// <summary> /// 处理请求 /// </summary> 通过requesCode 找到controller ,然后再actionCode 找到里面的方法 public void HandelRequset(RequesCode requesCode,ActionCode actionCode,string data) { BaseController controller; bool isGet= controllerDic.TryGetValue(requesCode, out controller); if (isGet==false) { Console.WriteLine("无法得到"+requesCode+"所对应的Controller,无法处理请求");return; } string methodName = Enum.GetName(typeof(ActionCode), actionCode); MethodInfo mi = controller.GetType().GetMethod(methodName); if (mi==null) { Console.WriteLine("[警告]在controller]"+controller.GetType()+"]中没有对应的处理方法"); return; } object[] parameters = new object[] {data }; ///o是是否要给客户端相应 object o= mi.Invoke(controller, parameters); //调用controller方法 带data参数 } }}
在controller文件夹新建DefaultController类:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace GameServer.Controller{ class DefaultController:BaseController // 当RequesCode没有指定的时候调用 { }}
结构图如下图
12,客户端请求相应的处理:
修改Server类 实例化出来一个ControllerManager:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;using GameServer.Controller;using Common;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); } 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); serverSocket.Listen(0); serverSocket.BeginAccept(AcceptCallBack, null); } public void AcceptCallBack(IAsyncResult ar) { Socket clientSocket = serverSocket.EndAccept(ar); // Client client = new Client(clientSocket,this); client.Start(); clientList.Add(client); //添加到list集合 } public void RemoveClient(Client client) { lock (clientList) //锁定移除 { clientList.Remove(client); } } /// <summary> /// 给Clinet客户端发起相应 /// </summary> /// <param name="clinet"></param> /// <param name="requestCode"></param> /// <param name="data"></param> public void SentResponse(Client clinet,RequesCode requestCode,string data) { //TODO给客户端相应 } }}
Message类不做任何变化:
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]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了 //如果消息不完整 我们不处理。我们会读取下一条 private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10; //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。 /// <summary> /// 这个数组用来存取我们读取到的数据 /// </summary> public byte[] Data { get { return data; } } /// <summary> /// 代表存储了多少个字节的数据 开始索引 /// </summary> public int StartIndex { get { return startIndex; } } /// <summary> /// 剩余的空间 /// </summary> public int RemaniSize { get { return data.Length - startIndex; } } /// <summary> /// 更新了多少数据 /// </summary> /// <param name="count"></param> //public void AddCount(int count) //{ // startIndex += count; //} /// <summary> /// 解析数据或者叫做读取数据 newDataAmount数量 /// </summary> public void ReadMessage(int newDataAmount) { startIndex += newDataAmount; while (true) { if (startIndex <= 4) return; int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节 ,变成int 就是数据长度 if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的 { string s = Encoding.UTF8.GetString(data, 4, count); Console.WriteLine("解析出来一条数据 :" + s); Array.Copy(data, count + 4, data, 0, startIndex - 4 - count); startIndex -= (count + 4); //移动完了之后更新startIndex } else { break; } } } }}
client类不做变化:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;namespace GameServer.Servers{ class Client { private Socket clientSocket; private Server server; private Message messgae = new Message(); public Client() { } public Client(Socket clientSocket, Server server) { this.clientSocket = clientSocket; this.server = server; } public void Start() { clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null); } public void AsyncCallback(IAsyncResult ar){ try { int count = clientSocket.EndReceive(ar); if (count == 0) { Close(); } messgae.ReadMessage(count); Start(); //TODO 处理接收到的数据 } catch (Exception e) { Console.WriteLine(e); Close(); } } private void Close() { if (clientSocket!=null) clientSocket.Close(); server.RemoveClient(this); } }}
DefaultController类没变化:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace GameServer.Controller{ class DefaultController:BaseController // 当RequesCode没有指定的时候调用 { }}
ControllerManager类变化:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;using System.Reflection;using GameServer.Servers;namespace GameServer.Controller{ /// <summary> /// 用来管理当前服务器端有哪些COntroller /// </summary> class ControllerManager { private Dictionary<RequesCode, BaseController> controllerDic = new Dictionary<RequesCode, BaseController>(); private Server server; public ControllerManager(Server server) { this.server = server; InitController(); } /// <summary> /// 初始化所有的Controller /// </summary> void InitController() { //TODO DefaultController defaultController = new DefaultController(); controllerDic.Add(defaultController.RequestCode, defaultController); } /// <summary> /// 处理请求 /// </summary> 通过requesCode 找到controller ,然后再actionCode 找到里面的方法 public void HandelRequset(RequesCode requesCode,ActionCode actionCode,string data ,Client client) { BaseController controller; bool isGet= controllerDic.TryGetValue(requesCode, out controller); if (isGet==false) { Console.WriteLine("无法得到"+requesCode+"所对应的Controller,无法处理请求");return; } string methodName = Enum.GetName(typeof(ActionCode), actionCode); MethodInfo mi = controller.GetType().GetMethod(methodName); if (mi==null) { Console.WriteLine("[警告]在controller]"+controller.GetType()+"]中没有对应的处理方法"); return; } object[] parameters = new object[] {data,client,server }; ///o是是否要给客户端相应 object o= mi.Invoke(controller, parameters); //调用controller方法 带data参数 if (o==null||string.IsNullOrEmpty(o as string)) { return; } server.SentResponse(client, requesCode, o as string); } }}
BaseController类也做了一些修改:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;using GameServer.Servers;namespace GameServer.Controller{ abstract class BaseController { RequesCode requesCode = RequesCode.None; public RequesCode RequestCode { get { return requesCode; } } /// <summary> /// 用来处理默认的请求 当我们没有指定ActionCode的时候会执行 /// </summary>data 是发送的数据 public virtual string DefaultHandle(string data,Client clinet ,Server server) { return null; } }}
Common类库也没变化 :
using System;using System.Collections.Generic;using System.Text;namespace Common{ public enum RequesCode { None, }}
using System;using System.Collections.Generic;using System.Text;namespace Common{ public enum ActionCode { None, }}
13,如何把客户端消息的解析和传递给ControllerManager进行处理
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;namespace GameServer.Servers{ class Message { private byte[] data = new byte[1024]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了 //如果消息不完整 我们不处理。我们会读取下一条 private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10; //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。 /// <summary> /// 这个数组用来存取我们读取到的数据 /// </summary> public byte[] Data { get { return data; } } /// <summary> /// 代表存储了多少个字节的数据 开始索引 /// </summary> public int StartIndex { get { return startIndex; } } /// <summary> /// 剩余的空间 /// </summary> public int RemaniSize { get { return data.Length - startIndex; } } /// <summary> /// 更新了多少数据 /// </summary> /// <param name="count"></param> //public void AddCount(int count) //{ // startIndex += count; //} /// <summary> /// 解析数据或者叫做读取数据 newDataAmount数量 /// </summary> public void ReadMessage(int newDataAmount,Action<RequesCode,ActionCode,string> processDataCallback) { startIndex += newDataAmount; while (true) { if (startIndex <= 4) return; int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节 ,变成int 就是数据长度 if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的 { //string s = Encoding.UTF8.GetString(data, 4, count); //Console.WriteLine("解析出来一条数据 :" + s); RequesCode requestCode=(RequesCode) BitConverter.ToInt32(data, 4); ///解析出来的是ResourceCode ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 8); ///解析出来的是ActionCode string 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); //移动完了之后更新startIndex } else { break; } } } }}通过委托回调给Clinet,client回调给Server,server再回调给ControllerManager
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;using Common;namespace GameServer.Servers{ class Client { private Socket clientSocket; private Server server; private Message messgae = new Message(); public Client() { } public Client(Socket clientSocket, Server server) { this.clientSocket = clientSocket; this.server = server; } public void Start() { clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null); } public void AsyncCallback(IAsyncResult ar){ try { int count = clientSocket.EndReceive(ar); if (count == 0) { Close(); } messgae.ReadMessage(count,OnProcessMessage); Start(); //TODO 处理接收到的数据 } catch (Exception e) { Console.WriteLine(e); Close(); } } private void Close() { if (clientSocket!=null) clientSocket.Close(); server.RemoveClient(this); } private void OnProcessMessage(RequesCode requesCode, ActionCode actionCode, string data) { server.HandleRequest(requesCode, actionCode, data, this); } }}Server:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;using GameServer.Controller;using Common;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); } 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); serverSocket.Listen(0); serverSocket.BeginAccept(AcceptCallBack, null); } public void AcceptCallBack(IAsyncResult ar) { Socket clientSocket = serverSocket.EndAccept(ar); // Client client = new Client(clientSocket,this); client.Start(); clientList.Add(client); //添加到list集合 } public void RemoveClient(Client client) { lock (clientList) //锁定移除 { clientList.Remove(client); } } /// <summary> /// 给Clinet客户端发起相应 /// </summary> /// <param name="clinet"></param> /// <param name="requestCode"></param> /// <param name="data"></param> public void SentResponse(Client clinet,RequesCode requestCode,string data) { //TODO给客户端相应 } /// <summary> /// Manager跟server交互 server跟Clinet交互 /// </summary> /// <param name="requesCode"></param> /// <param name="actionCode"></param> /// <param name="data"></param> /// <param name="client"></param> public void HandleRequest(RequesCode requesCode, ActionCode actionCode, string data, Client client) { controllerManager.HandelRequset(requesCode, actionCode, data, client); } }}
14,数据的打包和数据的发送到客户端
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using Common;namespace GameServer.Servers{ class Message { private byte[] data = new byte[1024]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了 //如果消息不完整 我们不处理。我们会读取下一条 private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10; //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。 /// <summary> /// 这个数组用来存取我们读取到的数据 /// </summary> public byte[] Data { get { return data; } } /// <summary> /// 代表存储了多少个字节的数据 开始索引 /// </summary> public int StartIndex { get { return startIndex; } } /// <summary> /// 剩余的空间 /// </summary> public int RemaniSize { get { return data.Length - startIndex; } } /// <summary> /// 更新了多少数据 /// </summary> /// <param name="count"></param> //public void AddCount(int count) //{ // startIndex += count; //} /// <summary> /// 解析数据或者叫做读取数据 newDataAmount数量 /// </summary> public void ReadMessage(int newDataAmount,Action<RequesCode,ActionCode,string> processDataCallback) { startIndex += newDataAmount; while (true) { if (startIndex <= 4) return; int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节 ,变成int 就是数据长度 if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的 { //string s = Encoding.UTF8.GetString(data, 4, count); //Console.WriteLine("解析出来一条数据 :" + s); RequesCode requestCode=(RequesCode) BitConverter.ToInt32(data, 4); ///解析出来的是ResourceCode ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 8); ///解析出来的是ActionCode string 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); //移动完了之后更新startIndex } else { break; } } } /// <summary> /// 用于组合包装 /// </summary> /// <param name="requsetData"></param> /// <param name="data"></param> /// <returns></returns> public static byte[] PakeDaa(RequesCode requsetData,string data) { byte[] requsetCodeBytes = BitConverter.GetBytes((int)requsetData); byte[] dataBytes = Encoding.UTF8.GetBytes(data); int dataAmount = requsetCodeBytes.Length + dataBytes.Length; byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount); return dataAmountBytes.Concat(requsetCodeBytes).Concat(dataBytes).ToArray(); } }}
Clinet 类:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;using Common;namespace GameServer.Servers{ class Client { private Socket clientSocket; private Server server; private Message messgae = new Message(); public Client() { } public Client(Socket clientSocket, Server server) { this.clientSocket = clientSocket; this.server = server; } public void Start() { clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null); } public void AsyncCallback(IAsyncResult ar){ try { int count = clientSocket.EndReceive(ar); if (count == 0) { Close(); } messgae.ReadMessage(count,OnProcessMessage); Start(); //TODO 处理接收到的数据 } catch (Exception e) { Console.WriteLine(e); Close(); } } private void Close() { if (clientSocket!=null) clientSocket.Close(); server.RemoveClient(this); } private void OnProcessMessage(RequesCode requesCode, ActionCode actionCode, string data) { server.HandleRequest(requesCode, actionCode, data, this); } public void Send(RequesCode requsetCode,string data) { byte[] bytes = Message.PakeDaa(requsetCode, data); clientSocket.Send(bytes); } }}
Server类:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;using GameServer.Controller;using Common;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); } 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); serverSocket.Listen(0); serverSocket.BeginAccept(AcceptCallBack, null); } public void AcceptCallBack(IAsyncResult ar) { Socket clientSocket = serverSocket.EndAccept(ar); // Client client = new Client(clientSocket,this); client.Start(); clientList.Add(client); //添加到list集合 } public void RemoveClient(Client client) { lock (clientList) //锁定移除 { clientList.Remove(client); } } /// <summary> /// 给Clinet客户端发起相应 /// </summary> /// <param name="clinet"></param> /// <param name="requestCode"></param> /// <param name="data"></param> public void SentResponse(Client clinet,RequesCode requestCode,string data) { //TODO给客户端相应 clinet.Send(requestCode, data); } /// <summary> /// Manager跟server交互 server跟Clinet交互 /// </summary> /// <param name="requesCode"></param> /// <param name="actionCode"></param> /// <param name="data"></param> /// <param name="client"></param> public void HandleRequest(RequesCode requesCode, ActionCode actionCode, string data, Client client) { controllerManager.HandelRequset(requesCode, actionCode, data, client); } }}
15,创建ConnHelper,数据库连接的创建和关闭。
然后门在Tool文件夹下新建ConnHelper类:
并且在mysql数据库中创建一个game01
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using MySql.Data.MySqlClient;namespace GameServer.Tool{ /// <summary> /// 用来建立跟服务端的连接 /// </summary> class ConnHelper { public const string CONNECTIONDTRING = "dataSource=127.0.0.1;port=3306;database=game01;user=root;password=root"; public static MySqlConnection Connect() { MySqlConnection conn = new MySqlConnection(CONNECTIONDTRING); 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 System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net.Sockets;using System.Net;using Common;using MySql.Data.MySqlClient;using GameServer.Tool;namespace GameServer.Servers{ class Client { private Socket clientSocket; private Server server; private Message messgae = 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(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null); } public void AsyncCallback(IAsyncResult ar){ try { int count = clientSocket.EndReceive(ar); if (count == 0) { Close(); } messgae.ReadMessage(count,OnProcessMessage); Start(); //TODO 处理接收到的数据 } catch (Exception e) { Console.WriteLine(e); Close(); } } private void Close() { ConnHelper.CloseConnection(mysqlConn); if (clientSocket!=null) clientSocket.Close(); server.RemoveClient(this); } /// <summary> /// 消息的解析 /// </summary> /// <param name="requesCode"></param> /// <param name="actionCode"></param> /// <param name="data"></param> private void OnProcessMessage(RequesCode requesCode, ActionCode actionCode, string data) { server.HandleRequest(requesCode, actionCode, data, this); } /// <summary> /// 消息的发送 /// </summary> /// <param name="requsetCode"></param> /// <param name="data"></param> public void Send(RequesCode requsetCode,string data) { byte[] bytes = Message.PakeDaa(requsetCode, data); clientSocket.Send(bytes); } }}
12,导入开发好的UI框架和目录框架:
资源 :链接: https://pan.baidu.com/s/1hrSJUmS密码: xe7b
新建Unity3D工程 ; SkUIFramework
13,导入游戏素材
导入资源 Res
14 ,游戏客户端和架构分析
架构图如下:
Access文件夹下新建Scripts文件夹 下面创建GameFacade 然后新建一个Manager文件夹 ,创建BaseManager, 然后修改UIManager,新建 CameraManager AudioManager,PlayerManager,RequestManager ClientManager,都继承自BaseManager 。
BaseManager类如下、;
using System.Collections;using System.Collections.Generic;using UnityEngine;public class BaseManager { /// <summary> /// 生命的开始 /// </summary> public virtual void OnInit() { } /// <summary> /// 生命的结束 /// </summary> public virtual void OnDestory() { }}
然后在GameFacade 类:
using System.Collections;using System.Collections.Generic;using UnityEngine;public class GameFacade : MonoBehaviour { private UIManager uiMng; private AudioManager audioMng; private PlayerManager playerMng; private CameraManager cameraMng; private RequestManager requesMng; private ClientManager clientMng;void Start () { InitManager(); }void Update () {} private void InitManager() { uiMng = new UIManager(); audioMng = new AudioManager(); playerMng = new PlayerManager(); cameraMng = new CameraManager(); requesMng = new RequestManager(); clientMng = new ClientManager(); uiMng.OnInit(); audioMng.OnInit(); playerMng.OnInit(); cameraMng.OnInit(); requesMng.OnInit(); clientMng.OnInit(); } private void DestoryManager() { uiMng.OnDestory(); audioMng.OnDestory(); playerMng.OnDestory(); cameraMng.OnDestory(); requesMng.OnDestory(); clientMng.OnDestory(); } private void OnDestory() { DestoryManager(); }}
13,开发ClinetManager,跟服务器端的连接和关闭
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Net.Sockets;using System.Net;using UnityEngine;/// <summary>/// 这个是用来管理跟服务器端的Socket连接/// </summary>public class ClientManager:BaseManager{ public const string IP = "127.0.0.1"; public const int PORT = 6688; private Socket clinetSocket; public override void OnInit() { base.OnInit(); clinetSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { clinetSocket.Connect(IP, PORT); } catch (Exception e) { Debug.LogWarning("无法连接到服务器端请检查你的网络"+e); } } public override void OnDestory() { base.OnDestory(); try { clinetSocket.Close(); } catch (Exception e) { Debug.LogWarning("无法关闭跟服务端的连接" + e); } }}
请看下集
- 直接用Socket TCP开发网络游戏(二)
- 直接用Socket TCP开发网络游戏(一)
- 直接用Socket TCP开发网络游戏(三)
- 网络游戏开发(二)
- socket 直接的通信tcp
- 基于TCP的网络游戏黑白棋系列(二):数据传输
- 用 Unity 进行网络游戏开发
- QT TCP socket通信(二)
- cocos2d-x 开发网络游戏(http post&socket)
- Amf3+socket开发网络游戏或应用的一点研究心得
- cocos2d-x 开发网络游戏(http post&socket)
- Amf3+socket开发网络游戏或应用的一点研究心得
- cocos2d-x 开发网络游戏(http post&socket)
- 网络游戏开发
- QT tcp Socket 通信开发
- QT tcp Socket 通信开发
- QT tcp Socket 通信开发
- Unity3d网络游戏Socket通讯
- 图形用户界面 作业2 事件
- hihocoder1580-Matrix
- Mac下测试顺序线性表
- dlib库+vs2017详细配置流程
- PostgreSQL入门(一)——世界上最先进的开源数据库
- 直接用Socket TCP开发网络游戏(二)
- 唐御智能销售管家
- Java中的策略模式-电商支付案例
- Hadoop2.6版本的FSImage结构解析
- 数字签名与数字证书
- 关键字static、super与this
- 蠢萌的小机器人
- Hibernate
- windows环境下安装spark