基于Socekt的Unity多人在线聊天系统

  • Post author:
  • Post category:其他

先看看效果图



以上分别是两个客户端相互通讯的情况的。


源码:https://github.com/zymix/Unity_ChatSystem


C#本身对Socket拥有着高度的封装,所以搭建这样的一个多人聊天系统是非常容易的事情。这里先提醒几点:

1.关于Unity内部编码问题,Unity在debug阶段时其编码模式是跟操作系统一致的,但是当其发布以后,由于Unity的跨平台性使其编码改变成UTF8的形式,所以无论哪个阶段,都应该注意客户端与服务器段的编码模式一致。

2.关于Scroll View的问题,想滚动条动态的改变滚动长度需要,动态修改Scroll Rect组件的Content对象的RectTransform的deltaSize


剩下的,看注释,算是比较清晰了

客户端代码

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

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

public class ChatClient : MonoBehaviour
{
    public GameObject content;
    public InputField input;  //用于发送输入框的信息
    public InputField usernameInput;//客户端用户名
    public GameObject messageItem;//预制件的宽高
    public Button connectButton; //连接按钮

    TcpClient client;//客户端
    NetworkStream nstream;//网络数据流
    string ip = "192.168.10.5";//客户端IP
    int port = 10086;//客户端端口
    byte[] data;//客户端收发数据
    bool isConnect = false;

    List<GameObject> messageList;//存储接受的消息列表
    public int messagesMaxSize = 20;//存储接受的消息列表最大长度
    bool isNewMessage = false;//消息更新的标志位
    string newMessage;//接受到的最新信息
    //预制件的宽高
    float height = 0;
    float width = 0;
    void Start()
    {
        messageList = new List<GameObject>();
        messageList.Capacity = messagesMaxSize;

        //获取预设件的宽高
        width = messageItem.GetComponent<RectTransform>().rect.width;
        height = messageItem.GetComponent<RectTransform>().rect.height;

        //初始化组件
        if (content == null)
            content = transform.FindChild("Chat View/Viewport/Content").gameObject;
        if (input == null)
            input = transform.FindChild("InputField").GetComponent<InputField>();
        if (usernameInput == null)
            usernameInput = transform.FindChild("UsernameInput").GetComponent<InputField>();

        if (connectButton == null)
            connectButton = transform.FindChild("ConnectButton").GetComponent<Button>();
        //给连接按钮添加OnConnect事件,控制链接开启和关闭
        connectButton.onClick.AddListener(delegate { OnConnect(); });

    }
    void Update()
    {
        if (isNewMessage)
        {//若存在新的消息,则添加消息更新界面
            AddMessage(newMessage);
            isNewMessage = false;
        }
    }
    public void OnDestroy()
    {
        Disconnect();
    }


    //连接按钮的点击事件调用Onconnect连接函数
    public void OnConnect()
    {
        isConnect = !isConnect;
        if (isConnect)
        {
            connectButton.transform.FindChild("Text").GetComponent<Text>().text = "断开连接";
            Connect();
        }
        else
        {
            connectButton.transform.FindChild("Text").GetComponent<Text>().text = "连接服务器";
            Disconnect();
        }
    }




    //中断网络连接
    void Disconnect()
    {
        //关闭流和客户端
        if(nstream !=null&&(nstream.CanRead|| nstream.CanWrite))
            nstream.Close();
        if (nstream != null && client.Connected)
            client.Close();
    }

    //网络连接
    void Connect()
    {
        Info.debugStr = "";
        if ("" == usernameInput.text)
        {
            Info.debugStr = DateTime.Now + "->用户名不能为空";
            return;
        }
        try
        {
            //创建TCP连接
            client = new TcpClient(ip, port);
            nstream = client.GetStream();
            //先发送用户名
            SendMessage(usernameInput.text);
            //初始化收发数据
            data = new byte[client.ReceiveBufferSize];

            //开启异步从服务器获取消息,获取到的数据流存入data,回调ReceiveMessage方法
            nstream.BeginRead(data, 0, data.Length, AsyncReceive, null);
        }
        catch (SocketException socketEx)
        {//输出错误码
            Info.debugStr = DateTime.Now + "->" + socketEx.ErrorCode + ": " + socketEx.Message;
            //关闭流和客户端
            client.Close();
        }
        catch (Exception e)
        {   //显示错误信息
            Info.debugStr = DateTime.Now + "->" + e.Message;
            //断开连接
            Disconnect();
        }
    }

  

    //发送消息到服务器
    public void Send()
    {
        Info.debugStr = "";

        if ("" == input.text)
        {
            Info.debugStr = DateTime.Now + "->消息不能为空";
            return;
        }
        //发送消息到服务器
        SendMessage(input.text);
        //清空输入框
        input.text = "";
    }
    new public void SendMessage(string message)
    {
        try
        {   //把输入框的消息写入数据流,发送服务器
            //NetworkStream stream = client.GetStream();
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(message);
            nstream.Write(bytes, 0, bytes.Length);
        }
        catch (Exception e)
        {
            //显示错误信息
            Info.debugStr = DateTime.Now + "->" + e.Message;
            //断开连接
            Disconnect();
        }
    }


   
    //异步处理从服务器获得的消息
    void AsyncReceive(IAsyncResult ar)
    {
        Info.debugStr = "";
        try
        {
            int bytesRead = client.GetStream().EndRead(ar);
            if (bytesRead < 1)
            {   //接收不到任何消息
                return;
            }
            else
            {
                //把接收到的消息编码为UTF8,跟Unity发布后编码方式一致
                newMessage = System.Text.Encoding.UTF8.GetString(data, 0, bytesRead);
                //设置标志位,在UI上添加一条消息
                isNewMessage = true;
            }
            //再次开启异步从服务器获取消息,形成循环等待服务器消息
            nstream.BeginRead(data, 0, client.ReceiveBufferSize, AsyncReceive, null);
        }
        catch (Exception e)
        {
            Info.debugStr = DateTime.Now + "->" + e.Message;
            //断开连接
            Disconnect();
        }
    }
    
    
    
    //在UI上添加一条消息
    private void AddMessage(string uiMessage)
    {
        //把接收到的消息存入消息列表中
        GameObject item = Instantiate<GameObject>(messageItem);
        item.GetComponent<Text>().text = uiMessage;
        //超出列表上限时,再删除第一条
        if (messageList.Count > messageList.Capacity)
            messageList.RemoveAt(0);
        messageList.Add(item);

        //刷新界面
        ShowMessages();
    }
   
    private void ShowMessages()
    {
        int i = 0;
        foreach (GameObject m in messageList)
        {
            //设置为显示内容面板的子对象,便于位置的设置位置
            m.transform.SetParent(content.transform, false);
            //按序设置位置
            RectTransform rf = m.GetComponent<RectTransform>();
            rf.sizeDelta = new Vector2(width, height);
            rf.localPosition = new Vector2(0, -i * height);
            ++i;
        }
        content.GetComponent<RectTransform>().sizeDelta = new Vector2(width, i * height);
    }
}

简单的负责debug的提示信息类

using UnityEngine;
using System.Collections;

public class Info : MonoBehaviour {

	public static string debugStr = "";

	void OnGUI() {
		GUILayout.Label(debugStr);

	}
}


服务器端的代码

主程序

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ChatSystem
{
    class Program
    {
        static void Main(string[] args)
        {
            //设置监听的端口号
            Console.WriteLine("请输入服务器需要监听的端口: ");
            string input = Console.ReadLine();
            int port = int.Parse(input);
            //调用方法启动服务器
            Server(port);
        }

        static void Server(int port)
        {
            //初始化服务器ip
            //IPAddress localAddress = IPAddress.Parse("127.0.0.1");
            //设置监听
            TcpListener listener = new TcpListener(IPAddress.Any, port);
            listener.Start();

            //提示信息
            Console.WriteLine("{0:HH:mm:ss}->监听端口{1}....", DateTime.Now, port);

            //循环等待客户端的连接请求
            while(true)
            {
                ChatServerHandle usr = new ChatServerHandle(listener.AcceptTcpClient());
                Console.WriteLine(usr.ip + "加入聊天室");
            }
        }

    }
}


服务器辅助类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Collections;

namespace ChatSystem
{
    class ChatServerHandle
    {
        public static Hashtable Clients = new Hashtable();//客户端连接记录表
        public TcpClient client;//客户端
        public string username;//客户端用户名

        public string ip;//客户端IP
        public int port;//客户端端口

        public byte[] data;//客户端收发数据

        bool firstConnet = true;

        public ChatServerHandle(TcpClient client)
        {
            this.client = client;
            
            //保存客户端IP和端口
            IPEndPoint ipEndPoint = client.Client.RemoteEndPoint as IPEndPoint;
            this.ip = ipEndPoint.Address.ToString();
            this.port = ipEndPoint.Port;

            //初始化收发数据
            this.data = new byte[client.ReceiveBufferSize];

            //开启异步从客户端获取消息,获取到的数据流存入data,回调ReceiveMessage方法
            client.GetStream().BeginRead(data, 0, data.Length, ReceiveMessage, null);

        }
        //向客户端发送消息
        public void SendMessage(string message)
        {
            try
            {
                NetworkStream stream;
                lock(client.GetStream())
                {
                    stream = client.GetStream();
                }
                byte[] bytes = Encoding.UTF8.GetBytes(message); //注意Unity发布后程序已UTF8编码
                stream.Write(bytes, 0, bytes.Length);
                //stream.Flush();
            }
            catch (Exception e)
            {
                Console.WriteLine("{0:HH:mm:ss}->[SendMessage]Error: {1}", DateTime.Now, e.Message);
            }
        }
        //从客户端接受到的数据,再广播给所有客户端
        public void ReceiveMessage(IAsyncResult ar)
        {
           try
            {
                int bytesRead;
                lock(client.GetStream())
                {
                    bytesRead = client.GetStream().EndRead(ar);
                }
                if (bytesRead < 1)
                {//接收不到任何数据,则删除在客户端连接记录表
                    Clients.Remove(this.ip);
                    //向所有客户端广播该用户的下线信息
                    Broadcast("<color=#00ffffff>"+this.username + "</color> 于" + DateTime.Now + " 已下线....");
                    return;
                }
                else
                {  
                    string recMessage = Encoding.UTF8.GetString(data, 0, bytesRead); //注意Unity发布后程序已UTF8编码
                    if (firstConnet)
                    {   //检查是不是同一台电脑重复连接
                        if(Clients.ContainsKey(this.ip))
                            return;
                        //第一次连接,将客户端信息记录入客户端连接记录表中
                        Clients.Add(this.ip,this);
                        //获取发送的用户名信息
                        this.username = recMessage;
                        //向所有客户端广播该用户的上线信息
                        Broadcast("<color=#00ffffff>" + this.username + "</color> 于" + DateTime.Now+ " 已上线....");
                        //不在是第一次连接
                        firstConnet = false;
                    }
                    else
                    {
                        //向所有客户端广播该用户发送的
                        Broadcast(DateTime.Now + " ->【"+ this.username+"】" + recMessage);
                    }
                    //Console.WriteLine(recMessage);
                }
                lock(client.GetStream())
                {
                    //再次开启异步从服务器获取消息,形成循环
                    client.GetStream().BeginRead(data, 0, client.ReceiveBufferSize, ReceiveMessage, null);
                }
            }
            catch(Exception e)
            {
                //删除连接记录
                Clients.Remove(this.ip);
                //向所有客户端广播该用户的下线信息
                Broadcast("<color=#00ffffff>" + this.username + "</color> 于" + DateTime.Now + " 已下线....");

                Console.WriteLine("{0:HH:mm:ss}->[ReceiveMessage]Error: {1}", DateTime.Now, e.Message);
            }
        }

        //向所有连接到的客户端广播
        public void Broadcast(string message)
        {
            Console.WriteLine(message);
            foreach (DictionaryEntry item in Clients)
            {
                ChatServerHandle c = item.Value as ChatServerHandle;
                c.SendMessage(message);
            }
        }
    }
}

版权声明:本文为u012325397原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。