基礎的unity局域網遊戲框架(一)

最近看了下關於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();
    }


}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章