Unity3D笔记——解决Unity官方《Tanks》同时启动会创建两个服务器问题

时间:2021-12-27 13:04:56

前言:Unity3D笔记是我平时做一些好玩的测试和研究,记录的笔记。会比较详细也可能随口一提就过了。 所以大家见谅了,内容一般都会是原创的(非原创我会注明转载)。由于很多内容其他的朋友也肯定研究发表过,大家请指出错误。

       公司一个同事在用Unet的时候,是参考unity 的官方教程《Tanks》的。然后发现一个小问题:在局域网中,当两台电脑同时按下(接近的时间按下)play的时候,会一起检测局域网是否有服务器去加入。然后 都检测不到;于是就各自创建了服务器。没有在一个游戏中;

于是呢,考虑了下;我觉得造成该问题的原因是:游戏打开的时间接近,没有办法判断谁创建服务器,谁是客户端;导致互相都创建的问题;直觉告诉我,可以用UDP的广播实现问题:1.开始游戏,全网监听固定端口(5555)发来的消息(是否有服务器了)2.如果有则加入;3.如果1秒后没有收到,则自己广播信号出去(我要创建服务器);4.创建服务器;

用到了之前的一些代码;我改了下

直接上代码:UDP的全网接受和发送

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Net;
using System.Net.Sockets;
using UnityEngine.UI;

namespace ImmerUDP
{

    public class GameUdpManager :MonoBehaviour
    {
        [Header("本地端口"),SerializeField]
        private int sendPort = 6666;

        [Header("目标端口"), SerializeField]
        private int receivePort = 8888;


        //服务器端Scoket对象;
        private Socket serverSocket;

        //udp客户端socket
        private UdpClient client;

        private EndPoint epSender;

        private IPEndPoint endpoint;

        //接受数据的字符数组
        private byte[] ReceiveData = new byte[1024];

        public static GameUdpManager Instance;

        private void Awake()
        {
            Instance = this;
        }


        public void StartUdpWork(int sendPort,int recevie)
        {
            this.sendPort = sendPort;
            this.receivePort = recevie;

            StartUdpReceive();

            OpenSendSocket();

        }


        /// <summary>
        /// 开启接受的Socket 并开始异步接受
        /// </summary>
        public void StartUdpReceive()
        {
            //服务器Socket对象实例化
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            //Socket对象跟服务器端的IP和端口绑定 
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, receivePort));

            //监听的端口和地址
            epSender = (EndPoint)new IPEndPoint(IPAddress.Parse("255.255.255.255"), sendPort);

            //开始异步接受数据
            serverSocket.BeginReceiveFrom(ReceiveData, 0, ReceiveData.Length, SocketFlags.None, ref epSender, new AsyncCallback(ReceiveFromClients), epSender);

        }


        /// <summary>
        /// 开启发送的Socket
        /// </summary>
        public void OpenSendSocket()
        {
            client = new UdpClient(new IPEndPoint(IPAddress.Any, sendPort));

            //目标端口和地址
            endpoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), receivePort);
        }




        /// <summary>
        /// 异步接受,处理数据
        /// </summary>
        /// <param name="iar"></param>
        private void ReceiveFromClients(IAsyncResult iar)
        {
            int reve = serverSocket.EndReceiveFrom(iar, ref epSender);

            //数据处理
            string str = System.Text.Encoding.UTF8.GetString(ReceiveData, 0, reve);

            //Debug.Log(str);

            //把得到数据传给数据处理中心
            NetworkDataHandler.Instance.NetworkDataUpdate(str);

            serverSocket.BeginReceiveFrom(ReceiveData, 0, ReceiveData.Length, SocketFlags.None, ref epSender, new AsyncCallback(ReceiveFromClients), epSender);

        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="dataStr">str 数据</param>
        public void Send(string dataStr)
        {
          

            byte[] SendData = System.Text.Encoding.UTF8.GetBytes(dataStr);

            client.Send(SendData, SendData.Length, endpoint);
        }

    }
}

说明:这边的UDP有这两个socket:分别是发送和接受;发送是向6666端口全网广播的;接受是:全网接受5555端口的;

消息处理中心:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class NetworkDataHandler : MonoBehaviour {

    public static NetworkDataHandler Instance;

    public delegate void VoidEvent();

    //注册对应的事件
    public event VoidEvent serverOpenEvent;


    bool isDataUpdate = false;

    public string data="";

    private void Awake()
    {
        Instance = this;
    }

    // Use this for initialization
    void Start () {

	}

    private void Update()
    {
        if (!isDataUpdate) return;

        isDataUpdate = false;

    }

    public void NetworkDataUpdate(string data) {     

        this.data = data;

       // Debug.Log(data);

        if (data.Contains("ServerIsOpen"))
        {

            if (serverOpenEvent != null)
                serverOpenEvent();
        }

    }
}

执行脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class demo5 : MonoBehaviour {

    public static bool isHasServer=false;

    bool isSendMessage = false;

    private void Awake()
    {
       
    }

    // Use this for initialization
    void Start () {
        Invoke("startServer", 1f);

        NetworkDataHandler.Instance.serverOpenEvent += openClient;

        ImmerUDP.GameUdpManager.Instance.StartUdpWork(5555,6666);

      
    }

    private void Update()
    {
       if(isSendMessage)
        ImmerUDP.GameUdpManager.Instance.Send("ServerIsOpen");
    }

    void startServer()
    {
        if (isHasServer) return;

        isSendMessage = true;
    }


    void openClient()
    {
        if (isHasServer) return;

        if (isSendMessage == true)
        {
            Debug.Log("本机是服务器,不用去开始客户端");
            return;
        }

        isHasServer = true;

    }
}

说明:执行脚本的作用就是通过广播判断下:局域网中是否有服务器了;没呢:广播信息告诉大家,这边会创建服务器;

然后是《Tanks》中局域网开始游戏代码更改:

   public IEnumerator DiscoverNetwork()
        {

            yield return new WaitForSeconds(1);

            NetworkDiscoveryCustom discovery = GetComponent<NetworkDiscoveryCustom>();
            discovery.Initialize();

            print(demo5.isHasServer);

            if (demo5.isHasServer)
            {
                discovery.StartAsClient();
            }
            else
            {
                yield return new WaitForSeconds(0.5f);
                //NetworkDiscoveryCustom discovery2 = GetComponent<NetworkDiscoveryCustom>();
                discovery.StartAsServer();
                StartHost();
            }

            //////start listening to other hosts
            //NetworkDiscoveryCustom discovery = GetComponent<NetworkDiscoveryCustom>();
            //discovery.Initialize();
            //discovery.StartAsClient();

            ////wait few seconds for broadcasts to arrive
            //yield return new WaitForSeconds(8);

            ////we haven't found a match, open our own
            //if (discovery.running)
            //{
            //    discovery.StopBroadcast();
            //    yield return new WaitForSeconds(0.5f);

            //    discovery.StartAsServer();
            //    StartHost();
            //}
        }

说明:这是《Tanks》中NetworkManagerCustom.cs中一段;主要功能是实现局域网的寻找服务器和搭建服务器; 我改了下:

如果之前广播结果是:有服务器了,那么就直接去连接;如果是没有服务器,那么本地创建服务器;


东西不多,也算是一种应用啦。希望对大家有帮助;