您的位置:首页 > 理论基础 > 计算机网络

直接用Socket TCP开发网络游戏(二)

2017-11-19 21:57 393 查看

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做桥梁: 创建客户端 服务
4000
器也需要改一下代码

客户端代码实现如下:

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进行处理

修改Message类:
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
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);
}
}
}
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,数据的打包和数据的发送到客户端

Message:
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);
}
}
}


请看下集
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐