【服务端】单线程服务端框架

一、架构消息处理流程

  1. 服务端开启监听,利用多路复用select(epoll)监听客户端请求。
  2. select(epoll)收到socket消息,如果触发的socketfd是监听socket,那么新创建一个state(每个state对应一个客户端),并加入stateList中。如果是已建立了通信的socket客户端发来消息,然后解析消息,根据消息取出协议,根据协议通过反射获取协议对应的处理函数,调用处理函数。
  3. 在协议处理函数中处理完逻辑后,一般会Send()消息会给客户端。

二、游戏序列化

服务端程序会有两处功能涉及类的序列化。

  1. 与客户端信息交互(send、receive)需要编码和和解码
  2. 玩家数据保存(序列化成bytes保存到数据库)

三、框架模块

分为4个部分:

  1. 处理select(epoll)多路复用的网络管理器NetManager,它是服务端网络模块的核心部件
  2. 定义客户端信息的ClientState类。每一个客户端连接对应一个ClientState对象,含有与客户端连接的套接字socket和读缓冲区readBuff。
  3. 处理网络消息的MsgHandle类。
  4. 事件处理类EventHandler。
程序入口
using System;

namespace Game
{
	class MainClass
	{
		public static void Main(string[] args)
		{
			NetManager.StartLoop(8888);
		}
	}
}
1.NetManager
public static void StartLoop(int listenPort)
{
	//Socket
	listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
	//Bind
	IPAddress ipAdr = IPAddress.Parse("0.0.0.0");
	IPEndPoint ipEp = new IPEndPoint(ipAdr, listenPort);
	listenfd.Bind(ipEp);
	//Listen
	listenfd.Listen(5);	
	Console.WriteLine("[服务器]启动成功");
	//循环
	while (true) 
	{
		//重置CheckRead
		ResetCheckRead();
		//Select
		Socket.Select(checkRead, null, null, 1000);	
		//检查可读对象
		for (int i = checkRead.Count -1; i >= 0; i--) {
			Socket s = checkRead[i];
			if (s == listenfd) {
				ReadListenfd(s);
			}
			else {
				ReadClientfd(s);
			}
		}	
	}
}
//填充checkRead列表
public static void ResetCheckRead() {
	checkRead.Clear();
	checkRead.Add(listenfd);
	foreach(ClientState s in clients.Values) {
		checkRead.Add(s.socket);
	}
}
1.2 处理监听事件方法

ReadListenfd会调用Accept接收客户端连接,然后新建一个客户端信息对象state,把它存入客户端信息列表clients。

public static void ReadListenfd(Socket listenfd) 
{
	try {
		Socket clientfd = listenfd.Accept();
		Console.WriteLine("Accept " + clientfd.RemoteEndPoint.ToString());
		state.socket = clientfd;
		clients.Add(clientfd, state);
	}
	catch (SocketException ex) {
		Console.WriteLine("Accept fail" + ex.ToString());
	}
}
1.3 处理客户端消息
public static void ReadClientfd(Socket clientfd) 
{
	ClientState state = clients[clientfd];
	ByteArray readBuff = state.readBuff;
	//接收
	int count  = 0;
	//缓冲区不够,清除,若依然不够,只能返回
	//缓冲区长度只有1024,单条协议超过缓冲区长度时会发生错误,根据需要调整长度
	if (readBuff.remain <= 0) {
		OnReceiveData(state);
		readBuff.MoveBytes();
	}
	if (readBuff.remain <= 0) {
		Console.WriteLine("Receive fail, maybe msg length > buff capacity");
		Close(state);
		return;
	}

	try {
		//接收消息
		count = clientfd.Receive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0);
	}
	catch (SocketException ex) {
		Console.WriteLine("Receive SocketException " + ex.ToString());
		Close(state);
		return;
	}
	
	//客户端关闭
	if (count <= 0) {
		Console.WriteLine("Socket Close " + clientfd.RemoteEndPoint.ToString());
		Close(state);
		return;
	}
	
	//消息处理
	readBuff.wirteIdx += count;
	//处理二进制消息
	OnReceiveData(state);
	//移动缓冲区
	readBuff.CheckAndMoveBytes(); 
}
1.4.处理协议

它会先判断读缓冲区的数据是否足够长,如果条件满足则调用MsgBase.DecodeName和MsgBase.Decode解析出协议名和协议体。最后做消息分发(利用反射获取MsgHandler类中对应的处理方法),即调用MsgHandler类名为protoName(例如:MsgHandler::MsgMove、MsgHandler::MsgPing)的方法。

//处理二进制消息
public static void OnReceiveData(ClientState state) 
{
	ByteArray readBuff = state.readBuff;
	byte[] bytes = readBuff.bytes;
	//消息长度
	if (readBuff.length < 2) {
		return;
	}
	Int16 bodyLength = (Int16)((bytes[readIdx+1] << 8) | bytes[readIdx]);
	//消息体
	if (readBuff.length < bodyLength) {
		return;
	}
	readBuff.readIdx += 2;
	//解析协议名
	int nameCount = 0;
	string protoName = MsgBase.DecodeName(readbuff.bytes, readBuff.readIdx, out nameCount);
	if (protoName == "") {
		Console.WriteLine("OnReceiveData MsgBase.DecodeName fail");	
		Close(state);
	}
	readBuff.readIdx += nameCount;
	//解析协议体
	int bodyCount = bodyLength - nameCount;
	MsgBase msgBase = MsgBase.Decode(protoName, readBuff.bytes, readBuff.readIdx, bodyCount);
	readBuff.readIdx += bodyCount;
	readBuff.CheckAndMoveBytes();
	//分发消息
	MethodInfo mi = typeof(MsgHandler).GetMethod(protoName);	//MethodInfo类对象mi包含它所指代的方法的所有信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用它。假设所有消息处理方法都定义在MsgHandler类中,且都是静态方法,通过typeof(MsgHandler).GetMethod(funName)便能够获取MsgHandler类中的名为funName的静态方法。由于MethodInfo定义于System.Reflection命名空间下,因此需要引用(using)该命名空间。
	object[] o = {state, msgBase}; 
	Console.WriteLine("Receive: " + protoName);
	if (mi != null) {
		mi.Invoke(null, o);	//mi.Invoke(null, o)代表调用mi所包含的方法。第一个参数null代表this指针,由于消息处理方法都是静态方法,因此此处要填null。第二个参数o代表的是参数列表。这里定义的消息处理函数都有两个参数,第一个参数是客户端状态state,第二个参数是消息的内容msgArgs。
	}
	else {
		Console.WriteLine("OnReceiveData Invoke fail " + protoName);
	}
	//继续读取消息
	if (readBuff.length > 2) {
		OnReceiveData(state);
	}
}
2.MsgHandler

C#中partical修饰的类,表明类是局部类型,它允许我们将一个类、结构或接口分成几个部分,分别实现在几个不同的.cs文件中。

BattleMsgHandler.cs

public partical class MsgHandler {
	public static void MsgMove(ClientState c, MsgBase msgBase) {
		MsgMove msgMove = (MsgMove)msgBase;
		Console.WriteLine(msgMove.x);
		msgMove.x++;
		NetManager.Send(c, msgMove);
	}
}

SysMsgHandler.cs

public partical class MsgHandler {
	public static void MsgPing(ClientState c, MsgBase msgBase) {
		Console.WriteLine("MsgPing");
		c.lastPingTime = NetManager.GetTimeStamp();
		MsgPong msgPong = new MsgPong();
		NetManager.Send(c, msgPong);
	}
}
3.EventHandler
using System;

public partial class EventHandler
{
	public static void OnDisconnect(ClientState c)
	{
		Console.WriteLine("Close");
	}

	public static void OnTimer() {}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值