1. 程式人生 > >ToLua熱更新之LuaFramework框架之網路(6)

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的過程