当前位置: 首页 > article >正文

【Unity网络编程知识】使用Socket实现简单UDP通讯

1、Socket的常用属性和方法

1.1 常用属性

1)套接字的连接状态

socketTcp.Connected

2)获取套接字的类型

socketTcp.SocketType

3)获取套接字的协议类型

socketTcp.ProtocolType

4)获取套接字的寻址方案

socketTcp.AddressFamily

5)从网络中获取准备读取的数据数据量

socketTcp.Available

6)获取本机EndPoint对象(注意:IPEndPoint继承EndPoint)

socketTcp.LocalEndPoint as IPEndPoint

7)获取远程EndPoint对象

socketTcp.RemoteEndPoint as IPEndPoint;

1.2 同步常用方法

服务端和客户端操作流程一样

1)绑定IP和端口, Bind(ip地址和端口),绑定的是本机的

IPEndPoint iPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
socket.Bind(iPoint);

2)发送消息,SendTo

socket.SendTo(Encoding.UTF8.GetBytes("欢迎发送消息给服务器"), remoteIpPoint2);

3)接受消息,ReceiveFrom

EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);
int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2);

4)释放连接并关闭socket,先于close调用

socket.Shutdown(SocketShutdown.Both);

5)关闭连接,释放所有Socket管理资源 

socket.Close();

1.3 异步常用方法

1)发送消息

发送消息方式1,BeginSendTo和EndSendTo

        byte[] bytes = Encoding.UTF8.GetBytes("dsjkdjs13");
        EndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
        socket.BeginSendTo(bytes, 0, bytes.Length, SocketFlags.None, ipPoint, SendToOver, socket);

    private void SendToOver(IAsyncResult result)
    {
        try
        {
            Socket s = result.AsyncState as Socket;
            s.EndSendTo(result);
            print("发送成功");
        }
        catch (SocketException e)
        {
            print("发送失败" + e.SocketErrorCode + e.Message);
        }
    }

发送消息方式2, SendToAsync

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        //设置要发送的数据
        args.SetBuffer(bytes, 0, bytes.Length);
        //设置完成事件
        args.Completed += SendToAsync;
        socket.SendToAsync(args);

    private void SendToAsync(object s, SocketAsyncEventArgs args)
    {
        if(args.SocketError == SocketError.Success)
        {
            print("发送成功");
        }
        else
        {
            print("发送失败");
        }
    }

2)接收消息

接收消息方式1,BeginReceiveFrom和EndReceiveFrom

socket.BeginReceiveFrom(cacheBytes, 0, cacheBytes.Length, SocketFlags.None, ref ipPoint, ReceiveFromOver, (socket, ipPoint));

    private void ReceiveFromOver(IAsyncResult result)
    {
        try
        {
            (Socket s, EndPoint ipPoint) info = ((Socket, EndPoint))result.AsyncState;
            //返回值 就是接受了多少个 字节
            int num = info.s.EndReceiveFrom(result, ref info.ipPoint);
            //处理消息

            //处理完消息 又继续接受消息
            info.s.BeginReceiveFrom(cacheBytes, 0, cacheBytes.Length, SocketFlags.None, ref info.ipPoint, ReceiveFromOver, info);
        }
        catch (SocketException e)
        {
            print("接受消息出问题" + e.SocketErrorCode + e.Message);
        }
    }

接收消息方式2,ReceiveFromAsync

        SocketAsyncEventArgs args2 = new SocketAsyncEventArgs();
        //设置接收消息的容器
        args2.SetBuffer(cacheBytes, 0, cacheBytes.Length);
        args2.Completed += ReceiveFromAsync;
        socket.ReceiveFromAsync(args2);


    private void ReceiveFromAsync(object s, SocketAsyncEventArgs args)
    {
        if(args.SocketError != SocketError.Success)
        {
            print("接受成功");
            //具体收到多少个字节
            //args.BytesTransferred

            //可以通过以下两种方式获取到收到的字节数组内容
            //args.Buffer
            //cacheBytes

            //解析消息

            Socket socket = s as Socket;
            //只需要设置 从第几个位置开始接受 能接几个
            args.SetBuffer(0, args.Buffer.Length);
            socket.ReceiveFromAsync(args);
        }
        else
        {
            print("接受失败");
        }
    }

 2、Socket实现UDP通讯流程

2.1 服务端

1)创建套接字

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

2)绑定本机地址

IPEndPoint iPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
socket.Bind(iPoint);

3)接受消息

            byte[] bytes = new byte[512];
            //这个变量主要用来记录 谁发的消息 传入函数后 在内部 会帮助我们赋值
            EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);
            int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2);

4)发送消息到指定目标

socket.SendTo(Encoding.UTF8.GetBytes("欢迎发送消息给服务器"), remoteIpPoint2);

5)释放关闭

            socket.Shutdown(SocketShutdown.Both);
            socket.Close();

2.2 客户端

与服务端一样流程

3、简单实现UDP通讯完整代码

3.1 消息类型区分代码

1)BaseData
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
 
public abstract class BaseData
{
    /// <summary>
    /// 用于子类重写的 获取字节数组容器大小的方法
    /// </summary>
    /// <returns></returns>
    public abstract int GetBytesNum();
 
    /// <summary>
    /// 把成员变量序列化为 对应的字节数组
    /// </summary>
    /// <returns></returns>
    public abstract byte[] Writing();
 
    /// <summary>
    /// 把二进制字节数组 反序列化 到成员变量当中
    /// </summary>
    /// <param name="bytes">反序列化使用的字节数组</param>
    /// <param name="beginIndex">从该字节数组的第几个位置开始解析 默认是 0</param>
    public abstract int Reading(byte[] bytes, int beginIndex = 0);
 
    /// <summary>
    /// 存储int类型变量到指定的字节数组
    /// </summary>
    /// <param name="bytes">指定字节数组</param>
    /// <param name="value">具体的int值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteInt(byte[] bytes, int value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(int);
    }
    protected void WriteShort(byte[] bytes, short value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(short);
    }
    protected void WriteLong(byte[] bytes, long value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(long);
    }
    protected void WriteFloat(byte[] bytes, float value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(float);
    }
    protected void WriteByte(byte[] bytes, byte value, ref int index)
    {
        bytes[index] = value;
        index += sizeof(byte);
    }
    protected void WriteBool(byte[] bytes, bool value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, (int)index);
        index += sizeof(bool);
    }
    protected void WriteString(byte[] bytes, string value, ref int index)
    {
        //先存储string字节数组长度
        byte[] strBytes = Encoding.UTF8.GetBytes(value);
        int num = strBytes.Length;
        BitConverter.GetBytes(num).CopyTo(bytes, index);
        index += sizeof(int);
        //在存储 string 字节数组
        strBytes.CopyTo(bytes, index);
        index += num;
    }
    protected void WriteData(byte[] bytes, BaseData data, ref int index)
    {
        data.Writing().CopyTo(bytes, index);
        index += data.GetBytesNum();
    }
 
    /// <summary>
    /// 根据字节数组读取整形
    /// </summary>
    /// <param name="bytes">读取数组</param>
    /// <param name="index">开始读取的索引位置</param>
    /// <returns></returns>
    protected int ReadInt(byte[] bytes, ref int index)
    {
        int value = BitConverter.ToInt32(bytes, index);
        index += sizeof(int);
        return value;
    }
    protected short ReadShort(byte[] bytes, ref int index)
    {
        short value = BitConverter.ToInt16(bytes, index);
        index += sizeof(short);
        return value;
    }
    protected long ReadLong(byte[] bytes, ref int index)
    {
        long value = BitConverter.ToInt64(bytes, index);
        index += sizeof(long);
        return value;
    }
    protected float ReadFloat(byte[] bytes, ref int index)
    {
        float value = BitConverter.ToSingle(bytes, index);
        index += sizeof(float);
        return value;
    }
    protected byte ReadByte(byte[] bytes, ref int index)
    {
        byte b = bytes[index];
        index++;
        return b;
    }
    protected bool ReadBool(byte[] bytes, ref int index)
    {
        bool value = BitConverter.ToBoolean(bytes, index);
        index += sizeof(bool);
        return value;
    }
    protected string ReadString(byte[] bytes, ref int index)
    {
        int length = BitConverter.ToInt32(bytes, index);
        index += sizeof(int);
        string value = Encoding.UTF8.GetString(bytes, index, length);
        index += length;
        return value;
    }
    protected T ReadData<T>(byte[] bytes, ref int index) where T : BaseData, new()
    {
        T value = new T();
        index += value.Reading(bytes, index);
        return value;
    }
 
}
2)BaseMsg
using System.Collections;
using System.Collections.Generic;
 
public class BaseMsg : BaseData
{
    public override int GetBytesNum()
    {
        throw new System.NotImplementedException();
    }
 
    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        throw new System.NotImplementedException();
    }
 
    public override byte[] Writing()
    {
        throw new System.NotImplementedException();
    }
 
    public virtual int GetID()
    {
        return 0;
    }
}
3)PlayerData
using System.Collections;
using System.Collections.Generic;
using System.Text;
 
/// <summary>
/// 玩家数据类
/// </summary>
public class PlayerData : BaseData
{
    public string name;
    public int atk;
    public int lev;
 
    public override int GetBytesNum()
    {
        return 4 + 4 + 4 + Encoding.UTF8.GetBytes(name).Length;
    }
 
    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        int index = beginIndex;
        name = ReadString(bytes, ref index);
        atk = ReadInt(bytes, ref index);
        lev = ReadInt(bytes, ref index);
        return index - beginIndex;
    }
 
    public override byte[] Writing()
    {
        int index = 0;
        byte[] bytes = new byte[GetBytesNum()];
        WriteString(bytes, name, ref index);
        WriteInt(bytes, atk, ref index);
        WriteInt(bytes, lev, ref index);
        return bytes;
    }
}
4)PlayerMsg
using System.Collections;
using System.Collections.Generic;
 
public class PlayerMsg : BaseMsg
{
    public int playerID;
    public PlayerData playerData;
 
    public override byte[] Writing()
    {
        int index = 0;
        byte[] bytes = new byte[GetBytesNum()];
        //先写消息ID
        WriteInt(bytes, GetID(), ref index);
        //写消息的成员变量
        WriteInt(bytes, playerID, ref index);
        WriteData(bytes, playerData, ref index);
        return bytes;
    }
 
    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        //反序列化不需要取解析ID 因为在这一步之前 就应该把ID反序列化出来
        //用来判断到底使用哪一个自定义类来反序列化
        int index = beginIndex;
        playerID = ReadInt(bytes, ref index);
        playerData = ReadData<PlayerData>(bytes, ref index);
        return index - beginIndex;
    }
 
    public override int GetBytesNum()
    {
        return 4 + //消息ID长度
               4 + //palyerID
               playerData.GetBytesNum(); //playerData
    }
 
    /// <summary>
    /// 自定义的消息ID 主要用于区分是哪一个消息类
    /// </summary>
    /// <returns></returns>
    public override int GetID()
    {
        return 1001;
    }
}
5)QuitMsg
using System.Collections;
using System.Collections.Generic;
 
public class QuitMsg : BaseMsg
{
    public override int GetBytesNum()
    {
        return 8;
    }
 
    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        return 0;
    }
 
    public override byte[] Writing()
    {
        int index = 0;
        byte[] bytes = new byte[GetBytesNum()];
        WriteInt(bytes, GetID(), ref index);
        WriteInt(bytes, 0, ref index);
        return bytes;
    }
 
    public override int GetID()
    {
        return 1003;
    }
}
6)HeartMsg
using System.Collections;
using System.Collections.Generic;
 
public class HeartMsg : BaseMsg
{
    public override int GetBytesNum()
    {
        return 8;
    }
 
    public override byte[] Writing()
    {
        int index = 0;
        byte[] bytes = new byte[GetBytesNum()];
        WriteInt(bytes, GetID(), ref index);
        WriteInt(bytes, 0, ref index);
        return bytes;
    }
 
    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        return 0;
    }
 
    public override int GetID()
    {
        return 999;
    }
}

3.2 服务端

1)Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TeachUdpServerExercises
{
    //用于记录和服务器通信过的客户端的IP和端口
    class Client
    {
        public IPEndPoint clientIPandPort;
        public string clientStrID;

        //上一次收到消息的时间
        public long frontTime = -1;

        public Client(string ip, int port)
        {
            //规则和外面一样 记录唯一ID 通过IP+端口拼接的形式
            clientStrID = ip + port;
            //记录客户端信息
            clientIPandPort = new IPEndPoint(IPAddress.Parse(ip), port);
        }

        public void ReceiveMsg(byte[] bytes)
        {
            //为了避免处理消息时 又 接受到其他消息 所以需要在处理之前 先把信息拷贝出来
            //处理消息和接受信息 用不同的容器 避免出现问题
            byte[] cacheBytes = new byte[512];
            bytes.CopyTo(cacheBytes, 0);
            //记录收到消息的 系统时间 单位为秒
            frontTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
            ThreadPool.QueueUserWorkItem(ReceiveHandle, cacheBytes);

        }

        //多线程处理消息
        private void ReceiveHandle(object obj)
        {
            try
            {
                //取出传进来的字节
                byte[] bytes = obj as byte[];
                int nowIndex = 0;

                //先处理 ID
                int msgID = BitConverter.ToInt32(bytes, nowIndex);
                nowIndex += 4;
                //再处理 长度
                int msgLength = BitConverter.ToInt32(bytes, nowIndex);
                nowIndex += 4;
                //再解析消息体
                switch (msgID)
                {
                    case 1001:
                        PlayerMsg playerMsg = new PlayerMsg();
                        playerMsg.Reading(bytes, nowIndex);
                        Console.WriteLine(playerMsg.playerID);
                        Console.WriteLine(playerMsg.playerData.name);
                        Console.WriteLine(playerMsg.playerData.atk);
                        Console.WriteLine(playerMsg.playerData.lev);
                        break;
                    case 1003:
                        QuitMsg quitMsg = new QuitMsg();
                        //由于它没有消息体 所以不用反序列化
                        //quitMsg.Reading(bytes, nowIndex);
                        //处理退出
                        Program.serverSocket.RemoveClient(clientStrID);
                        break;
                    default:
                        break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("处理消息是出错" + e.Message);
                //如果出错就不用记录这个客户端信息
                Program.serverSocket.RemoveClient(clientStrID);
            }
        }

    }
}
2)ServerSocket
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TeachUdpServerExercises
{
    class ServerSocket
    {
        private Socket socket;

        private bool isClose;

        //我们可以通过记录谁给我发了消息 把它的 IP和端口记下来 这样就认为它是我的客户端
        private Dictionary<string, Client> clientDic = new Dictionary<string, Client>();

        public void Start(string ip, int port)
        {
            IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            //声明一个用于UDP通信的Socket
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            try
            {
                socket.Bind(ipPoint);
                isClose = false;
                //消息接受的处理
                ThreadPool.QueueUserWorkItem(ReceiveMsg);
                //定时检测超时线程
                ThreadPool.QueueUserWorkItem(CheckTimeOut);
            }
            catch (Exception e)
            {
                Console.WriteLine("UDP开启出错" + e.Message);
            }
        }

        private void CheckTimeOut(object obj)
        {
            long nowTime = 0;
            List<string> delList = new List<string>();
            while (true)
            {
                //每30秒检测一次 是否移除长时间没有接受到消息的客户端信息
                Thread.Sleep(30000);
                //当前系统时间
                nowTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
                foreach (Client c in clientDic.Values)
                {
                    //超过10秒没有收到消息的客户端 需要被移除
                    if(nowTime - c.frontTime >= 10)
                    {
                        delList.Add(c.clientStrID);
                    }
                }

                //从待删除列表中移除 超时的客户端信息
                for (int i = 0; i < delList.Count; i++)
                    RemoveClient(delList[i]);

                delList.Clear();
            }
        }

        private void ReceiveMsg(object obj)
        {
            //接受消息的容器
            byte[] bytes = new byte[512];
            //记录谁发的
            EndPoint ipPoint = new IPEndPoint(IPAddress.Any, 0);
            //用于拼接字符串 唯一ID 是由 IP + 端口构成
            string strID = "";
            string ip;
            int port;
            while (!isClose)
            {
                if(socket.Available > 0)
                {
                    lock(socket)
                        socket.ReceiveFrom(bytes, ref ipPoint);
                    //处理消息 最好不要在这直接处理 而是交给 客户端对象处理
                    //收到消息时 我们可以来判断 是不是记录了这个客户端信息   (IP和端口)
                    //取出发送的 IP和端口
                    ip = (ipPoint as IPEndPoint).Address.ToString();
                    port = (ipPoint as IPEndPoint).Port;
                    strID = ip + port;//拼接成一个唯一ID
                    //判断有没有记录这个客户端信息 如果有 用它直接处理消息
                    if (clientDic.ContainsKey(strID))
                        clientDic[strID].ReceiveMsg(bytes);
                    //如果没有 直接添加并处理消息
                    else
                    {
                        clientDic.Add(strID, new Client(ip, port));
                        clientDic[strID].ReceiveMsg(bytes);
                    }
                }
            }
        }

        //指定发送一个消息给某个目标
        public void SendTo(BaseMsg msg, IPEndPoint ipPoint)
        {
            try
            {
                lock(socket)
                    socket.SendTo(msg.Writing(), ipPoint);
            }
            catch (SocketException s)
            {
                Console.WriteLine("发消息出现问题" + s.SocketErrorCode + s.Message);
            }
            catch(Exception e)
            {
                Console.WriteLine("发消息出现问题(可能是序列化问题)" + e.Message);
            }
        }

        public void Broadcast(BaseMsg msg)
        {
            //广播消息 给谁广播
            foreach (Client c in clientDic.Values)
            {
                SendTo(msg, c.clientIPandPort);
            }
        }

        public void Close()
        {
            if(socket != null)
            {
                isClose = true;
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
                socket = null;
            }
        }

        public void RemoveClient(string clientID)
        {
            if (clientDic.ContainsKey(clientID))
            {
                Console.WriteLine("客户端{0}被移除了", clientDic[clientID].clientIPandPort);
                clientDic.Remove(clientID);
            }
        }

    }
}
 3)启动代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TeachUdpServerExercises
{
    class Program
    {
        public static ServerSocket serverSocket;

        static void Main(string[] args)
        {
            #region UDP服务器要求
            //如同TCP通信一样让UDP服务端可以服务多个客户端
            //需要具备的功能有:
            //1.区分消息类型(不需要处理分包、黏包)
            //2.能够接受多个客户端的消息
            //3.能够主动给发送过消息给自己的客户端发消息(记录客户端信息)
            //4.主动记录上一次收到客户端消息的时间,如果长时间没有收到消息,主动移除记录的客户端信息

            // 分析:
            //1.UDP是无连接的,我们如何记录连入的客户端?
            //2.UDP收发消息都是通过一个Socket来进行处理,我们应该如何处理收发消息?
            //3.如果不使用心跳消息,我们如何记录上次收到消息的时间?

            serverSocket = new ServerSocket();
            serverSocket.Start("127.0.0.1", 8080);

            Console.WriteLine("UDP服务器启动了");

            while (true)
            {
                string input = Console.ReadLine();
                if(input.Substring(0, 2) == "B:")
                {
                    PlayerMsg msg = new PlayerMsg();
                    msg.playerData = new PlayerData();
                    msg.playerID = 100;
                    msg.playerData.name = "帅哥的UDP服务器";
                    msg.playerData.atk = 999;
                    msg.playerData.lev = 100;

                    serverSocket.Broadcast(msg);
                }
            }

            #endregion
        }
    }
}

3.3 客户端

1)UdpNetMgr
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;

public class UdpNetMgr : MonoBehaviour
{
    private static UdpNetMgr instance;
    public static UdpNetMgr Instance => instance;

    private EndPoint serverIpPoint;

    private Socket socket;

    //客户端Socket是否关闭
    private bool isClose = true;

    //两个容器 队列
    //接受和发送消息的队列 在多线程里面可以操作
    private Queue<BaseMsg> sendQueue = new Queue<BaseMsg>();
    private Queue<BaseMsg> receiveQueue = new Queue<BaseMsg>();

    private byte[] cacheBytes = new byte[512];

    private void Awake()
    {
        instance = this;
        DontDestroyOnLoad(gameObject);
    }

    private void Update()
    {
        if(receiveQueue.Count > 0)
        {
            BaseMsg baseMsg = receiveQueue.Dequeue();
            switch (baseMsg)
            {
                case PlayerMsg msg:
                    print(msg.playerID);
                    print(msg.playerData.name);
                    print(msg.playerData.atk);
                    print(msg.playerData.lev);
                    break;
            }
        }
    }

    /// <summary>
    /// 启动客户端socket相关方法
    /// </summary>
    /// <param name="ip">远端服务器的IP</param>
    /// <param name="port">远端服务器的端口</param>
    public void StartClient(string ip, int port)
    {
        //如果当前是开启状态 就不用在开启了
        if (!isClose)
            return;

        //先记录服务器地址,后面发送消息会使用
        serverIpPoint = new IPEndPoint(IPAddress.Parse(ip), port);

        IPEndPoint clientIpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);

        try
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.Bind(clientIpPoint);
            isClose = false;
            print("客户端网络启动");

            ThreadPool.QueueUserWorkItem(ReceiveMsg);
            ThreadPool.QueueUserWorkItem(SendMsg);
        }
        catch (System.Exception e)
        {
            print("启动Socket出问题" + e.Message);
        }
    }

    private void ReceiveMsg(object obj)
    {
        EndPoint tempIpPoint = new IPEndPoint(IPAddress.Any, 0);
        int nowIndex;
        int msgID;
        int msgLength;

        while (!isClose)
        {
            if(socket != null && socket.Available > 0)
            {
                try
                {
                    socket.ReceiveFrom(cacheBytes, ref tempIpPoint);
                    //为了避免处理 非服务器发来的 骚扰消息
                    if (!tempIpPoint.Equals(serverIpPoint))
                    {
                        //如果发现 发消息的不是服务器 证明是骚扰消息 不用处理
                        continue;
                    }

                    //处理服务器发来的消息
                    nowIndex = 0;
                    //解析ID
                    msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
                    nowIndex += 4;
                    //解析长度
                    msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
                    nowIndex += 4;
                    //解析消息体
                    BaseMsg msg = null;
                    switch (msgID)
                    {
                        case 1001:
                            msg = new PlayerMsg();
                            //反序列化消息体
                            msg.Reading(cacheBytes, nowIndex);
                            break;
                    }
                    if(msg != null) 
                        receiveQueue.Enqueue(msg);
                }
                catch (SocketException s)
                {
                    print("接受消息出问题" + s.SocketErrorCode + s.Message);
                }
                catch (Exception e)
                {
                    print("接受消息出问题(非网络问题)" + e.Message);
                }
            }
        }
    }

    private void SendMsg(object obj)
    {
        while (!isClose)
        {
            if (socket != null && sendQueue.Count > 0)
            {
                try
                {
                    socket.SendTo(sendQueue.Dequeue().Writing(), serverIpPoint);
                }
                catch (SocketException e)
                {
                    print("发送消息出错" + e.SocketErrorCode + e.Message);
                }
            }
        }
    }

    //发送消息
    public void Send(BaseMsg msg)
    {
        sendQueue.Enqueue(msg);
    }

    //关闭socket
    public void Close()
    {
        if(socket != null)
        {
            //发送一个退出消息给服务器 让其移除记录
            QuitMsg msg = new QuitMsg();
            socket.SendTo(msg.Writing(), serverIpPoint);
            isClose = true;
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            socket = null;
        }
    }

    private void OnDestroy()
    {
        Close();
    }
}
2)启动代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainUdp : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        if(UdpNetMgr.Instance == null)
        {
            GameObject obj = new GameObject("UdpNet");
            obj.AddComponent<UdpNetMgr>();
        }

        UdpNetMgr.Instance.StartClient("127.0.0.1", 8080);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
3)消息发送测试代码

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

public class Lesson15 : MonoBehaviour
{
    public Button btnSend;

    // Start is called before the first frame update
    void Start()
    {
        btnSend.onClick.AddListener(() =>
        {
            PlayerMsg msg = new PlayerMsg();
            msg.playerData = new PlayerData();
            msg.playerID = 1;
            msg.playerData.name = "小周的客户端";
            msg.playerData.atk = 999;
            msg.playerData.lev = 100;

            UdpNetMgr.Instance.Send(msg);
        });
    }

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


http://www.kler.cn/a/613892.html

相关文章:

  • VSCode中使用Markdown以及Mermaid实现流程图和甘特图等效果
  • Unity中实现UI的质感和圆角
  • parallelStream线程问题及解决方案
  • 从入门到精通:HTML 项目实战中的学习进度(二)
  • AI: 文生视频的主流产品
  • Github Webhook 以及主动式
  • 免费OpenAI gpt-4o-mini-tts API调用(已开源)
  • 分布式锁,rediss,redisson,看门狗,可重入,可重试
  • 【实战ES】实战 Elasticsearch:快速上手与深度实践-2.2.1 Bulk API的正确使用与错误处理
  • Open GL ES ->模型矩阵、视图矩阵、投影矩阵等变换矩阵数学推导以及方法接口说明
  • 信息学奥赛一本通 1514:【例 2】最大半连通子图 | 洛谷 P2272 [ZJOI2007] 最大半连通子图
  • Emacs 折腾日记(二十)——修改emacs的一些默认行为
  • 【C++项目实战】:基于正倒排索引的Boost搜索引擎(1)
  • s1: Simple test-time scaling 【论文阅读笔记】
  • PPTP、L2TP 和 IPSec
  • PyTorch 分布式训练(Distributed Data Parallel, DDP)简介
  • 在IDEA中快速注释所有console.log
  • Taro创建微信小程序项目 第一步搭建项目
  • 掌握!Postman 设置 Bearer Token 的完整指南
  • 3d pose 指标和数据集