本期工作进展
socket通信框架。
controller。
数据库。
具体内容
Server.cs
:
启动Socket服务。并且维护了一个连接上的Client链表。
private List<Client> clientList;//可能哈希表更好一点
void StartAccept()
{
serverSocket.BeginAccept(AcceptCallback, null);
}
void AcceptCallback(IAsyncResult iar)
{
Console.WriteLine("new player connected...");
Socket client = serverSocket.EndAccept(iar);
clientList.Add(new Client(client));//添加到链表
StartAccept();
}
Client.cs
:
代表一个客户端,具有私有变量(socket、message)
class Client
{
private Socket socket;
private Message msg;
public Client(Socket socket)
{
this.socket = socket;
msg = new Message();
SendNoticeAutomatically();
StartReceive();//调用startReceive接收消息
}
void StartReceive()
{
socket.BeginReceive(msg.Buffer, msg.StartIndex, msg.RemainSize, SocketFlags.None, ReceiveCallback, null);
}
void ReceiveCallback(IAsyncResult iar)//回调
{
try
{
Console.WriteLine("收到了socket内容");
if (socket == null || socket.Connected == false) return;
int len = socket.EndReceive(iar);
if(len == 0)//收到信息长度为0则代表对面断开连接
return;
//msg负责消息读取,调用readBuffer读取消息,顺便在内部调用函数解析消息
msg.ReadBuffer(len, this);
StartReceive();//调用startReceive继续接收消息
}
catch(Exception e)
{
Console.WriteLine("收取socket内容时出错:" + e.ToString());
}
}
public void Send(MainPack messagePack)//通过message打包然后发送到客户端
{
socket.Send(msg.ParcelPack(messagePack));
}
void SendNoticeAutomatically()//emm不重要
{
//***********模拟收到了公告请求
MainPack mainPack = new MainPack();
mainPack.RequestCode = RequestCode.NoticeRequest;
mainPack.ActionCode = ActionCode.ReceiveNotice;
Server.controllerManager.HandleRequest(mainPack, this);
}
}
Message.cs
:
负责消息的读取、打包
class Message
{
private byte[] buffer = new byte[1024];//缓存区
private int startindex;//buffer存到了第几位,用于解决粘包问题
public byte[] Buffer
{
get
{
return buffer;
}
}
public int StartIndex
{
get
{
return startindex;
}
}
public int RemainSize
{
get
{
return buffer.Length - startindex;
}
}
public Message()
{
}
//解决粘包机制,详细讲解在前一篇文章
public void ReadBuffer(int len, Client client)
{
startindex += len;
if (startindex <= 4)
return;
int count = BitConverter.ToInt32(Buffer, 0);//读取包头获取消息长度count
while(true)//可能没必要while??
{
if (startindex >= (count + 4))//从0到startindex至少包含一个完整的消息,提取出来并把之后的消息内容复制到最开始防止buffer超界。
{
Console.WriteLine("socket信息够一个包长了,handleRequest");
//这是protocolBuffers提供的函数,可以由字节数组转为实际信息
MainPack msgPack = (MainPack)MainPack.Descriptor.Parser.ParseFrom(Buffer, 4, count);
startindex -= (count + 4);
Array.Copy(buffer, count + 4, buffer, 0, startindex);
//转去处理消息
Server.controllerManager.HandleRequest(msgPack, client);
}
else
{
break;
}
}
}
//打包消息返回字节数组准备通过socket发送
//也涉及粘包机制的问题,别忘了在开头封装上信息长度
public byte[] ParcelPack(MainPack messagePack)
{
byte[] packBytes = messagePack.ToByteArray();
byte[] head = BitConverter.GetBytes(packBytes.Length);
return head.Concat(packBytes).ToArray();
}
}
Controller方面:
接收到消息后会先找到对应的controller。
而具体怎么找则是在Server类里的静态变量controllerManager通过RequestCode映射到我们需要的controller。
ControllerManager.cs
:
class ControllerManager
{
//字典,存储所有Controller
public Dictionary<RequestCode, BaseController> controllerDic = new Dictionary<RequestCode, BaseController>();//此类单例或者静态,通过此类对象访问此数据结构
public ControllerManager()//构造函数,将所有的controller放进dic
{
UserController userController = new UserController();
controllerDic.Add(userController.GetRequestCode, userController);
NoticeController noticeController = new NoticeController();
controllerDic.Add(noticeController.GetRequestCode, noticeController);
RecordController recordController = new RecordController();
controllerDic.Add(recordController.GetRequestCode, recordController);
}
//这里是Message类读取消息后转到这里执行
//通过对pack的requestcode分别调用对应controller的处理方法
public void HandleRequest(MainPack msgPack,Client client)
{
Console.WriteLine("执行HandleRequest找controller");
//通过requestCode获取对应controller
if(controllerDic.TryGetValue(msgPack.RequestCode, out BaseController controller))
{
string methodName = msgPack.ActionCode.ToString();
//通过反射得到方法,方法对应ActionCode
MethodInfo method = controller.GetType().GetMethod(methodName);
if(method == null)
{
Console.WriteLine("method not found!!!!!!");
return;
}
object[] param = new object[] { msgPack };
object result = method.Invoke(controller, param);
if(result != null)//返回数据不为空,发送给客户端
{
client.Send(result as MainPack);
}
}
else
{
Console.WriteLine("controller not found!!!");
return;
}
}
}
具体的controller只演示一个吧:
UserController.cs
:
按照约定,其RequestCode对应UserRequest。
class UserController:BaseController
{
private UserDao userDao;
public UserController()
{
requestCode = RequestCode.UserRequest;
userDao = new UserDao();
}
//注册
public MainPack Register(MainPack messagePack)
{
Console.WriteLine("Usercontroller的注册方法");
bool result = userDao.Register(messagePack.LoginPack.Account, messagePack.LoginPack.Password);
if(result)
{
messagePack.ReturnCode = ReturnCode.Succeed;
}
else
{
messagePack.ReturnCode = ReturnCode.Fail;
}
return messagePack;
}
public MainPack Login(MainPack messagePack)
{
bool result = userDao.Login(messagePack.LoginPack.Account, messagePack.LoginPack.Password);
if (result)
{
messagePack.ReturnCode = ReturnCode.Succeed;
}
else
{
messagePack.ReturnCode = ReturnCode.Fail;
}
return messagePack;
}
//查看个人基本信息
public MainPack CheckMyInfo(MainPack mainPack)
{
mainPack = userDao.CheckMyInfo(mainPack);
return mainPack;
}
//查看我的游戏信息
public MainPack CheckMyGameInfo(MainPack mainPack)
{
mainPack = userDao.CheckMyGameInfo(mainPack);
return mainPack;
}
数据库方面可以通过VS的nuget包管理安装mysql对应的dll。
Dao层:
同样只演示UserDao:
UserDao.cs
:
这种字符串拼接方式的sql指令,对于sql注入问题十分的危险。
建议采取参数化拼接并添加前后端校验。
具体的数据库API这里暂时不提供了,最基础的操作使用都比较简单,并且有jdbc基础的很容易看懂。而且IDE有提示,当时自己没怎么搜教程自己摸索写的结果确实能执行。
class UserDao
{
public UserDao()
{
//account\password\registertime\coins
}
//向数据库发起注册
public bool Register(string account, string password)
{
Console.WriteLine("dao的注册方法");
Conn conn = new Conn();
string sql = "select * from user where `account` = '"+ account + "'";
MySqlCommand mySqlCommand = new MySqlCommand(sql,conn.connection);
MySqlDataReader reader = mySqlCommand.ExecuteReader();
Console.WriteLine("准备执行sql语句");
//检查有没有已经注册
if (!reader.Read())//没有注册,可以注册
{
reader.Close();//!!!
Console.WriteLine("此账号可以注册");
DateTime datetime = DateTime.Now;
sql = "insert into user(`account`,`password`,`registertime`,`coins`) VALUES ('" + account + "','" + password + "','"+ Tools.TimeToString(datetime) +"',0)";
mySqlCommand = new MySqlCommand(sql, conn.connection);
Console.WriteLine("准备插入注册信息");
try
{
if (mySqlCommand.ExecuteNonQuery() == 1)//注册成功
{
Console.WriteLine("插入成功");
conn.CloseConn();
return true;
}
conn.CloseConn();
return false;
}
catch(Exception e)
{
Console.WriteLine(e.Message);
conn.CloseConn();
return false;
}
}
else
{
Console.WriteLine("this account has been registered, please change one");
conn.CloseConn();
return false;
}
}
//登录
public bool Login(string account, string password)
{
Conn conn = new Conn();
string sql = "select * from `socketGame`.`user` where `account` = '" + account + "'";
MySqlCommand mySqlCommand = new MySqlCommand(sql, conn.connection);
MySqlDataReader reader = mySqlCommand.ExecuteReader();
//查询账号
if (!reader.Read())//没有此账号
{
Console.WriteLine("account not exist");
return false;
}
else//存在,对比密码
{
if (reader.GetString("password").Equals(password))
{
conn.CloseConn();
return true;
}
conn.CloseConn();
return false;
}
}
//查个人信息
public MainPack CheckMyInfo(MainPack mainPack)
{
Conn conn = new Conn();
string sql = "select * from user where `account` = '" + mainPack.LoginPack.Account + "'";
MySqlCommand mySqlCommand = new MySqlCommand(sql, conn.connection);
MySqlDataReader reader = mySqlCommand.ExecuteReader();
if (reader.Read())
{
mainPack.LoginPack.RegTime = reader.GetString(2);//0123
mainPack.ReturnCode = ReturnCode.Succeed;
}
else
{
mainPack.ReturnCode = ReturnCode.Fail;
}
conn.CloseConn();
return mainPack;
}
//查个人战绩
public MainPack CheckMyGameInfo(MainPack mainPack)
{
Conn conn = new Conn();
string sql = "select * from gamerecord where `account` = '" + mainPack.LoginPack.Account + "' and `gameID` = '"+ Const.GetGameIntId(mainPack.RecordPack.GameId) +"'";
MySqlCommand mySqlCommand = new MySqlCommand(sql, conn.connection);
MySqlDataReader reader = mySqlCommand.ExecuteReader();
Console.WriteLine("---userDao执行查询个人战绩信息后");
if (reader.Read())
{
Console.WriteLine("读到个人战绩信息");
if(mainPack.RecordPack!=null)
{
mainPack.RecordPack.WinNum = reader.GetInt32(3);
mainPack.RecordPack.AllNum = reader.GetInt32(4);
}
else
{
RecordPack recordPack = new RecordPack();
recordPack.WinNum = reader.GetInt32(3);
recordPack.AllNum = reader.GetInt32(4);
mainPack.RecordPack = recordPack;
}
mainPack.ReturnCode = ReturnCode.Succeed;
}
else
{
mainPack.ReturnCode = ReturnCode.Fail;
}
conn.CloseConn();
return mainPack;
}
}
总结
继上期解决粘包机制后。
本期继续完善protocolBuffer与后端socket通信框架的融合,接收消息后转到controller处理、随后返回给客户端消息。
写好了数据库大致的增删改查。
其中后端校验与try-catch代码块可能存在一点缺陷。等前端对应部分开发完毕后进行测试时完善。