日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

tcp unity 图片_用 Unity 做个游戏(七) - TCP Socket 客户端

發布時間:2023/12/4 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 tcp unity 图片_用 Unity 做个游戏(七) - TCP Socket 客户端 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

這真的是最后一篇有關基礎框架的文章了!

寫到這里已經第七篇了orz之前的其實還是挺枯燥的,都是些基礎方面的東西,并看不到什么有趣的內容

可能是我把事情想的太復雜了吧,所有東西都想做到能力范圍內的最好,尤其是這些底層框架層次的東西

不過這些東西真的很重要,小游戲的話可能不會明顯,Unity的一大優勢便在于可以快速地產出游戲原型來,我這個項目整了這么久就一個TestView,里面居然只有兩個按鈕!233

我這些東西也是考慮了許多生產環境中遇到過的問題,不敢說是最優,我也還是在學習嘛XD

嘛,等把網絡框架也搭起來,我們就能正式開始寫游戲相關的邏輯啦~

網絡通信

我們這游戲是個多人在線實時對戰的游戲,之前的坑里就是網絡這塊給搞崩了,重新來設計

網絡這塊使用原生TCP Socket進行通訊,自定協議。這里主要先介紹客戶端,先把協議定下來,這樣之后介紹服務端的時候就不會和客戶端有太大耦合了。當然一開始的話還是先弄一個最簡單的服務端,本項目計劃使用Node.js開發

最簡單的服務端

在某個端口上創建一個TCP服務器,接收客戶端傳來的消息,拼接一個字符串后返回。

代碼如下:

const net = require("net");

net.createServer(function(socket){

console.log("有新的連接:" + socket.remoteAddress);

socket.on("data", function(data){

console.log("request: " + data);

socket.write('{"pid":1,"retCode":0}');

});

socket.on("end", function(data){

console.log("socket end");

});

socket.on("close", function(data){

console.log("連接已斷開");

});

socket.write("Hello!");

}).listen(19621);復制代碼

客戶端設計

用兩個類,一個相對底層的SFTcpClient,用戶不直接使用這個類,而是通過SFNetworkManager加一層封裝,這是個單例類,可以在游戲運行過程中隨時訪問網絡,還有就是為了以后可能不僅僅使用一個SfTcpClient,封裝之后可以更優雅地管理多個TCP客戶端。

SFNetworkManager

先看SFNetworkManager,管理著若干個SFTcpClient,通過后者的以下接口:

|方法|說明|

|--|--|

|void init(string, int, SFClientCallback, SFSocketStateCallback)|根據指定的IP地址,端口以及相關回調初始化|

|void uninit()|關閉TCP客戶端|

|void sendData(string)|往服務器發送數據|

|bool isReady|服務器是否就緒|

首先是作為一個單例類應該有的內容:私有的構造函數,唯一的實例,獲取實例的方法:

private SFNetworkManager(){}

private static sm_instance = null;

public static SFNetworkManager getInstance(){

if (null == sm_instance)

{

sm_instance = new SFNetworkManager();

}

return sm_instance;

}復制代碼

然后是連接初始化

public void init(){

m_client = new SFTcpClient();

m_client.init("127.0.0.1", 19621, onRecvMsg, ret =>

{

dispatcher.dispatchEvent(SFEvent.EVENT_NETWORK_READY, new SFSimpleEventData(ret));

});

// 向上傳遞連接斷開的事件

m_client.dispatcher.addEventListener(SFEvent.EVENT_NETWORK_INTERRUPTED, e =>

{

dispatcher.dispatchEvent(e);

});

}復制代碼

void onRecvMsg(string)是處理服務端推送消息的回調函數

void onRecvMsg(string msg){

SFUtils.log("收到了" + msg);

}復制代碼

現在問題來了,因為消息回調函數是在Socket子線程里調用的,Unity里不允許在子線程中對場景中的物體進行修改,所以要稍加改造,讓這些消息在主線程中處理。

用一個隊列,子線程中收到的消息全加入到這個隊列,把內容存在內存里,然后主線程通過update函數定期檢查隊列中是否還有未處理的信息,有的話就全部取出來處理。

void onRecvMsg(string msg){

m_recvQueue.Enqueue(msg);

}

void update(){

while (m_recvQueue.Count > 0)

{

string data = m_recvQueue.Dequeue();

SFUtils.log("收到了" + data);

}

}復制代碼

SFTcpClient

使用C# TCP Socket的異步實現。數據收發的子線程由系統管理。

所有的方法都有對應的一對BeginXX和EndXX,以接收數據為例:

try

{

if (!m_socket.Connected)

{

throw new Exception("Socket is not connected");

}

byte[] data = new byte[1024]; // 以1024字節為單位接收數據

m_socket.BeginReceive(data, 0, data.Length, SocketFlags.None, result =>

{

int length = m_socket.EndReceive(result); // length為實際接收到的數據長度

if (length > 0)

{

m_callback(Encoding.UTF8.GetString(data)); // 轉換為字符串并調用回調

}

else

{

// length為0說明網絡已斷開

m_socket.close();

SFUtils.logWarning("網絡連接中斷");

dispatcher.dispatchEvent(SFEvent.EVENT_NETWORK_INTERRUPTED);

}

}, null);

}

catch (Exception e)

{

SFUtils.logWarning("網絡連接中斷:" + e.Message);

}復制代碼

其他像是連接,發送都大同小異,具體的完整代碼可以查看文章末尾的完整代碼鏈接。

自定協議

數據的首發暫時就先這樣(當然有很多坑,比如因為我使用原生TCP Socket來傳輸數據包,數據多的時候必然會產生粘包的情況,所以必須手動分包,這個之后再說,和接下來的內容關系不大,要加的話直接在SFTcpClient里的sendData()方法和socketRecv()方法里修改就是了)

網絡中傳輸的數據使用JSON字符串,發送和接收的時候客戶端和服務端分別各自進行序列化和反序列化,這里先只討論客戶端的實現。

Unity提供了一個JsonUtility類,有了這個類我們就能方便地進行對象和JSON之間的序列化和反序列化了。主要使用的是兩個方法:

|方法名|作用|

|--|--|

|string JsonUtility.ToJson(object)|把一個對象轉化成JSON字符串|

|T JsonUtility.FromJson(string)|把一個JSON字符串轉化成指定類型的對象,如果出錯則拋出異常|

請求

請求類型均繼承自基類SFBaseRequestMessage,舉一個例子:

// 基類

public class SFBaseRequestMessage

{

public int pid; // 協議號

public string uid; // 用戶唯一ID

};

// 用戶登陸登出

[Serializable]

public class SFRequestMsgUnitLogin : SFBaseRequestMessage

{

public SFRequestMsgUnitLogin(){ pid = 1; }

public int loginOrOut;

};復制代碼

響應

響應類型是類似的,每個請求類型一定對應一個響應類型,但反過來卻不一定,即一個協議擁有請求類型是擁有響應類型的必要非充分條件。

同樣是上面那個登陸的協議:

// 基類

public class SFBaseResponseMessage : ISFEventData // 為了讓響應結果也可以方便地作為事件數據傳遞

{

public int pid; // 協議號

public int retCode; // 錯誤代碼,0表示成功

};

// 用戶登陸登出

[Serializable]

public class SFResponseMsgUnitLogin : SFBaseResponseMessage

{

public const string pName = "socket_1"; // pName作為SFEvent的事件名稱

public SFReponseMsgUnitLogin(){ pid = 1; }

};復制代碼

發送和接收

發送非常簡單,創建一個sendMessage()方法,接收參數類型為請求基類SFBaseRequestMessage,先序列化然后直接丟給TCP Client來處理發送即可。

public void sendMessage(SFBaseRequestMessage req){

string data = JsonUtility.ToJson(req);

m_client.sendData(data);

}復制代碼

接收稍微復雜點兒,分兩步,首先把原始字符串轉成SFBaseResponseMessage,獲取其協議號pid,然后根據不同的pid再轉成具體的響應類型。

SFBaseResponse obj = null;

obj = JsonUtility.FromJson(data);

if (obj == null)

{

SFUtils.logWarning("不能解析的信息格式:\n" + data);

}

else

{

int pid = obj.pid;

string pName = string.Format("socket_{0}", pid);

if (pid == 1)

{

obj = JsonUtility.FromJson(data)

}

// else if 更多協議

else

{

SFUtils.logWarning("不能識別的協議號: {0}", 0, pid);

obj = null;

}

if (obj != null)

{

dispatcher.dispatchEvent(pName, obj);

}

}復制代碼

然后在其他地方添加相應協議的監聽即可:

SFNetworkManager.getInstance().dispatcher.addEventListener(SFResponseMsgUnitLogin.pName, onRecvMsg);復制代碼

回調函數一定在主線程中被調用,所以可以在里面放心地修改游戲場景。

測試程序

創建一個這樣的UI0701

點擊連接服務器的按鈕,嘗試連接服務器:

m_mgr = SFNetworkManager.getInstance();

m_mgr.init();

m_mgr.dispatcher.addEventListener(SFEvent.EVENT_NETWORK_READY, result =>

{

SFSimpleEventData retCode = result.data as SFSimpleEventData;

if (retCode.intVal == 0)

{

m_infoMsg = "服務器連接成功";

}

else

{

m_infoMsg = "服務器連接失敗";

}

});

m_mgr.dispatcher.addEventListener(SFEvent.EVENT_NETWORK_INTERRUPTED, onInterrupt);

m_mgr.dispatcher.addEventListener(SFResponseMsgUnitLogin.pName, onRecvMsg);復制代碼

在此之前啟動服務端程序的話就會成功連接至服務器。0702

同時會收到來自服務端的消息"Hello!",當然這個不符合我們的協議,console面板可以看到程序無法解析這個字符串,并忽略。

然后點擊發送消息按鈕,客戶端程序會發送一個測試協議給服務端,服務端就會收到:

$ node ./

started

有新的連接:::ffff:127.0.0.1

request: {"pid":1,"uid":"abc","loginOrOut":1}復制代碼

此時服務端返回字符串'{"pid":1,"retCode":0}',這就是一個標準的協議信息了,程序解析后發現這是一個登陸成功的響應,做出處理:0703

然后按Ctrl+C強制關閉服務端程序進程,網絡中斷,客戶端也有對應的處理0704

完整代碼

上面貼出的代碼片段由于篇幅限制只保留了關鍵部分,完整的代碼可在我的github上找到

總結

以上是生活随笔為你收集整理的tcp unity 图片_用 Unity 做个游戏(七) - TCP Socket 客户端的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。