最近看了下關於unity的網絡模塊netwrok即將淘汰,思索了下準備自己用socket去封裝個局域網的通訊插件
看了一下關於同步問題
https://www.jianshu.com/p/fbd8eda9df62
然後局域網遊戲,外掛基本上靠的是玩家自覺,而且也不需要自己搞個服務器
單機聯機的點
-
房間系統:房主是服務器也是客戶端
-
隨機數的生成保持一至:統一的隨機種子(並且要保持種子的調用次數一至)
-
連接後玩家操作通訊只發送操作指令
-
每隔一段時間進行狀態同步(針對每個需要同步的組件數據寫一個組件)
實現步驟:
1、實現連接與通訊
2、製作根據業務需求的封裝
首先第一步:實現連接與通訊
該組件能做最基礎的房間系統和聊天室,稍微封裝可以滿足各種需求
當然胡來也是會有bug的,因爲沒有真正去用過
組件GameNetWorkManager作爲Unity訪問的核心類
//連接相關API
ServerDataList:可以獲取局域網內的所有服務器數據
CreateServer:傳進服務器名字和最大連接數,可以創建一個服務器
ConnectServer:傳進服務器數據,可以連接服務器
//通訊相關API
OnReceive(事件):綁定該事件後,服務器接受到消息時將獲取該消息,即收到消息時回調
SendData:發送給服務器的內的所有客戶端
代碼:
一共兩個文件
GameNetWorkManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameNetWorkManager : MonoBehaviour
{
private GameClient gameClient;
private ServerData serverData;
private GameServer gameServer;
private ServerDataList serverDataList;
public ServerData ServerData { get => serverData; }
public GameClient GameClient { get => gameClient; }
public GameServer GameServer { get => gameServer; }
public ServerDataList ServerDataList { get => serverDataList; }
/// <summary>
/// 當收到服務器消息時
/// </summary>
public event System.Action<string> OnReceive;
/// <summary>
/// 創建服務器
/// </summary>
/// <param name="serverName"></param>
/// <param name="maxNumber"></param>
public void CreateServer(string serverName, int maxNumber)
{
serverData = new ServerData(serverName, maxNumber);
gameServer = new GameServer(serverData);
gameClient.ConnectServer(serverData, (ServerData ServerData) =>
{
//初始化隨機種子
Random.InitState(ServerData.randomSeed);
},
(string message) =>
{
OnReceive(message);
});
}
/// <summary>
/// 連接服務器
/// </summary>
/// <param name="serverData"></param>
public void ConnectServer(ServerData serverData)
{
gameClient.ConnectServer(serverData, (ServerData ServerData) =>
{
//初始化隨機種子
Random.InitState(ServerData.randomSeed);
},
(string message) =>
{
OnReceive(message);
});
}
public void SendData(string message)
{
gameClient.SendMessageData(message);
}
private void Awake()
{
//創建客戶端
gameClient = new GameClient();
//掃描服務器(即收到廣播就進行數據更新)
GameClient.CheckOutServerList((ServerDataList serverDataList) =>
{
this.serverDataList = serverDataList;
});
}
private void OnDestroy()
{
if (gameClient != null)
{
gameClient.Close();
}
if (gameServer != null)
{
gameServer.Close();
}
}
}
GameNetWork.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
[System.Serializable]
public class ServerDataList
{
public List<ServerData> list = new List<ServerData>();
internal void Add(ServerData serverData)
{
list.Add(serverData);
}
internal void Remove(ServerData serverData)
{
list.RemoveAll((ServerData item) =>
{
return serverData.IP == item.IP && serverData.port == item.port;
});
}
internal void Update(ServerData serverData)
{
foreach (var item in list)
{
if (serverData.IP == item.IP && serverData.port == item.port)
{
item.name = serverData.name;
item.number = serverData.number;
item.maxNumber = serverData.maxNumber;
}
}
}
}
[System.Serializable]
public class ServerData
{
public string name;
public int number;
public int maxNumber;
public string IP;
public int port;
/// <summary>
/// 隨機種子
/// </summary>
public int randomSeed;
public ServerData()
{
randomSeed = new System. Random().Next(0, 99999);
}
public ServerData(string name, int maxNumber)
{
this.name = name;
this.maxNumber = maxNumber;
randomSeed = new System.Random().Next(0, 99999);
}
}
/// <summary>
/// 遊戲數據包
/// </summary>
[System.Serializable]
public struct GameNetPacket
{
public string name;
public string data;
}
public interface IGameNetWorkBase
{
void Close();
}
public interface IGameServer : IGameNetWorkBase
{
}
public interface IGameClient : IGameNetWorkBase
{
void CheckOutServerList(Action<ServerDataList> OnCheckOut);
void ConnectServer(ServerData serverData, Action<ServerData> OnConnect, Action<string> OnReceive);
void SendMessageData(string data);
}
/// <summary>
/// 遊戲服務器
/// </summary>
public class GameServer : IGameServer
{
public ServerData serverData;
/// <summary>
/// 服務器套接字
/// </summary>
Socket ServerSocket;
/// <summary>
/// 客戶端的套接字
/// </summary>
List<Socket> ClientSockets = new List<Socket>();
const int minPort = 2725;
const int maxPort = 2730;
const int radiateMinPort = 2730;
const int radiateMaxPort = 2735;
private bool open;
/// <summary>
/// 循環客戶端的信息
/// </summary>
/// <param name="socket"></param>
private void LoopClientMessage(Socket socket)
{
ClientSockets.Add(socket);
//監聽客戶端發送信息,並且進行廣播
new Thread(() =>
{
while (open)
{
try
{
byte[] receive = new byte[1024];
//等待獲取信息
socket.Receive(receive);
//進行廣播發送
foreach (var item in ClientSockets)
{
item.Send(receive);
}
}
catch (Exception)
{
Debug.Log("斷開連接...");
Close();
}
}
}).Start();
}
public GameServer(ServerData serverData)
{
this.serverData = serverData;
open = true;
//服務器初始化
// 偵聽所有網絡客戶接口的客活動
//使用指定的地址簇協議、套接字類型和通信協議
ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
BindServerSocket(minPort);
//設定最多玩家數量
ServerSocket.Listen(this.serverData.maxNumber);
// string ipStr = ipAddress.ToString();
Debug.Log("服務器初始化完成");
SendServerDataToAll("UpdateServer");
//接受廣播信息
Socket receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint sep = BindReceiverSocket(receiveSocket, radiateMinPort);
EndPoint ep = (EndPoint)sep;
//接收廣播線程
new Thread(() =>
{
while (open)
{
byte[] message = new byte[1024];//設置緩衝數據流
receiveSocket.ReceiveFrom(message, ref ep);//接收數據,並確把數據設置到緩衝流裏面
string data = Encoding.Unicode.GetString(message).TrimEnd('\u0000');
GameNetPacket receiveData = JsonUtility.FromJson<GameNetPacket>(data);
switch (receiveData.name)
{
case "Is Room?":
GameNetPacket gameNetPacket = new GameNetPacket();
gameNetPacket.name = "NewServer";
gameNetPacket.data = JsonUtility.ToJson(serverData);
SendReceiveTo(JsonUtility.ToJson(gameNetPacket));
break;
default:
break;
}
}
}).Start();
//監聽客戶端連接線程
new Thread(() =>
{
while (open)
{
try
{
//與客戶的排隊建立連接(堵塞)
Socket socket = ServerSocket.Accept();
LoopClientMessage(socket);
Debug.Log("連接成功");
////進行廣播更新房間信息
SendServerDataToAll("UpdateServer");
}
catch (Exception)
{
Debug.Log("停止等待客戶端連接");
open = false;
}
}
}).Start();
}
private void SendServerDataToAll(string receiveType)
{
GameNetPacket gameNetPacket = new GameNetPacket();
gameNetPacket.name = receiveType;
gameNetPacket.data = JsonUtility.ToJson(serverData);
SendReceiveTo(JsonUtility.ToJson(gameNetPacket));
}
private static void SendReceiveTo(string data)
{
//發送廣播等待傳輸回來房間信息
//初始化一個發送廣播和指定端口的網絡端口實例
Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//設置該scoket實例的發送形式
sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
for (int i = radiateMinPort; i < radiateMaxPort; i++)
{
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, i);
byte[] buffer = Encoding.Unicode.GetBytes(data);
//發送
sendSocket.SendTo(buffer, iep);
}
}
private static IPEndPoint BindReceiverSocket(Socket receiveSocket, int port)
{
IPEndPoint sep = null;
try
{
sep = new IPEndPoint(IPAddress.Any, port);//初始化一個偵聽局域網內部所有IP和指定端口
receiveSocket.Bind(sep);//綁定這個實例
}
catch (Exception)
{
port++;
if (port < radiateMaxPort)
{
BindReceiverSocket(receiveSocket, port);
}
else
{
throw;
}
}
return sep;
}
private void BindServerSocket(int port)
{
try
{
IPEndPoint IPEndPoint = new IPEndPoint(IPAddress.Any, port);
//綁定IP地址和端口號
ServerSocket.Bind(IPEndPoint);
string hostName = Dns.GetHostName(); //獲取本機名
IPAddress[] localhosts = Dns.GetHostAddresses(hostName);
foreach (var item in localhosts)
{
if (item.ToString().IndexOf("192.168") != -1)
{
serverData.IP = item.ToString();
break;
}
}
serverData.port = port;
}
catch (Exception)
{
port++;
if (port < maxPort)
{
BindServerSocket(port);
}
else
{
throw;
}
}
}
public void Close()
{
open = false;
//發送關閉房間信號
GameNetPacket gameNetPacket = new GameNetPacket();
gameNetPacket.name = "CloseServer";
gameNetPacket.data = JsonUtility.ToJson(serverData);
SendReceiveTo(JsonUtility.ToJson(gameNetPacket));
//關閉服務器socket
ServerSocket.Close();
//關閉所有客戶端的socket
foreach (var item in ClientSockets)
{
item.Close();
}
}
}
/// <summary>
/// 客戶端 封裝部分
/// </summary>
public partial class GameClient
{
/// <summary>
/// 廣播端口
/// </summary>
const int receiveMinPort = 2730;
const int receiveMaxPort = 2735;
/// <summary>
/// 廣播接收socket
/// </summary>
private Socket radiateSocket;
/// <summary>
/// 客戶端的socket
/// </summary>
private Socket clientSocket;
private EndPoint ep;
private bool openCheckOutServer = true;
/// <summary>
/// 廣播發送一條信息
/// </summary>
/// <param name="data"></param>
private static void SendReceiveTo(string data)
{
//發送廣播等待傳輸回來房間信息
//初始化一個發送廣播和指定端口的網絡端口實例
Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//設置該scoket實例的發送形式
sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
for (int i = receiveMinPort; i < receiveMaxPort; i++)
{
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, i);
byte[] buffer = Encoding.Unicode.GetBytes(data);
//發送
sendSocket.SendTo(buffer, iep);
}
}
private void CloseCheckOutServer()
{
openCheckOutServer = false;
radiateSocket.Close();
radiateSocket = null;
}
private static IPEndPoint BindReceiverSocket(Socket receiveSocket, int port)
{
IPEndPoint sep = null;
try
{
sep = new IPEndPoint(IPAddress.Any, port);//初始化一個偵聽局域網內部所有IP和指定端口
receiveSocket.Bind(sep);//綁定這個實例
}
catch (Exception)
{
port++;
if (port < receiveMaxPort)
{
BindReceiverSocket(receiveSocket, port);
}
else
{
throw;
}
}
return sep;
}
}
/// <summary>
/// 客戶端 接口實現部分
/// </summary>
public partial class GameClient : IGameClient
{
private bool connecting;
public GameClient()
{
radiateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//初始化一個Scoket協議
IPEndPoint sep = BindReceiverSocket(radiateSocket, receiveMinPort);//初始化一個偵聽局域網內部所有IP和指定端口
ep = (EndPoint)sep;//綁定這個實例
}
/// <summary>
/// 查看服務器列表
/// </summary>
/// <param name="OnCheckOut"></param>
public void CheckOutServerList(Action<ServerDataList> OnCheckOut)
{
openCheckOutServer = true;
Debug.Log("查找房間");
//廣播發送一個數據
SendReceiveTo(JsonUtility.ToJson(new GameNetPacket() { name = "Is Room?", data = "" }));
ServerDataList serverDataList = new ServerDataList();
new Thread(() =>
{
while (openCheckOutServer)
{
try
{
byte[] message = new byte[1024];//設置緩衝數據流
radiateSocket.ReceiveFrom(message, ref ep);//接收數據,並確把數據設置到緩衝流裏面
string json = Encoding.Unicode.GetString(message).TrimEnd('\u0000');
GameNetPacket gameNetPacket = JsonUtility.FromJson<GameNetPacket>(json);
switch (gameNetPacket.name)
{
case "NewServer":
serverDataList.Add(JsonUtility.FromJson<ServerData>(gameNetPacket.data));
break;
case "CloseServer":
serverDataList.Remove(JsonUtility.FromJson<ServerData>(gameNetPacket.data));
break;
case "UpdateServer":
serverDataList.Update(JsonUtility.FromJson<ServerData>(gameNetPacket.data));
break;
default:
break;
}
OnCheckOut(serverDataList);
}
catch (Exception)
{
throw;
}
}
}).Start();
}
/// <summary>
/// 關閉客戶端
/// </summary>
public void Close()
{
if (openCheckOutServer)
{
CloseCheckOutServer();
}
if (connecting)
{
CloseConnecting();
}
}
private void CloseConnecting()
{
connecting = false;
clientSocket.Close();
}
public void SendMessageData(string data)
{
if (clientSocket == null)
{
Debug.Log("未連接服務器");
return;
}
byte[] message = Encoding.UTF8.GetBytes(data); //通信時實際發送的是字節數組,所以要將發送消息轉換字節
clientSocket.Send(message);
}
/// <summary>
/// 連接至服務器
/// </summary>
/// <param name="serverData"></param>
/// <param name="OnConnect"></param>
/// <param name="OnReceive"></param>
public void ConnectServer(ServerData serverData, Action<ServerData> OnConnect, Action<string> OnReceive)
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//使用指定的地址簇協議、套接字類型和通信協議
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(serverData.IP), serverData.port); // 用指定的ip和端口號初始化IPEndPoint實例
clientSocket.Connect(serverEndPoint);
new Thread(() =>
{
connecting = true;
byte[] receive = new byte[1024];
while (connecting)
{
Debug.Log("等待消息");
int length = clientSocket.Receive(receive); // length 接收字節數組長度
OnReceive(Encoding.UTF8.GetString(receive));
}
}).Start();
}
}