Unity功能开发 多人聊天基于Socket的通讯功能

本文介绍了在异步网络框架中使用Socket进行实例化,包括AddressFamily、SocketType和ProtocolType的选择,以及IPEndPoint的使用。重点讲述了服务器端的异步操作,如单例模式、消息组装、回调处理和客户端连接。最后展示了客户端的交互过程,包括消息发送、接收和处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先看一下效果

先看Socket的实例化

Socket s_socket= new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

参数解释:

1、AddressFamily: Socket 地址簇 

2、SocketType:指定Socket类的实例表示的套接字的类型 

3、ProtocolType:Socket 类支持的协议

再看这个IPEndPoint地址的实例化

IPEndPoint作用是用来封装ip和端口号 以便于指定一个唯一设备

iPEndPoint = new IPEndPoint(127.0.0.1,9000);

参数解释:

1、ip地址

2、端口号

其次就是侦听的有一个客户端的一个数量限制

s_socket.Listen(10);

参数解释:

参数为0时,是不做数量限制;其次就是参数决定数量

编写服务器端

Socket里面有同步和异步之分 本次的服务器端是异步框架

编写服务器端泛型单例脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 异步框架练习1
{
    /// <summary>
    /// 泛型单例
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class Singleton<T>where T:class,new()
    {
        private static T t = default(T);
        public static T Instance
        {
            get
            {
                if (t == null)
                {
                    t = new T();
                }
                return t;
            }
        }
    }
}
编写服务器端组装消息体脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 异步框架练习1
{
    class PackMsg
    {
        //组装消息体
        public static byte[] PackMsgUtil(int id,byte[] msgBody)
        {
            ushort msgLen = (ushort)(4 + msgBody.Length);//获得消息的长度
            byte[] msgLenBuffer = BitConverter.GetBytes(msgLen);//获得消息长度的字节数组
            byte[] idBuffer = BitConverter.GetBytes(id); //获得协议id的字节数组
            byte[] fullMsgBuffer = new byte[0]; //记住这里是0
            fullMsgBuffer = fullMsgBuffer.Concat(msgLenBuffer).ToArray(); //组装消息头 
            fullMsgBuffer = fullMsgBuffer.Concat(idBuffer).ToArray(); //组装消息id
            fullMsgBuffer = fullMsgBuffer.Concat(msgBody).ToArray(); //组装pb消息体
            return fullMsgBuffer;
        }
    }
}
编写服务器端消息回调处理脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//
using PB;
using Google.Protobuf;

namespace 异步网络框架1
{
    public class NetHandler:Singleton<NetHandler>
    {
        /// <summary>
        /// 注册消息号 处理消息回调
        /// </summary>
        public void Register()
        {

        }

    }
}
编写服务器端管理类脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;

namespace 异步框架练习1
{
    //服务器类
    public class Client
    {
        //Soket
        public Socket client_socket;
        ///缓冲区
        public byte[] receiveBuffer = new byte[1024 * 2];
        //唯一ID
        public string unid;
        
        public void SendMsg(int id,byte[] msgBody)
        {
            byte[] msg = PackMsg.PackMsgUtil(id, msgBody);
            client_socket.BeginSend(msg, 0, msg.Length,SocketFlags.None,OnSend,null);
        }

        private void OnSend(IAsyncResult ar)
        {
            int len = client_socket.EndSend(ar);
        }
        
    }
    //网络管理类
    class NetMgr:Singleton<NetMgr>
    {
        private Socket server_Socket;
        public List<Client> lstClients = new List<Client>();
        public void InitGameServer(string ip, int port)
        {
            //构造
            server_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //bind
            IPEndPoint iPEnd = new IPEndPoint(IPAddress.Parse(ip), port);
            server_Socket.Bind(iPEnd);
            //listen
            server_Socket.Listen(0);
            //异步接收客户端链接
            server_Socket.BeginAccept(OnAccept, server_Socket);
            Console.WriteLine("异步游戏服务器启动成功!");
        }

        private void OnAccept(IAsyncResult ar)
        {
            Socket socket = server_Socket.EndAccept(ar); //回调
            Client client = new Client();
            client.client_socket = socket;
            client.unid = Guid.NewGuid().ToString();
            lstClients.Add(client);
            //上线提醒
            Console.WriteLine(string.Format("当前玩家的数量:{0}",lstClients.Count));
            server_Socket.BeginAccept(OnAccept, server_Socket);
            client.client_socket.BeginReceive(client.receiveBuffer, 0,client.receiveBuffer.Length,SocketFlags.None,OnReceive,client);
        }

        private void OnReceive(IAsyncResult ar)
        {
            Client client = ar.AsyncState as Client;
            try
            {
                int len = client.client_socket.EndReceive(ar);
                if(len>0)
                {
                    byte[] realBuffer = new byte[len];
                    Buffer.BlockCopy(client.receiveBuffer, 0, realBuffer, 0, len);
                    //粘包处理
                    if(realBuffer.Length>2)
                    {
                        ushort msgLen = BitConverter.ToUInt16(realBuffer, 0);
                        if(realBuffer.Length-2>=msgLen)
                        {
                            byte[] oneFullMsg = new byte[msgLen];
                            Buffer.BlockCopy(realBuffer, 2, oneFullMsg, 0,msgLen);
                            int id = BitConverter.ToInt32(oneFullMsg, 0);
                            byte[] msgBody = new byte[msgLen - 4];
                            //消息中心广播
                            MessageManager.Instance.BrocastMsg(id, msgBody, client);
                        }
                    }
                }
                if(len==0)
                {
                    NotifyOffline(client);
                    return;
                }
            }
            catch (Exception)
            {

                throw;
            }
        }
        ///下线提醒
        private void NotifyOffline(Client client)
        {
            Console.WriteLine(string.Format("下线客户端是:{0}",client.unid));
            lstClients.Remove(client);
        }
        //广播消息
        public void BrocastToAllClients(int id,byte[] msgBody)
        {
            for (int i = 0; i < lstClients.Count; i++)
            {
                lstClients[i].SendMsg(id, msgBody);
            }
        }
    }
}
编写服务器端消息号的管理类脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 异步框架练习1
{
    /// <summary>
    /// 协议id
    /// </summary>
    public class MsgID
    {
        //C To S 是服务器发送到客户端的消息号
        //S To C 是客户端发送到服务器的消息号
        public const int C2S_Chat = 1001;
        public const int S2C_Chat = 1002;
    }
}
编写服务器端消息中心脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 异步框架练习1
{
    /// <summary>
    /// 消息中心
    /// </summary>
    class MessageManager:Singleton<MessageManager>
    {
        public Dictionary<int, Action<byte[], Client>> dic = new Dictionary<int, Action<byte[], Client>>();

        public void AddMsg(int id,Action<byte[],Client> action)
        {
            if(!dic.ContainsKey(id))
            {
                dic.Add(id,action);
            }
        }

        public void RemoveMsg(int id)
        {
            if(dic.ContainsKey(id))
            {
                dic.Remove(id);
            }
        }

        public void BrocastMsg(int id,byte[] msgBody,Client client)
        {
            if(dic.ContainsKey(id))
            {
                Action<byte[], Client> action = dic[id];
                if(action!=null)
                {
                    action(msgBody, client);
                }
            }
        }
    }
}

编写客户端

编写客户端单例脚本

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


    public class Singleton<T>where T:class,new()
    {
        private static T t = default(T);
        public static T Instance
        {
            get
            {
                if(t==null)
                {
                    t = new T();
                }
                return t;
            }
        }
    }

编写客户端管理类脚本
using System.Collections.Generic;
//
using System.Net.Sockets;
using System;

/// <summary>
/// 网络管理
/// </summary>
public class NetMgr : Singleton<NetMgr>
{
    /// <summary>
    /// 客户端和服务器进行通信的套接字对象
    /// </summary>
    public Socket c_Socket;
    /// <summary>
    /// 进行通信的消息缓冲区
    /// </summary>
    public byte[] receiveBuffer = new byte[2048];
    /// <summary>
    /// 存储接受的消息数据
    /// </summary>
    public Queue<byte[]> msgQue = new Queue<byte[]>();

    /// <summary>
    /// 连接服务器
    /// </summary>
    /// <param name="ip">ip地址</param>
    /// <param name="port">端口号</param>
    public void ConnectGameServer(string ip, int port)
    {
        c_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//构造
        c_Socket.BeginConnect(ip, port, OnConnect, null);//异步连接
    }
    private void OnConnect(IAsyncResult ar)
    {
        c_Socket.EndConnect(ar);
        c_Socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, OnReceive, null);//开始接受消息
    }
    private void OnReceive(IAsyncResult ar)
    {
        int len = c_Socket.EndReceive(ar);
        if (len > 0)
        {
            byte[] realBuffer = new byte[len];
            Buffer.BlockCopy(receiveBuffer, 0, realBuffer, 0, len);
            if (realBuffer.Length > 2)
            {
                ushort msgLen = BitConverter.ToUInt16(realBuffer, 0);
                if (realBuffer.Length - 2 >= msgLen)
                {
                    byte[] oneFullMsg = new byte[msgLen];
                    Buffer.BlockCopy(realBuffer, 2, oneFullMsg, 0, msgLen);
                    msgQue.Enqueue(oneFullMsg);
                }
            }
        }
        c_Socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, OnReceive, null);//开始接受消息
    }
    /// <summary>
    /// 更新处理消息队列里面的消息,记住;如果不放在继承MonoBehaviour的update函数里面去调用的化,生成模型的时候,不报错,但是没有效果
    /// </summary>
    public void UpdateHandleMsg()
    {
        if (msgQue.Count > 0)
        {
            byte[] oneFullMsg = msgQue.Dequeue();
            int id = BitConverter.ToInt32(oneFullMsg, 0);
            byte[] msgBody = new byte[oneFullMsg.Length - 4];
            Buffer.BlockCopy(oneFullMsg, 4, msgBody, 0, msgBody.Length);
            MsgCenter.Instance.Brocast(id, msgBody);
        }
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="id">消息id</param>
    /// <param name="msgBody">消息携带的pb数据</param>
    public void SendMsg(int id, byte[] msgBody)
    {
        byte[] msgBuffer = NetMsgUntil.NetMsg(id, msgBody);
        c_Socket.BeginSend(msgBuffer, 0, msgBuffer.Length, SocketFlags.None, OnSend, null);
    }

    private void OnSend(IAsyncResult ar)
    {
        int len = c_Socket.EndSend(ar);
    }

    /// <summary>
    /// 客户端断开连接
    /// </summary>
    public void Disconnect()
    {
        if (c_Socket != null && c_Socket.Connected)//客户端套接字不为nll同时,为连接状态中
        {
            c_Socket.Shutdown(SocketShutdown.Both);
            c_Socket.Close();
        }
    }
}
编写客户端组装消息体脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


/// <summary>
/// 组装消息体
/// </summary>
    public class NetMsgUntil
    {
        public static byte[] NetMsg(int id,byte[] msgBody)
        {
            ushort msgLen = (ushort)(msgBody.Length + 4);
            byte[] msgBuffer = BitConverter.GetBytes(msgLen);
            byte[] idBuffer = BitConverter.GetBytes(id);
            byte[] fullBuffer = new byte[0];

            fullBuffer = fullBuffer.Concat(msgBuffer).ToArray();
            fullBuffer = fullBuffer.Concat(idBuffer).ToArray();
            fullBuffer = fullBuffer.Concat(msgBody).ToArray();
            return fullBuffer;
        }
    }

编写客户端处理消息脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Pb;
using Google.Protobuf;
using UnityEngine;

public class NetHandler : Singleton<NetHandler>
{

    /// <summary>
    /// 注册消息号 处理消息回调
    /// </summary>
    public void Register()
    {
    }

   
}

编写客户端的消息号脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


    public class MsgID
    {
        public const int C2S_Chet = 1001;
        public const int S2C_Chet = 1002;
    }

编写客户端的消息中心脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/// <summary>
/// 消息中心
/// </summary>
    public class MsgCenter:Singleton<MsgCenter>
    {
        public Dictionary<int, Action<byte[]>> dic = new Dictionary<int, Action<byte[]>>();
        public void AddMsg(int id,Action<byte[]> action)
        {
            if(!dic.ContainsKey(id))
            {
                dic.Add(id, action);
            }
        }

        public void Remove(int id)
        {
            if(dic.ContainsKey(id))
            {
                dic.Remove(id);
            }
        }

        public void Brocast(int id,byte[] msgBody)
        {
            if(dic.ContainsKey(id))
            {
                Action<byte[]> action = dic[id];
                if(action!=null)
                {
                    action(msgBody);
                }
            }
        }
    }

编写客户端的启动脚本

用来初始化连接服务器和初始化消息回调处理

(ip和端口号一定号对得上)

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

public class GameManager : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        NetMgr.Instance.ConnectGameServer("127.0.0.1", 9000);
        NetHandler.Instance.Register();
    }

    // Update is called once per frame
    void Update()
    {
        NetMgr.Instance.UpdateHandleMsg();
    }
    private void OnDestroy()
    {
        NetMgr.Instance.Disconnect();
    }
}

配置需要的库和PB数据

PB文件的编写(去VSCode上搜索proto3)

 然后CTRL+N新建项目  然后CTRL+S保存选择目录和保存格式(格式为protocol Buffers类型)

然后编写PB文件即可

由于是基础的聊天功能 需要的字段和数据两个即可

然后是lib库和工具的转换(把pb文件转为C#文件)

这个库和工具可以私聊我(可以讲解用法)

转换后基本就是这样

 这个文件在双端(客户端和服务器端)都拖一下

双端就可以使用在pb中定义的字段了

聊天功能逻辑编写

客户端发送消息到服务器端

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using PB;
using Google.Protobuf;

public class UIMgr : MonoBehaviour
{
    public Button btn_send; //发送按钮
    public InputField input_tx; //输入框
    public InputField input_show; //输入框
    public static UIMgr instance; //静态单例
    private void Awake()
    {
        instance = this;
    }
    public void Show(string str)
    {
        input_show.text = str;
    }
    // Start is called before the first frame update
    void Start()
    {
        btn_send.onClick.AddListener(OnSend);
    }

    private void OnSend()
    {
        if(!string.IsNullOrEmpty(input_tx.text))
        {
            C2S_Chet c2S_Chet = new C2S_Chet();
            c2S_Chet.CChet = input_tx.text;
            NetMgr.Instance.SendMsg(MsgID.C2S_Chet, c2S_Chet.ToByteArray());
        }
    }
}

在服务器端的处理消息的回调函数中处理消息 

然后再把消息广播给所有在线的客户端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//
using PB;
using Google.Protobuf;

namespace 异步网络框架1
{
    public class NetHandler:Singleton<NetHandler>
    {
        /// <summary>
        /// 注册消息号 处理消息回调
        /// </summary>
        public void Register()
        {
            MsgCenter.Instance.AddMsg(MsgID.C2S_Chet, OnChet);
        }
        private void OnChet(byte[] arg1, Client arg2)
        {
            Console.WriteLine(Encoding.UTF8.GetString(arg1));
            C2S_Chet c2S_Chet = C2S_Chet.Parser.ParseFrom(arg1);
            S2C_Chet s2C_Chet = new S2C_Chet();
            s2C_Chet.SChet = c2S_Chet.CChet;
            NetMgr.Instance.BocastToAll(MsgID.S2C_Chet, s2C_Chet.ToByteArray());
        }

    }
}

  再在客户端的消息处理回调函数中处理本消息号的协议

using System.Text;
using PB;
using Google.Protobuf;
using UnityEngine;
public class NetHandler : Singleton<NetHandler>
{
    /// <summary>
    /// 注册消息号 处理消息回调
    /// </summary>
    public void Register()
    {
        MsgCenter.Instance.AddMsg(MsgID.S2C_Chet, OnChet);
    }
    StringBuilder sb = new StringBuilder();
    private void OnChet(byte[] obj)
    {
        S2C_Chet s2C_Chet = S2C_Chet.Parser.ParseFrom(obj);
        Debug.Log("发送数据成功!");
        sb.AppendLine(s2C_Chet.SChet);
        UIMgr.instance.Show(sb.ToString());
    }
}

打包exe

 测试功能

先开启服务器端   在这个脚本中开启

 然后再打开客户端打成的exe运行程序(通常为2个用于测试)

然后就是整体的效果了 

谢谢观看--

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值