Unity网络聊天室

起因是计网、网络编程、游戏开发三门课作业重咯。于是打算随便学学

简单总结

三天时间踩了不少坑,借助搜索引擎与人工智能,还是把这个小demo完成了

多路复用

核心代码

                //填充checkRead列表
                checkRead.Clear();
                checkRead.Add(server);

                foreach (ClientState cs in clients.Values)
                {
                    checkRead.Add(cs.socket);
                }
                //Select,多路复用,同时检测多个Socket的状态
                Socket.Select(checkRead, null, null, 1000);

核心思路:把以连接的Client都放进checkRead列表里,使用Socket.Select筛选出在一定时间内接收到数据的Socket,再进行处理。

解析协议

        public static byte[] Pack(int type, byte[] msg)
        {
            byte[] container = new byte[1024];
            byte[] msgByte = BitConverter.GetBytes(type);
            Buffer.BlockCopy(msgByte, 0, container, 0, 4);
            byte[] msgLength = BitConverter.GetBytes(msg.Length);
            Buffer.BlockCopy(msgLength, 0, container, 4, 4);
            Buffer.BlockCopy(msg, 0, container, 8, msg.Length);

            container = container.Take(4 + 4 + msg.Length).ToArray();
            return container;
        }
        public static Data UnPack(byte[] msg, Socket Remote)
        {
            int type = BitConverter.ToInt32(msg, 0);
            int msgLength = BitConverter.ToInt32(msg, 4);
            byte[] msgBody = msg.Skip(8).Take(msgLength).ToArray();
            Console.WriteLine("msgBody.Length:" + msgBody.Length);
            Console.WriteLine("msgLength:" + msgLength);
            return new Data(type, msgLength, msgBody);
        }

客户端服务器按照一定的格式进行打包与解包,(这只是一个最简单的案例,不过简单的框架出来了再往里加也就简单了

小坑

image-20230508003517686

image-20230508003557866

哎呀懒得写了,润去Java安全了,估计过段时间心血来潮再来学一波叭,溜咯。一下哎代码

Server端代码

NetProtocol.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
namespace Server
{
    class Data
    {
        public int type;
        public int length;
        public byte[] msg;
        public Data(int type,int length, byte[] msg)
        {
            this.type = type;
            this.length = length;
            this.msg = msg;
        }
    }
    class NetProtocol
    {
        public static byte[] Pack(int type, byte[] msg)
        {
            byte[] container = new byte[1024];
            byte[] msgByte = BitConverter.GetBytes(type);
            Buffer.BlockCopy(msgByte, 0, container, 0, 4);
            byte[] msgLength = BitConverter.GetBytes(msg.Length);
            Buffer.BlockCopy(msgLength, 0, container, 4, 4);
            Buffer.BlockCopy(msg, 0, container, 8, msg.Length);

            container = container.Take(4 + 4 + msg.Length).ToArray();
            return container;
        }
        public static Data UnPack(byte[] msg, Socket Remote)
        {
            int type = BitConverter.ToInt32(msg, 0);
            int msgLength = BitConverter.ToInt32(msg, 4);
            byte[] msgBody = msg.Skip(8).Take(msgLength).ToArray();
            Console.WriteLine("msgBody.Length:" + msgBody.Length);
            Console.WriteLine("msgLength:" + msgLength);
            return new Data(type, msgLength, msgBody);
        }

    }
}

program.cs

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;

namespace Server
{
    class ClientState
    {
        public Socket socket;
        public byte[] recvBuffer = new byte[1024];
        public string recvStr = "";
    }
    class Program
    {
        static Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
        static List<Socket> checkRead = new List<Socket>();
        static void Main(string[] args)
        {
            StartServer();
        }
        public static void StartServer()
        {
            server.Bind(new IPEndPoint(IPAddress.Any, 8888));
            server.Listen(20);
            Console.WriteLine("[服务器启动]");
            
            
            while (true)
            {
                //填充checkRead列表
                checkRead.Clear();
                checkRead.Add(server);

                foreach (ClientState cs in clients.Values)
                {
                    checkRead.Add(cs.socket);
                }
                //Select,多路复用,同时检测多个Socket的状态
                Socket.Select(checkRead, null, null, 1000);
                //检查可读对象
                foreach (Socket s in checkRead)
                {
                    if (s == server)
                    {
                        ReadListenfd(s);
                    }
                    else
                    {
                        ReadClientfd(s);
                    }
                }
            }
        }
        /// <summary>
        /// 读取listenfd,开始Accept
        /// </summary>
        /// <param name="listenfd"></param>
        public static void ReadListenfd(Socket listenfd)
        {
            Console.WriteLine("Accept");
            Socket clientfd = listenfd.Accept();
            ClientState clientState = new ClientState();
            clientState.socket = clientfd;
            clients.Add(clientfd, clientState);
        }

        public static bool ReadClientfd(Socket clientfd)
        {
            //Console.WriteLine("Read");
            ClientState clientState = clients[clientfd];
            //接收
            int count = 0;
            try
            {
                count = clientfd.Receive(clientState.recvBuffer);
            }
            catch (SocketException ex)
            {
                clientfd.Close();
                Console.WriteLine("Receive SocketException " + ex.ToString());
                return false;
            }
            //客户端关闭
            if (count == 0)
            {
                clientfd.Close();
                clients.Remove(clientfd);
                Console.WriteLine("Socket Colse");
                return false;
            }
            //Console.WriteLine("Copy");
            byte[] receivedData = new byte[count];
            Buffer.BlockCopy(clientState.recvBuffer, 0, receivedData, 0, count);
            // 解析协议
            Data data=NetProtocol.UnPack(receivedData, clientfd);
            //Console.WriteLine( data.type);
            if(data.type==1)
            {
                //Console.WriteLine(data.length);
                Console.WriteLine(Encoding.UTF8.GetString(data.msg,0,data.length));
            }
            else if(data.type==2)
            {
                Console.WriteLine(Encoding.UTF8.GetString(data.msg, 0, data.length));
                byte[] msg = NetProtocol.Pack(2, data.msg);
                foreach(ClientState cs in clients.Values)
                {
                    cs.socket.Send(msg);
                }
                //clientfd.Send(msg);
            }
            return true;
        }
    }
}

Client端代码

NetProtocol.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
namespace Server
{
    class Data
    {
        public int type;
        public int length;
        public byte[] msg;
        public Data(int type,int length, byte[] msg)
        {
            this.type = type;
            this.length = length;
            this.msg = msg;
        }
    }
    class NetProtocol
    {
        public static byte[] Pack(int type, byte[] msg)
        {
            byte[] container = new byte[1024];
            byte[] msgByte = BitConverter.GetBytes(type);
            Buffer.BlockCopy(msgByte, 0, container, 0, 4);
            byte[] msgLength = BitConverter.GetBytes(msg.Length);
            Buffer.BlockCopy(msgLength, 0, container, 4, 4);
            Buffer.BlockCopy(msg, 0, container, 8, msg.Length);

            container = container.Take(4 + 4 + msg.Length).ToArray();
            return container;
        }
        public static Data UnPack(byte[] msg, Socket Remote)
        {
            int type = BitConverter.ToInt32(msg, 0);
            int msgLength = BitConverter.ToInt32(msg, 4);
            byte[] msgBody = msg.Skip(8).Take(msgLength).ToArray();
            Console.WriteLine("msgBody.Length:" + msgBody.Length);
            Console.WriteLine("msgLength:" + msgLength);
            return new Data(type, msgLength, msgBody);
        }

    }
}

Login.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine;
using TMPro;
using network;
using System;

public class Login : MonoBehaviour
{
    public  TMP_Text text;

    public  void login()
    {
        Client.ClientName = text.text;
        SceneManager.LoadScene("chat");
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Client.cs

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

namespace network
{
    public class Client 
    {
        public static String ClientName;
        public static Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        public static byte[] buffer = new byte[1024];
        // Start is called before the first frame update

        /// <summary>
        /// 点击连接按钮
        /// </summary>
        public static void Connection()
        {
            try
            {
                if(string.IsNullOrEmpty(ClientName))
                {
                    ClientName = "client" + new System.Random().Next().ToString();
                }
                socket.Connect("127.0.0.1", 8888);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Connection failed: " + ex.Message);
            }
        }
        public static void Send(int type,string s)
        {
            
            byte[] msg = NetProtocol.Pack(type, Encoding.UTF8.GetBytes(s));
            socket.Send(msg);
        }
        
    }

}

Chat.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using network;
using System;
using System.Text;
using System.Net.Sockets;

public class Chat : MonoBehaviour
{

    public GameObject ListItemPrefeb;

    public GameObject Content;

    ScrollRect scrollRect ;

    public TMP_Text test;

    public TMP_Text sendText;

    public TMP_InputField inputField;

    public Queue<Data> datas = new Queue<Data>();
    public void chat()
    {
        try
        {
            
            //if(String.IsNullOrEmpty(sendText.text))
            //{
            //    return;
            //}
            string s = inputField.text;
            Debug.Log("s.Length:" + s.Length);
            Debug.Log("Encoding.UTF8.GetBytes(s):" + Encoding.UTF8.GetBytes(s).Length);
            //Client.Send(s.Substring(0,s.Length-1));
            Client.Send(2,"["+Client.ClientName+"]: "+s);
        }
        catch (Exception ex)
        {
            test.text = "Send Failed"+ex.Message;

        }

    }


    // Start is called before the first frame update
    void Start()
    {
        scrollRect = Content.GetComponent<ScrollRect>();
        try
        {
            Client.Connection();
            ReceiveMessageFromServer();
            test.text = "Connect : success";
        }
        catch(Exception ex)
        {
            test.text = "Connect : Fail\n" + ex.Message;

        }
    }

    // Update is called once per frame
    void Update()
    {
        if(datas.Count!=0)
        {
            Data data = datas.Dequeue();
            HandleMessage(data);
        }
    }
    public void ReceiveMessageFromServer()
    {
        //Debug.Log("开始接收数据");
        Array.Clear(Client.buffer,0,Client.buffer.Length);
        
        Client.socket.BeginReceive(Client.buffer, 0, Client.buffer.Length, SocketFlags.None, ReceiveMessageCallback, "");
    }
    public void ReceiveMessageCallback(IAsyncResult ar)
    {
        //Debug.Log("接收结束!!");
        //结束接收
        int length = Client.socket.EndReceive(ar);
        Debug.Log("接收的长度是:" + length);
        byte[] msg = Client.buffer;
        Data data = NetProtocol.UnPack(msg,Client.socket);
        datas.Enqueue(data);
        Debug.Log("服务器发过来的消息是:" + msg);
        
        //开启下一次消息的接收
        ReceiveMessageFromServer();

    }
    public void HandleMessage(Data data)
    {
        if(data.type == 2)
        {
            addListItem(Encoding.UTF8.GetString(data.msg));
        }
    }
    public void addListItem(string s)
    {
        Debug.Log("addListItem");
        GameObject newItem = Instantiate(ListItemPrefeb, new Vector3(0, 0, 0), Quaternion.identity);

        newItem.transform.SetParent(Content.transform);
        newItem.transform.GetComponent<TMP_Text>().text = s;
        Debug.Log("addListItem over");
    }
}

发表评论