Unity中的聊天功能(異步Socket)

實(shí)現(xiàn)聊天功能分為兩部分:服務(wù)端,客戶端。話不多說,上代碼。

1. 服務(wù)端 (用VS寫的控制臺(tái)程序),主要實(shí)現(xiàn)異步通信,及連接池

1.1 ConnectClient (客戶端連接類)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;

namespace TestSocketServer
{
    /// <summary>
    /// 客戶端連接對(duì)象類
    /// </summary>
    class ConnectClient
    {
        //緩沖區(qū)大小+
        public const int BUFFER_SIZE = 1024;
        public Socket socket;
        //是否使用
        public bool isUse = false; 
        //緩沖區(qū)
        public byte[] readBuff = new byte[BUFFER_SIZE];
        //數(shù)據(jù)大小
        public int bufferCount;

        //構(gòu)造
        public ConnectClient()
        {
            readBuff = new byte[BUFFER_SIZE];
        }

        /// <summary>
        /// 初始化數(shù)據(jù)
        /// </summary>
        /// <param name="soc"></param>
        public void Init(Socket soc)
        {
            this.socket = soc;
            isUse = true;
            bufferCount = 0;
        }
        
        /// <summary>
        /// 緩沖區(qū)剩余字節(jié)空間
        /// </summary>
        /// <returns></returns>
        public int BufferRemain()
        {
            return BUFFER_SIZE - bufferCount;
        }

        /// <summary>
        /// 獲取socket連接地址
        /// </summary>
        /// <returns></returns>
        public string Address()
        {
            if (!isUse)
            {
                return null;
            }
            else
            {
                return socket.RemoteEndPoint.ToString();
            }

        }

        /// <summary>
        /// 關(guān)閉連接
        /// </summary>
        public void Close()
        {
            if (!isUse)
            {
                return;
            }
            else
            {
                Console.WriteLine(Address() + " [ 斷開連接 ]");
                socket.Close();
                isUse = false;
            }
        }
    }
}

1.2 SocketServer (服務(wù)端控制類)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;

namespace TestSocketServer
{
    /// <summary>
    /// 服務(wù)器類
    /// </summary>
    class SocketServer
    {
        //監(jiān)聽socket對(duì)象
        public Socket listenSocket;
        //客戶端連接池
        public ConnectClient[] clientList;
        //容納客戶端數(shù)量
        public int maxClient = 50;

        /// <summary>
        /// 從連接池中 獲取客戶端連接對(duì)象 ,如果列表中以滿 則獲取失敗
        /// </summary>
        /// <returns></returns>
        public int GetIndex()
        {
            //如果連接池為空 則新建連接池 返回第一個(gè)連接對(duì)象
            if (clientList == null)
            {
                clientList = new ConnectClient[maxClient];
                clientList[0] = new ConnectClient();
                return 0;
            }
            else
            {
                //遍歷連接池 , 返回未使用連接對(duì)象的索引
                for (int i = 0; i < clientList.Length; i++)
                {
                    if (clientList[i] == null)
                    {
                        clientList[i] = new ConnectClient();
                        return i;
                    }
                    else if (clientList[i].isUse == false)
                    {
                        return i;
                    }
                }
                //如果都有對(duì)象且在使用中,則返回-1. 代表獲取失敗
                return -1;
            }
        }

        //接收回調(diào)
        private void AcceptCallBack(IAsyncResult ar)
        {
            try
            {
                Socket socket = listenSocket.EndAccept(ar);
                int index = GetIndex();
                if (index< 0)
                {
                    socket.Close();
                    Console.WriteLine("服務(wù)器連接已滿,請(qǐng)稍候再試");
                }
                else
                {
                    ConnectClient client = clientList[index];
                    client.Init(socket);
                    client.socket.BeginReceive(client.readBuff, client.bufferCount, client.BufferRemain(), SocketFlags.None, ReceiveCallBack,client);
                    Console.WriteLine("客戶端連接成功 = " + client.Address());
                }
                //重新開始異步接收請(qǐng)求
                listenSocket.BeginAccept(AcceptCallBack, null);
            }
            catch (Exception e)
            {
                Console.WriteLine("客戶端請(qǐng)求異常! Exception = " + e.Message);
            }

        }
        //返回回調(diào)
        private void ReceiveCallBack(IAsyncResult ar)
        {
            ConnectClient client = (ConnectClient)ar.AsyncState;
            try
            {
                int count = client.socket.EndReceive(ar);
                //斷開連接
                if (count <= 0)
                {
                    Console.WriteLine("斷開連接  = " + client.Address());
                    client.Close();
                }
                else
                {
                    string receiveString = System.Text.Encoding.UTF8.GetString(client.readBuff, 0, count);
                    Console.WriteLine("接收 " + client.Address() + "    的數(shù)據(jù) =  " + receiveString);
                    byte[] sendBytes = System.Text.Encoding.UTF8.GetBytes(client.Address() + " :   " + receiveString);

                    //廣播信息
                    for (int i = 0; i < clientList.Length; i++)
                    {
                        if (clientList[i] == null)
                        {
                            continue;
                        }

                        if (clientList[i].isUse == false)
                        {
                            continue;
                        }
                        clientList[i].socket.Send(sendBytes);
                        Console.WriteLine("廣播 " + client.Address() + " 的數(shù)據(jù) 給 " + clientList[i].Address());
                    }
                }
                //繼續(xù)接收數(shù)據(jù)
                client.socket.BeginReceive(client.readBuff, client.bufferCount, client.BufferRemain(), SocketFlags.None, ReceiveCallBack, client);
            }
            catch (Exception e)
            {
                Console.WriteLine("[接收數(shù)據(jù)異常]  client = " + client.Address());
                Console.WriteLine(" Execption = " + e.Message);
                client.Close();
            }
        }

        /// <summary>
        /// 開啟服務(wù)
        /// </summary>
        /// <param name="host">主機(jī)地址</param>
        /// <param name="port">端口</param>
        /// <param name="maxClient">容納客戶端數(shù)量 (默認(rèn)50)</param>
        public void Start(string host , int port , int maxClient = 50)
        {
            //初始化連接池
            this.maxClient = maxClient;
            clientList = new ConnectClient[this.maxClient];

            listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipa = IPAddress.Parse(host);
            IPEndPoint ipe = new IPEndPoint(ipa, port);
            listenSocket.Bind(ipe);
            listenSocket.Listen(maxClient);
            //開啟異步接收連接
            listenSocket.BeginAccept(AcceptCallBack, null);

            Console.WriteLine("服務(wù)器啟動(dòng)成功!");
        }
    }
}

啟動(dòng)服務(wù)端:

 static void Main(string[] args)
        {
            Console.WriteLine("請(qǐng)輸入服務(wù)端IP地址:");
            string host = Console.ReadLine();
            Console.WriteLine("請(qǐng)輸入服務(wù)端端口號(hào):");
            string port = Console.ReadLine();

            SocketServer server = new SocketServer();
            //只是本機(jī)測試,可以寫127.0.0.1 , 但是要讓其他機(jī)器連接的話,要寫實(shí)際ip地址
            //server.Start("192.168.0.171", 1234);
            server.Start(host, int.Parse(port));
            while (true)
            {
                string write = Console.ReadLine();
                switch (write)
                {
                    case "quit":
                        return;
                }
            }
        }

2. 客戶端,聊天Demo 及Socket通信。

場景界面如下:


圖片.png

功能控制腳本:

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

public class TestClient : MonoBehaviour {

    private Socket m_Socket;

    public InputField HostField;
    public InputField PortField;
    public InputField MessageField;
    public InputField LinkMessageField;
    public InputField ReceiveFiled;
    private byte[] readBuff = new byte[1024];

    private string reveString; //接收的字符數(shù)據(jù)
    private bool isReceived = false; //數(shù)據(jù)接收完成
    // Use this for initialization
    void Start ()
    {
        //設(shè)置后臺(tái)運(yùn)行,數(shù)據(jù)就會(huì)立馬同步更新。否則其他客戶端發(fā)送一條消息,服務(wù)端進(jìn)行廣播,但是Unity進(jìn)程被掛起了,就無法實(shí)時(shí)更新
        Application.runInBackground = true;
    }
    
    public void LinkServer()
    {      
        m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            m_Socket.Connect(HostField.text, int.Parse(PortField.text));
            LinkMessageField.text = "連接成功-----" + m_Socket.LocalEndPoint.ToString();
        }
        catch (Exception)
        {
            LinkMessageField.text = "連接失?。?!";
            throw;
        }
        
        m_Socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, null);
    }


    public void SendMessageToServer()
    {
        try
        {
            byte[] sendBytes = System.Text.Encoding.UTF8.GetBytes(MessageField.text);
            m_Socket.Send(sendBytes);
        }
        catch (Exception)
        {
            throw;
        }

    }

    //服務(wù)器返回回調(diào)
    private void ReceiveCallBack(IAsyncResult ar)
    {
        try
        {
            int count = m_Socket.EndReceive(ar);
            reveString = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
            isReceived = true;
            //之所以不直接在這里賦值,是因?yàn)榫€程問題,會(huì)報(bào)錯(cuò),該回調(diào)不是在unity主線程中執(zhí)行的,所以賦值放在update中
            //if (ReceiveFiled.text.Length > 500)
            //{
            //    ReceiveFiled.text = "";
            //}
            //ReceiveFiled.text += reveString + '\n';
            //繼續(xù)接收返回信息
            m_Socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, ReceiveCallBack, null);
        }
        catch (Exception)
        {
            reveString = m_Socket.LocalEndPoint.ToString() + "連接斷開";
            isReceived = true;
            m_Socket.Close();
            throw;
        }

    }

    private void Update()
    {
        if (isReceived)
        {
            if (ReceiveFiled.text.Length > 500)
            {
                ReceiveFiled.text = "";
            }
            ReceiveFiled.text += reveString + '\n';
            isReceived = false;
        }
    }

}

運(yùn)行效果如下:


圖片.png

最后:

以上純屬個(gè)人總結(jié),如有不對(duì)或者更好的方法,歡迎指正,交流。
工程文件鏈接 : https://github.com/IongX/Unity_Socket

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 12,425評(píng)論 6 13
  • Socket編程 1基礎(chǔ)知識(shí) 協(xié)議 端口號(hào)(辨別不同應(yīng)用) TCP/IP協(xié)議 是目前世界上應(yīng)用最廣泛的協(xié)議是以TC...
    __豆約翰__閱讀 1,191評(píng)論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • 在別人沉不住氣的時(shí)候,你越是要沉的住氣。在別人越是想跟你爭執(zhí)的時(shí)候,越是他心虛,站不住腳的時(shí)候。他一定是在努力的想...
    neojos閱讀 202評(píng)論 0 0
  • 飛哥說:如何通過線下的微信引流活動(dòng),為企業(yè)及實(shí)體店導(dǎo)入更多的新用戶資源,這是每一個(gè)創(chuàng)業(yè)者都在思考的問題。飛哥今天想...
    司業(yè)飛閱讀 1,439評(píng)論 1 13

友情鏈接更多精彩內(nèi)容