1. 程式人生 > >Unity 與C#伺服器 實現Socket的UDP通訊(多客戶端)

Unity 與C#伺服器 實現Socket的UDP通訊(多客戶端)

前言

上一篇簡單的介紹了下Unity客戶端和伺服器的Socket通訊,但是還不能實現多個客戶端與伺服器的通訊,所以今天在這邊把前面的工程完善一下(使用的是上篇講到的UdpClient類來實現),實現多個客戶端與伺服器的udp通訊。效果圖如下,兩個客戶端可以向伺服器傳送訊息,然後伺服器每隔3秒給“連線上”的客戶端傳送收到的訊息。

內容

思路上一篇已經講到過了,我們定義兩個特殊的欄位,來告知伺服器,哪些客戶端連線上來了,哪些客戶端退出了,如下指令碼,該指令碼客戶端伺服器各存一份。

namespace Tool {
    class SocketDefine {
        public const int port = 8078;//埠號
        public const string ip = "127.0.0.1";
        public const string udpConnect = "udpConnect";//udp連線
        public const string udpDisconnect = "udpDisconnect";//udp斷開連線
    }
}

伺服器端

伺服器相比之前要做的就是,當接收到udpConnect資訊時要記錄下發該訊息的客戶端ip地址,儲存在一個字典中,字典不為空的時候,每隔三秒給字典中存在的客戶端傳送訊息(簡單測試,目前伺服器給各個客戶端發的訊息就是客戶端發給伺服器的最新訊息)。當收到udpDisconnect訊息時,將該客戶端從字典中刪除即可。同時我們定義一個新的類ClientMessage,用於存放客戶端資料。具體程式碼如下:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Tool {

    public class ClientMessage {
        /// <summary>
        /// 每個客戶端的唯一id,即ip地址+埠號
        /// </summary>
        public string clientId;
        /// <summary>
        /// 客戶端地址資訊
        /// </summary>
        public IPEndPoint clientIPEndPoint;
        /// <summary>
        /// 該客戶端傳送給伺服器的最新訊息
        /// </summary>
        public string recieveMessage;

        public ClientMessage(string id, IPEndPoint point, string msg) {
            clientId = id;
            clientIPEndPoint = new IPEndPoint(point.Address, point.Port);
            recieveMessage = msg;
        }
    }

    class UdpManager : SingleClass<UdpManager> {

        UdpClient m_udpClient;
        IPEndPoint m_clientIpEndPoint;//存放客戶端地址
        Thread m_connectThread;//接收客戶端訊息的執行緒
        byte[] m_result = new byte[1024];//存放接收到的訊息
        int m_sendCount;//傳送次數

        Dictionary<string, ClientMessage> m_clientMessageDic;//存放客戶端資訊,key->ip+port

        //初始化
        public void Start() {
            IPEndPoint ipEnd = new IPEndPoint(IPAddress.Parse(SocketDefine.ip), SocketDefine.port);
            m_udpClient = new UdpClient(ipEnd);
            m_clientMessageDic = new Dictionary<string, ClientMessage>();
            m_sendCount = 0;

            //定義客戶端
            m_clientIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
            Console.WriteLine("等待連線資料");
            //開啟一個執行緒連線
            m_connectThread = new Thread(new ThreadStart(Receive));
            m_connectThread.Start();

            //定時器
            System.Timers.Timer t = new System.Timers.Timer(3000);//例項化Timer類,設定間隔時間為3000毫秒
            t.Elapsed += new System.Timers.ElapsedEventHandler(SendToClient);//到達時間的時候執行事件
            t.AutoReset = true;//設定是執行一次(false)還是一直執行(true)
            t.Enabled = true;//是否執行System.Timers.Timer.Elapsed事件
        }

        public void SendToClient(object source, System.Timers.ElapsedEventArgs e) {
            Send(GetAllClientMessage());
        }

        public void Send(string data) {
            if(m_clientMessageDic == null || m_clientMessageDic.Count == 0) {
                return;
            }
            try {
                NetBufferWriter writer = new NetBufferWriter();
                writer.WriteString(data);
                byte[] msg = writer.Finish();
                foreach(var point in m_clientMessageDic) {
                    Console.WriteLine("send to  " + point.Key + "  " + data);
                    m_udpClient.Send(msg, writer.finishLength, point.Value.clientIPEndPoint);
                }
                m_sendCount++;
            } catch(Exception ex) {
                Console.WriteLine("send error   " + ex.Message);
            }
        }

        //伺服器接收
        void Receive() {
            while(true) {
                try {
                    m_result = new byte[1024];
                    m_result = m_udpClient.Receive(ref m_clientIpEndPoint);
                    NetBufferReader reader = new NetBufferReader(m_result);
                    string data = reader.ReadString();
                    string clientId = string.Format("{0}:{1}", m_clientIpEndPoint.Address, m_clientIpEndPoint.Port);

                    if(data.Equals(SocketDefine.udpConnect)) {
                        AddNewClient(clientId, new ClientMessage(clientId, m_clientIpEndPoint, data));
                    } else if(data.Equals(SocketDefine.udpDisconnect)) {
                        RemoveClient(clientId);
                    } else {
                        if(m_clientMessageDic != null && m_clientMessageDic.ContainsKey(clientId)) {
                            m_clientMessageDic[clientId].recieveMessage = data;
                        }
                    }

                    Console.WriteLine(m_clientIpEndPoint + "  資料內容:{0}", data);
                } catch(Exception ex) {
                    Console.WriteLine("receive error   " + ex.Message);
                }
            }
        }

        //連線關閉
        void Close() {
            //關閉執行緒
            if(m_connectThread != null) {
                m_connectThread.Interrupt();
                //Thread abort is not supported on this platform.
                //m_connectThread.Abort();
                m_connectThread = null;
            }

            m_clientMessageDic.Clear();

            if(m_udpClient != null) {
                m_udpClient.Close();
                m_udpClient.Dispose();
            }
            Console.WriteLine("斷開連線");
        }

        void AddNewClient(string id, ClientMessage msg) {
            if(m_clientMessageDic != null && !m_clientMessageDic.ContainsKey(id)) {
                m_clientMessageDic.Add(id, msg);
            }
        }

        void RemoveClient(string id) {
            if(m_clientMessageDic != null && m_clientMessageDic.ContainsKey(id)) {
                m_clientMessageDic.Remove(id);
            }
        }

        string GetAllClientMessage() {
            string allMsg = "m_sendCount    " + m_sendCount + "\n";
            foreach(var msg in m_clientMessageDic) {
                allMsg += (msg.Value.clientId + "->" + msg.Value.recieveMessage + "\n");
            }
            return allMsg;
        }
    }
}

伺服器

伺服器就更簡單啦,connect的時候給伺服器傳送一個udpConnect訊息,斷開的時候傳送一個udpDisconnect訊息即可。

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;

namespace Tool {

    public class UdpManager : SingleClass<UdpManager>, ISocket {

        public delegate void OnGetReceive(string message);//接收到訊息的委託
        public OnGetReceive onGetReceive;

        byte[] m_result = new byte[1024];

        Thread m_connectThread;
        UdpClient m_udpClient;
        IPEndPoint m_serverIPEndPoint;
        IPEndPoint m_recieveIPEndPoint;

        bool m_isConnected;

        //是否已連線的標識  
        public bool IsConnected() {
            return m_isConnected;
        }

        //udp是無連線的,所以方法裡只做了一些初始化操作
        public void Connect(string ip, int port) {
            m_serverIPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            m_udpClient = new UdpClient(0);//!!!!!主要一定要設定port為0,不然無法接收到伺服器的訊息
            m_isConnected = true;

            m_recieveIPEndPoint = new IPEndPoint(IPAddress.Any, 0);

            //開啟一個執行緒連線
            m_connectThread = new Thread(new ThreadStart(RecieveMessage));
            m_connectThread.Start();

            SendMessage(SocketDefine.udpConnect);
        }

        public void SendMessage(string data) {
            if(IsConnected()) {
                NetBufferWriter writer = new NetBufferWriter();
                Debug.Log("SendMessage  " + data);
                writer.WriteString(data);
                m_udpClient.Send(writer.Finish(), writer.finishLength, m_serverIPEndPoint);
            }
        }

        public void Disconnect() {
            SendMessage(SocketDefine.udpDisconnect);

            if(m_connectThread != null) {
                m_connectThread.Interrupt();
                //在呼叫此方法的執行緒上引發 ThreadAbortException,以開始終止此執行緒的過程。 呼叫此方法通常會終止執行緒。
                m_connectThread.Abort();
            }

            if(IsConnected()) {
                Debug.Log("Disconnect");
                m_isConnected = false;

            }
            if(m_udpClient != null) {
                m_udpClient.Close();
            }
        }

        public void RecieveMessage() {
            while(IsConnected()) {
                try {
                    m_result = new byte[1024];
                    //m_udpClient的port不是0的時候,會報錯,無效引數
                    m_result = m_udpClient.Receive(ref m_recieveIPEndPoint);
                    NetBufferReader reader = new NetBufferReader(m_result);
                    string msg = reader.ReadString();
                    Debug.Log("RecieveMessage   " + msg);

                    if(onGetReceive != null) {
                        onGetReceive(msg);
                    }
                } catch(Exception e) {
                    Debug.Log("recieve error    " + e.Message);
                }
            }
        }
    }
}

這樣就可以實現最簡單的多客戶端和伺服器的udp通訊了