网络编程Unity知识归纳

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

2018-01-18  本文已影响52人  IongX

实现聊天功能分为两部分:服务端,客户端。话不多说,上代码。

1. 服务端 (用VS写的控制台程序),主要实现异步通信,及连接池

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>
    /// 客户端连接对象类
    /// </summary>
    class ConnectClient
    {
        //缓冲区大小+
        public const int BUFFER_SIZE = 1024;
        public Socket socket;
        //是否使用
        public bool isUse = false; 
        //缓冲区
        public byte[] readBuff = new byte[BUFFER_SIZE];
        //数据大小
        public int bufferCount;

        //构造
        public ConnectClient()
        {
            readBuff = new byte[BUFFER_SIZE];
        }

        /// <summary>
        /// 初始化数据
        /// </summary>
        /// <param name="soc"></param>
        public void Init(Socket soc)
        {
            this.socket = soc;
            isUse = true;
            bufferCount = 0;
        }
        
        /// <summary>
        /// 缓冲区剩余字节空间
        /// </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>
        /// 关闭连接
        /// </summary>
        public void Close()
        {
            if (!isUse)
            {
                return;
            }
            else
            {
                Console.WriteLine(Address() + " [ 断开连接 ]");
                socket.Close();
                isUse = false;
            }
        }
    }
}

1.2 SocketServer (服务端控制类)
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>
    /// 服务器类
    /// </summary>
    class SocketServer
    {
        //监听socket对象
        public Socket listenSocket;
        //客户端连接池
        public ConnectClient[] clientList;
        //容纳客户端数量
        public int maxClient = 50;

        /// <summary>
        /// 从连接池中 获取客户端连接对象 ,如果列表中以满 则获取失败
        /// </summary>
        /// <returns></returns>
        public int GetIndex()
        {
            //如果连接池为空 则新建连接池 返回第一个连接对象
            if (clientList == null)
            {
                clientList = new ConnectClient[maxClient];
                clientList[0] = new ConnectClient();
                return 0;
            }
            else
            {
                //遍历连接池 , 返回未使用连接对象的索引
                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;
                    }
                }
                //如果都有对象且在使用中,则返回-1. 代表获取失败
                return -1;
            }
        }

        //接收回调
        private void AcceptCallBack(IAsyncResult ar)
        {
            try
            {
                Socket socket = listenSocket.EndAccept(ar);
                int index = GetIndex();
                if (index< 0)
                {
                    socket.Close();
                    Console.WriteLine("服务器连接已满,请稍候再试");
                }
                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());
                }
                //重新开始异步接收请求
                listenSocket.BeginAccept(AcceptCallBack, null);
            }
            catch (Exception e)
            {
                Console.WriteLine("客户端请求异常! Exception = " + e.Message);
            }

        }
        //返回回调
        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() + "    的数据 =  " + 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() + " 的数据 给 " + clientList[i].Address());
                    }
                }
                //继续接收数据
                client.socket.BeginReceive(client.readBuff, client.bufferCount, client.BufferRemain(), SocketFlags.None, ReceiveCallBack, client);
            }
            catch (Exception e)
            {
                Console.WriteLine("[接收数据异常]  client = " + client.Address());
                Console.WriteLine(" Execption = " + e.Message);
                client.Close();
            }
        }

        /// <summary>
        /// 开启服务
        /// </summary>
        /// <param name="host">主机地址</param>
        /// <param name="port">端口</param>
        /// <param name="maxClient">容纳客户端数量 (默认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("服务器启动成功!");
        }
    }
}

启动服务端:

 static void Main(string[] args)
        {
            Console.WriteLine("请输入服务端IP地址:");
            string host = Console.ReadLine();
            Console.WriteLine("请输入服务端端口号:");
            string port = Console.ReadLine();

            SocketServer server = new SocketServer();
            //只是本机测试,可以写127.0.0.1 , 但是要让其他机器连接的话,要写实际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; //接收的字符数据
    private bool isReceived = false; //数据接收完成
    // Use this for initialization
    void Start ()
    {
        //设置后台运行,数据就会立马同步更新。否则其他客户端发送一条消息,服务端进行广播,但是Unity进程被挂起了,就无法实时更新
        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;
        }

    }

    //服务器返回回调
    private void ReceiveCallBack(IAsyncResult ar)
    {
        try
        {
            int count = m_Socket.EndReceive(ar);
            reveString = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
            isReceived = true;
            //之所以不直接在这里赋值,是因为线程问题,会报错,该回调不是在unity主线程中执行的,所以赋值放在update中
            //if (ReceiveFiled.text.Length > 500)
            //{
            //    ReceiveFiled.text = "";
            //}
            //ReceiveFiled.text += reveString + '\n';
            //继续接收返回信息
            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;
        }
    }

}

运行效果如下:


图片.png

最后:

以上纯属个人总结,如有不对或者更好的方法,欢迎指正,交流。
工程文件链接 : https://github.com/IongX/Unity_Socket

上一篇 下一篇

猜你喜欢

热点阅读