39.UDP异步客户端

39.网络通信-套接字Socket-UDP通信-异步-客户端综合练习题


39.1 知识点

使用UDP通信中Async相关异步方法,将UDP同步通信中客户端综合练习改为使用异步方法通信。

定义UdpNetAsyncManager类,实现继承Monobehaviour的单例。定义必要的变量。

public class UdpNetAsyncManager : BaseSingletonInMonoBehaviour<UdpNetAsyncManager>
{
    private EndPoint serverIpPoint;// 服务器的IP地址和端口信息

    private Socket socket; // 客户端Socket

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

    //发送消息的队列 在多线程里面可以操作
    private Queue<BaseMessage> receiveQueue = new Queue<BaseMessage>();

    private byte[] cacheBytes = new byte[512];// 用于缓存接收的字节数据
}

实现启动客户端和关闭客户端方法。销毁时需要调用关闭客户端。

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

    // 创建一个表示服务器地址的IPEndPoint对象,稍后用于发送消息
    serverIpPoint = new IPEndPoint(IPAddress.Parse(ip), port);

    // 创建表示客户端的本地地址和端口的IPEndPoint对象
    IPEndPoint clientIpPort = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
    try
    {
        // 创建UDP套接字
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        // 将套接字绑定到客户端的本地地址和端口
        socket.Bind(clientIpPort);
        isClose = false;

        // 创建SocketAsyncEventArgs对象来接收数据
        SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();
        socketAsyncEventArgs.SetBuffer(cacheBytes, 0, cacheBytes.Length);
        socketAsyncEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

        // 设置接收完成后的事件处理函数
        socketAsyncEventArgs.Completed += ReceiveMsg;

        // 开始异步接收数据
        socket.ReceiveFromAsync(socketAsyncEventArgs);

        // 输出调试信息
        print("客户端网络启动");
    }
    catch (System.Exception e)
    {
        // 发生异常时输出错误信息
        print("启动Socket出问题" + e.Message);
    }
}

// 关闭socket
public void Close()
{
    if (socket != null)
    {
        isClose = true;

        // 创建一个退出消息对象
        QuitMessage quitMessage = new QuitMessage();

        // 向服务器发送退出消息,以通知服务器移除客户端记录
        socket.SendTo(quitMessage.Writing(), serverIpPoint);

        // 关闭套接字的发送和接收,然后关闭套接字
        socket.Shutdown(SocketShutdown.Both);
        socket.Close();
        socket = null;
    }
}

// 当对象被销毁时自动调用Close方法,确保关闭socket
private void OnDestroy()
{
    Close();
}

实现收发消息方法

//收消息
private void ReceiveMsg(object obj, SocketAsyncEventArgs socketAsyncEventArgs)
{
    // 声明变量以存储消息的当前索引、消息ID和消息长度
    int nowIndex;
    int msgID;
    int msgLength;

    // 检查套接字异步事件是否成功
    if (socketAsyncEventArgs.SocketError == SocketError.Success)
    {
        try
        {
            // 检查接收到的消息是否来自服务器
            if (socketAsyncEventArgs.RemoteEndPoint.Equals(serverIpPoint))
            {
                // 初始化当前索引
                nowIndex = 0;

                // 解析消息ID(4字节整数)
                msgID = BitConverter.ToInt32(socketAsyncEventArgs.Buffer, nowIndex);
                nowIndex += 4;

                // 解析消息长度(4字节整数)
                msgLength = BitConverter.ToInt32(socketAsyncEventArgs.Buffer, nowIndex);
                nowIndex += 4;

                // 创建一个消息对象用于存储接收到的消息
                BaseMessage baseMessage = null;

                // 根据消息ID执行相应的处理
                switch (msgID)
                {
                    case 1001:
                        // 如果消息ID是1001,创建一个玩家消息对象
                        baseMessage = new PlayerMessage();

                        // 反序列化消息体
                        baseMessage.Reading(socketAsyncEventArgs.Buffer, nowIndex);
                        break;
                }

                // 如果消息对象不为空,将其加入接收队列
                if (baseMessage != null)
                    receiveQueue.Enqueue(baseMessage);
            }

            // 如果套接字仍处于打开状态且不被关闭
            if (socket != null && !isClose)
            {
                // 重置异步事件的缓冲区以接收更多数据
                socketAsyncEventArgs.SetBuffer(0, cacheBytes.Length);

                // 继续异步接收数据
                socket.ReceiveFromAsync(socketAsyncEventArgs);
            }
        }
        catch (SocketException s)
        {
            // 处理套接字异常,通常在接收消息时发生
            print("接收消息出错" + s.SocketErrorCode + s.Message);

            // 关闭套接字
            Close();
        }
        catch (Exception e)
        {
            // 处理其他异常,可能是反序列化问题
            print("接收消息出错(可能是反序列化问题)" + e.Message);

            // 关闭套接字
            Close();
        }
    }
    else
    {
        // 打印套接字错误,表示接收消息失败
        print("接收消息失败" + socketAsyncEventArgs.SocketError);
    }
}

//每帧检查消息列表
void Update()
{
    // 如果接收队列中有待处理的消息
    if (receiveQueue.Count > 0)
    {
        // 从队列中取出下一个消息
        BaseMessage baseMessage = receiveQueue.Dequeue();

        // 根据消息类型进行处理
        switch (baseMessage)
        {
            case PlayerMessage playerMessage:
                // 处理玩家消息,打印相关信息
                print(playerMessage.playerID);
                print(playerMessage.playerData.name);
                print(playerMessage.playerData.atk);
                print(playerMessage.playerData.lev);
                break;
        }
    }
}

// 发送消息
public void Send(BaseMessage baseMessage)
{
    try
    {
        // 检查套接字是否存在且未关闭
        if (socket != null && !isClose)
        {
            // 创建一个SocketAsyncEventArgs对象用于发送消息
            SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();

            // 将消息对象序列化为字节数组
            byte[] bytes = baseMessage.Writing();

            // 设置SocketAsyncEventArgs的缓冲区以包含要发送的字节数组
            socketAsyncEventArgs.SetBuffer(bytes, 0, bytes.Length);

            // 设置发送完成后的回调函数
            socketAsyncEventArgs.Completed += SendToCallBack;

            // 设置远端目标,即服务器的IP和端口
            socketAsyncEventArgs.RemoteEndPoint = serverIpPoint;

            // 发送消息
            socket.SendToAsync(socketAsyncEventArgs);
        }
    }
    catch (SocketException s)
    {
        // 处理套接字异常,通常在发送消息时发生
        print("发送消息出错" + s.SocketErrorCode + s.Message);
    }
    catch (Exception e)
    {
        // 处理其他异常,可能是序列化问题
        print("发送消息出错(可能是序列化问题)" + e.Message);
    }
}

// 发送消息后的回调函数
private void SendToCallBack(object o, SocketAsyncEventArgs socketAsyncEventArgs)
{
    // 如果发送消息过程中出现套接字错误
    if (socketAsyncEventArgs.SocketError != SocketError.Success)
    {
        // 打印错误信息,表示发送消息失败
        print("发送消息失败" + socketAsyncEventArgs.SocketError);
    }
}

客户端入口连接服务端,并定义发消息测试按钮

public InputField InputField;
public Button sendButton;
void Start()
{
    UdpNetAsyncManager.Instance.StartClient("127.0.0.1", 8080);

    sendButton.onClick.AddListener(() =>
    {
        PlayerMessage playerMessage = new PlayerMessage();
        playerMessage.playerData = new PlayerData();
        playerMessage.playerID = 1;
        playerMessage.playerData.name = "韬老狮的UDP客户端发的异步消息";
        playerMessage.playerData.atk = 888;
        playerMessage.playerData.lev = 666;
        UdpNetAsyncManager.Instance.Send(playerMessage);
    });
}

开启UDP服务端和客户端进行测试


39.2 知识点代码

UdpNetAsyncManager

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

public class UdpNetAsyncManager : BaseSingletonInMonoBehaviour<UdpNetAsyncManager>
{
    private EndPoint serverIpPoint;// 服务器的IP地址和端口信息

    private Socket socket; // 客户端Socket

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

    //发送消息的队列 在多线程里面可以操作
    private Queue<BaseMessage> receiveQueue = new Queue<BaseMessage>();

    private byte[] cacheBytes = new byte[512];// 用于缓存接收的字节数据

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

        // 创建一个表示服务器地址的IPEndPoint对象,稍后用于发送消息
        serverIpPoint = new IPEndPoint(IPAddress.Parse(ip), port);

        // 创建表示客户端的本地地址和端口的IPEndPoint对象
        IPEndPoint clientIpPort = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
        try
        {
            // 创建UDP套接字
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            // 将套接字绑定到客户端的本地地址和端口
            socket.Bind(clientIpPort);
            isClose = false;

            // 创建SocketAsyncEventArgs对象来接收数据
            SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();
            socketAsyncEventArgs.SetBuffer(cacheBytes, 0, cacheBytes.Length);
            socketAsyncEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

            // 设置接收完成后的事件处理函数
            socketAsyncEventArgs.Completed += ReceiveMsg;

            // 开始异步接收数据
            socket.ReceiveFromAsync(socketAsyncEventArgs);

            // 输出调试信息
            print("客户端网络启动");
        }
        catch (System.Exception e)
        {
            // 发生异常时输出错误信息
            print("启动Socket出问题" + e.Message);
        }
    }

    // 关闭socket
    public void Close()
    {
        if (socket != null)
        {
            isClose = true;

            // 创建一个退出消息对象
            QuitMessage quitMessage = new QuitMessage();

            // 向服务器发送退出消息,以通知服务器移除客户端记录
            socket.SendTo(quitMessage.Writing(), serverIpPoint);

            // 关闭套接字的发送和接收,然后关闭套接字
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            socket = null;
        }
    }

    // 当对象被销毁时自动调用Close方法,确保关闭socket
    private void OnDestroy()
    {
        Close();
    }

    //收消息
    private void ReceiveMsg(object obj, SocketAsyncEventArgs socketAsyncEventArgs)
    {
        // 声明变量以存储消息的当前索引、消息ID和消息长度
        int nowIndex;
        int msgID;
        int msgLength;

        // 检查套接字异步事件是否成功
        if (socketAsyncEventArgs.SocketError == SocketError.Success)
        {
            try
            {
                // 检查接收到的消息是否来自服务器
                if (socketAsyncEventArgs.RemoteEndPoint.Equals(serverIpPoint))
                {
                    // 初始化当前索引
                    nowIndex = 0;

                    // 解析消息ID(4字节整数)
                    msgID = BitConverter.ToInt32(socketAsyncEventArgs.Buffer, nowIndex);
                    nowIndex += 4;

                    // 解析消息长度(4字节整数)
                    msgLength = BitConverter.ToInt32(socketAsyncEventArgs.Buffer, nowIndex);
                    nowIndex += 4;

                    // 创建一个消息对象用于存储接收到的消息
                    BaseMessage baseMessage = null;

                    // 根据消息ID执行相应的处理
                    switch (msgID)
                    {
                        case 1001:
                            // 如果消息ID是1001,创建一个玩家消息对象
                            baseMessage = new PlayerMessage();

                            // 反序列化消息体
                            baseMessage.Reading(socketAsyncEventArgs.Buffer, nowIndex);
                            break;
                    }

                    // 如果消息对象不为空,将其加入接收队列
                    if (baseMessage != null)
                        receiveQueue.Enqueue(baseMessage);
                }

                // 如果套接字仍处于打开状态且不被关闭
                if (socket != null && !isClose)
                {
                    // 重置异步事件的缓冲区以接收更多数据
                    socketAsyncEventArgs.SetBuffer(0, cacheBytes.Length);

                    // 继续异步接收数据
                    socket.ReceiveFromAsync(socketAsyncEventArgs);
                }
            }
            catch (SocketException s)
            {
                // 处理套接字异常,通常在接收消息时发生
                print("接收消息出错" + s.SocketErrorCode + s.Message);

                // 关闭套接字
                Close();
            }
            catch (Exception e)
            {
                // 处理其他异常,可能是反序列化问题
                print("接收消息出错(可能是反序列化问题)" + e.Message);

                // 关闭套接字
                Close();
            }
        }
        else
        {
            // 打印套接字错误,表示接收消息失败
            print("接收消息失败" + socketAsyncEventArgs.SocketError);
        }
    }

    //每帧检查消息列表
    void Update()
    {
        // 如果接收队列中有待处理的消息
        if (receiveQueue.Count > 0)
        {
            // 从队列中取出下一个消息
            BaseMessage baseMessage = receiveQueue.Dequeue();

            // 根据消息类型进行处理
            switch (baseMessage)
            {
                case PlayerMessage playerMessage:
                    // 处理玩家消息,打印相关信息
                    print(playerMessage.playerID);
                    print(playerMessage.playerData.name);
                    print(playerMessage.playerData.atk);
                    print(playerMessage.playerData.lev);
                    break;
            }
        }
    }

    // 发送消息
    public void Send(BaseMessage baseMessage)
    {
        try
        {
            // 检查套接字是否存在且未关闭
            if (socket != null && !isClose)
            {
                // 创建一个SocketAsyncEventArgs对象用于发送消息
                SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();

                // 将消息对象序列化为字节数组
                byte[] bytes = baseMessage.Writing();

                // 设置SocketAsyncEventArgs的缓冲区以包含要发送的字节数组
                socketAsyncEventArgs.SetBuffer(bytes, 0, bytes.Length);

                // 设置发送完成后的回调函数
                socketAsyncEventArgs.Completed += SendToCallBack;

                // 设置远端目标,即服务器的IP和端口
                socketAsyncEventArgs.RemoteEndPoint = serverIpPoint;

                // 发送消息
                socket.SendToAsync(socketAsyncEventArgs);
            }
        }
        catch (SocketException s)
        {
            // 处理套接字异常,通常在发送消息时发生
            print("发送消息出错" + s.SocketErrorCode + s.Message);
        }
        catch (Exception e)
        {
            // 处理其他异常,可能是序列化问题
            print("发送消息出错(可能是序列化问题)" + e.Message);
        }
    }

    // 发送消息后的回调函数
    private void SendToCallBack(object o, SocketAsyncEventArgs socketAsyncEventArgs)
    {
        // 如果发送消息过程中出现套接字错误
        if (socketAsyncEventArgs.SocketError != SocketError.Success)
        {
            // 打印错误信息,表示发送消息失败
            print("发送消息失败" + socketAsyncEventArgs.SocketError);
        }
    }


}

Lesson39_网络通信_套接字Socket_UDP通信_异步_客户端综合练习题

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

public class Lesson39_网络通信_套接字Socket_UDP通信_异步_客户端综合练习题 : MonoBehaviour
{
    public InputField InputField;
    public Button sendButton;
    void Start()
    {
        UdpNetAsyncManager.Instance.StartClient("127.0.0.1", 8080);

        sendButton.onClick.AddListener(() =>
        {
            PlayerMessage playerMessage = new PlayerMessage();
            playerMessage.playerData = new PlayerData();
            playerMessage.playerID = 1;
            playerMessage.playerData.name = "韬老狮的UDP客户端发的异步消息";
            playerMessage.playerData.atk = 888;
            playerMessage.playerData.lev = 666;
            UdpNetAsyncManager.Instance.Send(playerMessage);
        });
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏