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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,849评论 6 13
  • Socket编程 1基础知识 协议 端口号(辨别不同应用) TCP/IP协议 是目前世界上应用最广泛的协议是以TC...
    __豆约翰__阅读 1,086评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 在别人沉不住气的时候,你越是要沉的住气。在别人越是想跟你争执的时候,越是他心虚,站不住脚的时候。他一定是在努力的想...
    neojos阅读 152评论 0 0
  • 飞哥说:如何通过线下的微信引流活动,为企业及实体店导入更多的新用户资源,这是每一个创业者都在思考的问题。飞哥今天想...
    司业飞阅读 1,348评论 1 13