ToLua热更新之LuaFramework框架之网络(6)

如今大部分的游戏都是网络游戏,网络游戏便涉及到网络连接发起、网络数据接收等内容。LuaFramework内置了网络模块(NetworkManager、SocketClient、ByteBuffer、Converter、Protocal),本篇将会介绍该模块的调用方法以及其原理。

1、发起连接

发起连接是客户端网络通信的第一步,LuaFramewor中,只需通过LuaFramework.AppConst.SocketAddress和LuaFramework.AppConst.SocketPort设置ip和端口,然后调用NetworkManager的SendConnect方法即可发起连接。Main.lua的代码如下:

require "Network"
 
--主入口函数。从这里开始lua逻辑
function Main()		
	local LuaHelper = LuaFramework.LuaHelper
	local networkMgr = LuaHelper.GetNetManager()
	local AppConst = LuaFramework.AppConst
	
    AppConst.SocketPort = 1234;
    AppConst.SocketAddress = "127.0.0.1";
	networkMgr:SendConnect();
end

在收到服务端回应后,LuaFramework会调用Network的OnSocket方法(写死)。新建名为Network.lua的文件,处理消息回调。在如下的代码中,Protocal代表协议号,比如“连接服务器”(Protocal.Connect)的协议号是101,在OnSocket的参数中,key便是收到的协议号,data是收到的数据。

Network = {};
 
--协议
Protocal = {
	Connect		= '101';	--连接服务器
	Exception   = '102';	--异常掉线
	Disconnect  = '103';	--正常断线   
	Message		= '104';	--接收消息
}
 
--Socket消息--
function Network.OnSocket(key, data)
	if key == 101 then
		LuaFramework.Util.Log('OnSocket Connect');		
	else
		LuaFramework.Util.Log('OnSocket Other');	
	end
end

为了测试网络功能,需要编写服务端,这里使用c#编写一套简单的服务端程序,仅为调试使用,代码如下:

using System;
using System.Net;
using System.Net.Sockets;
using System.Linq;
 
class MainClass
{
	public static void Main(string[] args)
	{
		Console.WriteLine("Hello World!");
		//Socket
		Socket listenfd = new Socket(AddressFamily.InterNetwork,
		                             SocketType.Stream, ProtocolType.Tcp);
		//Bind
		IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
		IPEndPoint ipEp = new IPEndPoint(ipAdr, 1234);
		listenfd.Bind(ipEp);
		//Listen
		listenfd.Listen(0);
		Console.WriteLine("[服务器]启动成功");
		while (true)
		{
			//Accept
			Socket connfd = listenfd.Accept();
			Console.WriteLine("[服务器]Accept");
		}
	}
}

运行服务端和客户端,客户端会发起连接,服务端accept该连接后回应,客户端会显示“OnSocket Connect”

图:服务端

图:客户端

此时把服务端关掉(断开连接),客户端会收到协议号为102的消息,即异常掉线(Exception)。

图:异常掉线

调用NetworkManager.SendConnect实际是调用BeginConnect发起连接。连接之后,回调OnConnect方法。

图:连接过程

OnConnect方法调用NetworkManager.AddEvent,排除设计模式的内容,相当于调用Network.lua的OnSocket方法。传入OnSocket的第1个参数为101(Protocal.Connect),指代协议名,第2个参数是空的字节流。网络模块中定义了101、102、103这3个固定的协议号,分别代表连接服务器、异常断线和正常断线。

 

图:连接回调

2、发送和接收

接下来尝试发送和接收数据。LuaFramework默认(如果不去改它的代码)使用的协议格式如下图所示,前面的2个字节为消息长度,用于处理沾包分包,随后的2个字节代表协议号(如上面的101、102、103),最后才是消息的内容。

图:协议

修改Network.lua,在连接成功后(OnSocket方法的101协议),调用send发送一串协议号为104的数据。服务端收到数据后回射给客户端,客户端在收到回应后(OnSocket方法的104协议),读取并显示出来。

send方法中新建了一个buffer,然后往buffer中添加协议号(104)和协议内容(字符串:《Unity3D网络游戏实战》是一本好书!),最后调用networkMgr:SendMessage()发送数据。networkMgr:SendMessage()会自动计算协议长度,并附加到buffer上发送出去。

--Socket消息--
function Network.OnSocket(key, data)
	if key == 101 then
		LuaFramework.Util.Log('OnSocket Connect');	
		Send()
	elseif key == 104 then
		LuaFramework.Util.Log('OnSocket Message ');
		local str = data:ReadString();
		LuaFramework.Util.Log('收到的字符串:'..str);
	else
		LuaFramework.Util.Log('OnSocket Other '..key);	
	end
end
 
function Send()
	--组装数据
    local buffer = LuaFramework.ByteBuffer.New();
    buffer:WriteShort(Protocal.Message);
    buffer:WriteString("《Unity3D网络游戏实战》是一本好书!");
	--发送
	local LuaHelper = LuaFramework.LuaHelper
	local networkMgr = LuaHelper.GetNetManager()
    networkMgr:SendMessage(buffer);
	LuaFramework.Util.Log('数据发送完毕');	
end

修改服务端程序,读出接收到的内容,并echo回去。

	public static void Main(string[] args)
	{
		略,没有改动
		while (true)
		{
			//Accept
			Socket connfd = listenfd.Accept();
			Console.WriteLine("[服务器]Accept");
			//Recv 不考虑各种意外,只做测试
			byte[] readBuff = new byte[100];
			int count = connfd.Receive(readBuff);
			//显示字节流
			string showStr = "";
			for (int i = 0; i < count; i++)
			{
				int b = (int)readBuff[i];
				showStr += b.ToString() + " ";
			}
			Console.WriteLine("[服务器接收]字节流:"+ showStr);
			//解析协议
			Int16 messageLen = BitConverter.ToInt16(readBuff,0);
			Int16 protocal = BitConverter.ToInt16(readBuff,2);
			Int16 strLen = BitConverter.ToInt16(readBuff,4);
			string str = System.Text.Encoding.UTF8.GetString(readBuff, 6, strLen);
			Console.WriteLine("[服务器接收] 长度:" + messageLen);
			Console.WriteLine("[服务器接收] 协议号:" + protocal);
			Console.WriteLine("[服务器接收] 字符串:" + str);
			//Send(echo)
			byte[] writeBuff = new byte[count];
			Array.Copy(readBuff,writeBuff,count);
			connfd.Send(writeBuff);
		}
	}

运行游戏,可以看到服务端收到的如图所示的信息。字节流的前两位“53 0”表示消息长度为53字节,紧跟着的“104 0”代表协议号104。在字符串的封装中(buffer:WriteString),程序会先在buffer中添加字符串的长度,最后才是字符串的内容。“49 0”即表示“《Unity3D网络游戏实战》是一本好书!”占用49个字节(14个中文符号,每个3字节,7个英文符号,每个1字节)。协议长度53字节 = 协议号2个字节 + 字符串长度2字节 + 字符串内容49字节。

图:服务端收到的信息

客户端收到服务端回射的消息后,也会显示出来,如下图所示。

图:客户端收到的消息

在lua中调用networkMgr:SendMessage(buffer)时,实际上相当于调用了SocketClient的WriteMessage方法,该方法会计算协议的长度,然后将长度和内容组装在一起,调用BeginWrite发送数据。

图:发送数据

在建立连接后,SocketClient会调用BeginRead,当收到服务端的消息时,回调OnRead方法。OnRead又调用了OnReceive方法。

图:接收数据过程

OnReceive方法完成沾包分包处理,然后调用AddEvent方法分发消息(相当于调用了lua中NetWork表的OnSocket方法)。

图:解析数据过程

关于BeginRead、BeginConnect等方法的介绍,读者可以查看c#网络编程的资料或参照《Unity3D网络游戏实战》第6章“网络基础”。

 

3、消息分发

一款游戏往往涉及很多条网络通信协议,在Network.OnSocket中,如果只用ifelse语句处理不同协议,代码往往会混乱不堪。LuaFramework集成了消息分发的方法,用法如下所示。

1、引用LuaFramework\Lua\events.lua,然后使用Event.AddListener添加监听,例如“Event.AddListener(Protocal.Connect, Network.OnConnect); ”表示当收到101协议(Protocal.Connect)时,回调Network.OnConnect方法。Main.lua代码如下:

require "Network"
Event = require 'events'
 
--主入口函数。从这里开始lua逻辑
function Main()		
	local LuaHelper = LuaFramework.LuaHelper
	local networkMgr = LuaHelper.GetNetManager()
	local AppConst = LuaFramework.AppConst
	
    AppConst.SocketPort = 1234;
    AppConst.SocketAddress = "127.0.0.1";
	
	Event.AddListener(Protocal.Connect, Network.OnConnect); 
    Event.AddListener(Protocal.Message, Network.OnMessage); 
	
	networkMgr:SendConnect();
end

2、在需要分发消息的地方调用Event.Brocast,然后编写相应的回调函数。Network.lua的部分代码如下:

--Socket消息--
function Network.OnSocket(key, data)
	LuaFramework.Util.Log('OnSocket 消息分发:'..key);
	Event.Brocast(tostring(key), data);
end
 
function Network.OnConnect(data) 
    LuaFramework.Util.Log('Network.OnConnect');	
	Send()
end
 
function Network.OnMessage(data) 
    LuaFramework.Util.Log('Network.OnMessage');
	local str = data:ReadString();
	LuaFramework.Util.Log('收到的字符串:'..str);
end

运行游戏,可以看到消息分发的结果。

图:消息分发

调用Event.AddListener,实际上是在一个表中添加数据,把某个协议号对应于某个方法的信息记录起来。

图:AddListener的过程

当调用Event.Brocast时,程序会查找这份表,然后执行回调方法。这里使用了协程来调用回调函数。使用协程的目的应该是不让回调逻辑阻碍主体逻辑,然而由于协程是单线程的,这点不起作用。除非回调函数也使用协程,相互配合。所以这里应该可以不用协程的。

图:Brocast的过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值