【开发笔记】unity联网斗地主的实现(二,服务器与客户端的传输协议与收发消息队列框架)

一,定义玩家的信息协议

public class PlayerMessage
{
    /// <summary>
    /// 玩家id
    /// </summary>
    public int id;
    /// <summary>
    /// 玩家昵称
    /// </summary>
    public string nickName;
    /// <summary>
    /// 玩家状态(0=未准备,1=准备,2=出牌,3=pass)
    /// </summary>
    public int state;
    /// <summary>
    /// 玩家手里的牌
    /// </summary>
    public string cardsIds;
    /// <summary>
    /// 0==农民,1==地主
    /// </summary>
    public int scale=0;
}

二,定义消息传输类型协议

/// <summary>
/// 消息体
/// </summary>
public class MessageData
{

    /// <summary>
    /// 消息类型
    /// </summary>
    public MessageType msgType;

    /// <summary>
    /// 消息内容
    /// </summary>
    public string msg;

}

/// <summary>
/// 定义简单的协议类型
/// </summary>
public enum MessageType
{
    /// <summary>
    /// 申请加入
    /// </summary>
    REQUEST_JOIN = 0,
    /// <summary>
    /// 同意加入
    /// </summary>
    AGREE_JOIN=1,
    /// <summary>
    /// 准备开始游戏
    /// </summary>
    READY_BEGIN=2,
    /// <summary>
    /// 开始游戏
    /// </summary>
    BEGIN_PLAY=3,
    /// <summary>
    /// 发牌
    /// </summary>
    DEAL_CARDS=4,
    /// <summary>
    /// 洗牌
    /// </summary>
    REFRESH_CARDS=5,
    /// <summary>
    /// 出牌
    /// </summary>
    SEND_CARDS=6,
    /// <summary>
    /// 所有玩家信息
    /// </summary>
    ALLPLAYER_MESSAGE = 7,
    /// <summary>
    /// 我的信息
    /// </summary>
    MY_MESSAGE = 8,
    /// <summary>
    /// 地主牌
    /// </summary>
    LANLORD_CARDS = 9,
    /// <summary>
    /// 桌面的牌
    /// </summary>
    DESKTOP_CARDS = 10,
    /// <summary>
    /// 当前出牌玩家
    /// </summary>
    CURRENT_SEND=11,
    /// <summary>
    /// 游戏结束
    /// </summary>
    GAME_OVER = 12,
}

三,客户端的框架搭建(收发消息队列)

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


/// <summary>
/// 声明一个委托对象
/// </summary>
/// <param name="data">接收到的数据对象</param>
public delegate void ReceiveMessageData(MessageData data);
/// <summary>
/// 当连接状态发生改变
/// </summary>
public delegate void OnConnectChange();

public class ClientSocketController : MonoBehaviour
{

    Socket clientSocket;
    /// <summary>
    /// 数据缓冲池
    /// </summary>
    public byte[] buffer = new byte[512];
    /// <summary>
    /// 数据记忆流
    /// </summary>
    public MemoryStream bufferMs = new MemoryStream();
    /// <summary>
    /// 委托变量
    /// </summary>
    public ReceiveMessageData receiveMessageData;
    /// <summary>
    /// 连接成功
    /// </summary>
    public OnConnectChange OnConnectSuccess;
    /// <summary>
    /// 连接异常
    /// </summary>
    public OnConnectChange OnConnectExcept;
    /// <summary>
    /// 发送队列
    /// </summary>
    public Queue<MessageData> sendQueue = new Queue<MessageData>();
    /// <summary>
    /// 判断是否正在发送
    /// </summary>
    public bool IsSend = false;
   
    /// <summary>
    /// 主动连接次数
    /// </summary>
    public int ConnectedCount = 0;
    /// <summary>
    /// 最大主动连接次数
    /// </summary>
    public int MaxConnectNumber = 5;
    /// <summary>
    ///尝试连接服务器
    /// </summary>
    public bool tryConnectToServer = false;
    /// <summary>
    /// 等待时间
    /// </summary>
    public float holdTime = 1;
    // Use this for initialization
    void Start()
    {
        ConnectedCount = 0;
        //创建socket对象
        clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //开始链接服务器
        ConnectedToSercver();

    }
    /// <summary>
    /// 连接服务器
    /// </summary>
    public void ConnectedToSercver()
    {
        IPEndPoint ipendPoint = new IPEndPoint(IPAddress.Parse("192.168.213.54"), 10004);
        Debug.Log("开始连接服务器.......");
        //请求连接
        clientSocket.BeginConnect(ipendPoint, ConnectCallback, "");
    }

    /// <summary>
    /// 连接服务器的回调
    /// </summary>
    /// <param name="ar"></param>
    public void ConnectCallback(IAsyncResult ar)
    {
        try
        {
            clientSocket.EndConnect(ar);
        }
        catch (Exception e)
        {
            Debug.Log(e.ToString());

        }
        //判断到底连接成功了还是没有?
        Debug.Log("连接回调!!!!!");
        if (clientSocket.Connected == true)
        {
            OnConnectSuccess();
            Debug.Log("连接成功!!!!!");
            ReceiveMessageFormServer();
        }
        else
        {
            Debug.Log("连接失败");
            tryConnectToServer = true;
            holdTime = 1;
            //Invoke("ConnectedToSercver", 0.5f);
        }
    }
    /// <summary>
    /// 把发送的信息对象放入队列中
    /// </summary>
    public void PutMessageToQueue(MessageData data)
    {
        //byte[] msgBytes = ToolsUtil.StringToByteArry(JsonMapper.ToJson(data));
        //SendMessageToServer(msgBytes,0,msgBytes.Length);

        //把要发送的消息放入队列
        sendQueue.Enqueue(data);

        //看一下有没有内容发送!如果正在发,不用管,如果没有发,启动发送
        DeSendQueueToServer();

    }
    /// <summary>
    /// 出队列,发送给服务器
    /// </summary>
    public void DeSendQueueToServer()
    {
        if (IsSend == false)
        {
            //启动发送
            //出队列,对象转json,json字符串转byte[] 然后发送
            //确保队列中有元素
            if (sendQueue.Count > 0)
            {
                MessageData data = sendQueue.Dequeue();
                byte[] sendBytes = ToolsUtil.ObjectToJsonBytes(data);
                Debug.Log(System.Text.Encoding.UTF8.GetString(sendBytes));
                SendBytesMessageToServer(sendBytes, 0, sendBytes.Length);
            }

        }
    }
    /// <summary>
    /// 发送一条文字信息给服务器
    /// </summary>
    /// <param name="msg"></param>
    public void SendMessageToServer(string msg)
    {
        //发送类型
        MessageData data = new MessageData();
        data.msgType = MessageType.SEND_CARDS;
        data.msg = msg;
        PutMessageToQueue(data);
    }
    /// <summary>
    /// 发送消息的方法
    /// </summary>
    /// <param name="sendMsgConent">消息内容</param>
    /// <param name="offest">从消息内容第几个开始发送</param>
    /// <param name="size">发送的长度</param>
    public void SendBytesMessageToServer(byte[] sendMsgConent, int offest, int size)
    {
        if (clientSocket.Connected && IsSend == false)
        {
            IsSend = true;
            //需要粘包
            //这个信息的总长度+信息
            //int 转成byte数组
            byte[] lengthBytes = BitConverter.GetBytes(sendMsgConent.Length + 4);
            //创建一个内存流
            MemoryStream ms = new MemoryStream();
            //把头文件写进去,整个信息的长度
            ms.Write(lengthBytes, 0, lengthBytes.Length);
            //再把信息写进去
            ms.Write(sendMsgConent, 0, sendMsgConent.Length);

            //把内存流文件转成byte数组
            byte[] allMsgBytes = ms.ToArray();

            //用完之后,清理一下内存
            ms.Dispose();//释放
            ms.Close();//关闭


            Debug.Log("开始发送!!准备发送长度=" + allMsgBytes.Length);
            clientSocket.BeginSend(allMsgBytes, offest, allMsgBytes.Length, SocketFlags.None, SendMessageCallback, "");
        }

    }
    /// <summary>
    /// 发送信息的回调
    /// </summary>
    /// <param name="ar"></param>
    public void SendMessageCallback(IAsyncResult ar)
    {
        //发送结束
        IsSend = false;
        Debug.Log("发送结束!!!");
        //停止发送
        int length = clientSocket.EndSend(ar);
        Debug.Log("发送的长度:" + length);

        //开启下一次发送
        DeSendQueueToServer();
    }
    /// <summary>
    /// 从服务器接收数据
    /// </summary>
    public void ReceiveMessageFormServer()
    {
        Debug.Log("开始接收数据");
        clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveMessageCallback, "");
    }
    /// <summary>
    /// 接收信息的回调
    /// </summary>
    /// <param name="ar"></param>
    public void ReceiveMessageCallback(IAsyncResult ar)
    {
        Debug.Log("接收结束!!!!!");
        //结束接收
        int length = clientSocket.EndReceive(ar);
        Debug.Log("接收的数据长度=" + length);
        //把数据缓冲放进记忆流
        bufferMs.Write(buffer, 0, length);
        //拆包
        bool isDisOver = false;
        //当前拆到第几字节
        int currentIndex = 0;

        //把流转换为数组
        byte[] receiveMsgBytes = bufferMs.ToArray();
        while (isDisOver == false)
        {
            //开始拆包
            if (receiveMsgBytes.Length <= 4 + currentIndex)//如果信息包体长度小于4加当前读到得信息长度,说明记忆流最后的包体数据不完整
            {
                //如果下一条信息的长度不够了,存起来
                bufferMs.Dispose();
                bufferMs.Close();
                bufferMs = new MemoryStream();
                bufferMs.Write(receiveMsgBytes, currentIndex, receiveMsgBytes.Length - currentIndex);
                //结束循环
                isDisOver = true;
            }
            else
            {
                //判断记忆流开头数据长度
                int currentMsgLength = BitConverter.ToInt32(receiveMsgBytes, currentIndex);
                if (currentMsgLength > receiveMsgBytes.Length - currentIndex)//当前包的信息长度大于剩余信息长度
                {//后面数据包的信息不完整
                    //信息不完整,存起来
                    bufferMs.Dispose();
                    bufferMs.Close();
                    bufferMs = new MemoryStream();
                    bufferMs.Write(receiveMsgBytes, currentIndex, receiveMsgBytes.Length - currentIndex);
                    //结束循环
                    isDisOver = true;
                }
                else//信息是完整的
                {
                    //读取这条信息的内容
                    //读取信息是从包头后面开始开始,长度是总长度减去包头长度
                    string msg = ToolsUtil.ByteArrayToString(receiveMsgBytes, currentIndex + 4, currentMsgLength - 4);
                    //拆完之后把读取文件的下标往后移

                    Debug.Log("服务器发来消息=" + msg);
                    //把信息转换为byte数组
                    //byte[] resByte = ToolsUtil.StringToByteArry(msg);
                    //把接收到的数据转换为信息对象
                    try
                    {
                        MessageData reveiveMsgData = LitJson.JsonMapper.ToObject<MessageData>(msg.Trim());
                        if (receiveMessageData != null)
                        {
                            //接收消息委托
                            receiveMessageData(reveiveMsgData);
                        }
                        ReceiveMessageFormServer();
                        currentIndex += currentMsgLength;
                    }
                    catch (Exception e)
                    {
                        Debug.Log(e.ToString());
                        throw;
                    }
                   
                }

            }




        }



    }
    // Update is called once per frame

    void Update()
    {
        holdTime -= Time.deltaTime;
        if (tryConnectToServer == true && holdTime < 0 && ConnectedCount <= MaxConnectNumber)
        {

            tryConnectToServer = false;
            ConnectedCount += 1;
            ConnectedToSercver();
        }
    }
}

四,单个服务器的消息处理框架

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace LandlordServerDemo
{
    class ClientController
    {

        /// <summary>
        /// 用户链接的通道 
        /// </summary>
        private Socket clientSocket;


        public int id;//16
        /// <summary>
        /// 昵称
        /// </summary>
        public string nickName;
        /// <summary>
        /// 玩家id
        /// </summary>
        public int playerId = 1;
        /// <summary>
        /// 所有玩家列表
        /// </summary>
        public static List<PlayerMessage> allPlayerList = new List<PlayerMessage>();
        /// <summary>
        /// 准备游戏的玩家数量
        /// </summary>
        public int IsReadyPlayers=0;

        Thread receiveThread;

        /// <summary>
        /// 发送队列
        /// </summary>
        public Queue<MessageData> sendQueue = new Queue<MessageData>();

        /// <summary>
        /// 接收队列
        /// </summary>
        public Queue<MessageData> receiveQueue = new Queue<MessageData>();

        /// <summary>
        /// 发送线程
        /// </summary>
        private Thread sendThread;
        /// <summary>
        /// 处理接收的队列
        /// </summary>
        private Thread handlerReceiveThread;

        /// <summary>
        /// 缓冲内存流
        /// </summary>
        private MemoryStream bufferMS = new MemoryStream();

        public ClientController(Socket socket)
        {
            clientSocket = socket;
            //启动接收的方法
            //开始收的线程
            receiveThread = new Thread(ReceiveFromClient);
            //启动收的线程
            receiveThread.Start();

            //发送的无限死循环的线程开启一下
            sendThread = new Thread(SendToClient);
            //启动线程
            sendThread.Start();


            handlerReceiveThread = new Thread(HandlerReceiveQueue);
            //启动线程
            handlerReceiveThread.Start();
        }

        //接收的线程方法
        byte[] buffer = new byte[512];//到底用多少???
        void ReceiveFromClient()
        {
            try
            {
                //Socket clientSocket = (Socket)socket;
                while (clientSocket.Connected)
                {
                    //if(clientSocket.Connected)
                    {
                        //定义一个缓冲区域
                        //这儿会有阻塞
                        int length = clientSocket.Receive(buffer, buffer.Length, SocketFlags.None);
                        if (length > 0)
                        {

                            //首先把接收到的字节放入缓冲流
                            bufferMS.Write(buffer, 0, length);

                            //拆包
                            bool isDisOver = false;
                            //当前拆到第几个字节了
                            int currentIndex = 0;

                            //把流转换为数组
                            byte[] receiveMsgBytes = bufferMS.ToArray();

                            Console.WriteLine("开始拆包,receiveMsgBytes.Length=" + receiveMsgBytes.Length);

                            while (isDisOver == false)
                            {
                                Console.WriteLine("currentIndex=" + currentIndex);
                                //开始拆包
                                if (receiveMsgBytes.Length <= 4 + currentIndex)
                                {
                                    //如果下一条信息的长度不够了,存起来
                                    bufferMS.Dispose();
                                    bufferMS.Close();
                                    bufferMS = new MemoryStream();
                                    bufferMS.Write(receiveMsgBytes, currentIndex, receiveMsgBytes.Length - currentIndex);

                                    //结束循环
                                    isDisOver = true;
                                }
                                else
                                {
                                    //超过4个
                                    //判断一下头内容中有多长
                                    int currentMsgLength = BitConverter.ToInt32(receiveMsgBytes, currentIndex);
                                    Console.WriteLine("当前信息的长度是:" + currentMsgLength);
                                    if (currentMsgLength > receiveMsgBytes.Length - currentIndex)
                                    {
                                        //信息不完整,存起来

                                        //如果下一条信息的长度不够了,存起来
                                        bufferMS.Dispose();
                                        bufferMS.Close();
                                        bufferMS = new MemoryStream();
                                        bufferMS.Write(receiveMsgBytes, currentIndex, receiveMsgBytes.Length - currentIndex);


                                        //结束循环
                                        isDisOver = true;
                                    }
                                    else
                                    {
                                        //信息是完整
                                        //读取这条信息内容,读取完之后把指针往后拨
                                        string json = System.Text.Encoding.UTF8.GetString(receiveMsgBytes, currentIndex + 4, currentMsgLength - 4);

                                       
                      

                                        Console.WriteLine("接收到数据长度是:" + length + ",开始解析接收的内容。。。。");
                                        //接收的内容打印出来
                                        //服务器收到应该全部是json字符串,如果不是,说明出错了!
                                        Console.WriteLine("客户端发过来信息说:" + json);

                                        //处理接受信息
                                        //将json字符串转换为数据对象

                                        //异常捕获
                                        try
                                        {
                                            MessageData data = LitJson.JsonMapper.ToObject<MessageData>(json.Trim());

                                            //放入到接收队列中
                                            receiveQueue.Enqueue(data);

                                        }
                                        catch (Exception e)
                                        {
                                            Console.WriteLine(e.ToString());
                                            // Program.RemoveOutLineClient(this);
                                        }
                                        //指针往后拨
                                        currentIndex += currentMsgLength;

                                    }
                                }
                            }

                        }

                    }

                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                Program.RemoveOutLineClient(this);
                Console.WriteLine("当前还有" + Program.clientControllerList.Count + "个用户在线");
            }

        }


        /// <summary>
        /// 处理消息队列里的消息
        /// </summary>
        public void HandlerReceiveQueue()
        {
            while (true)
            {
                if (receiveQueue.Count > 0)
                {
                    //出队列
                    MessageData data = receiveQueue.Dequeue();
                    Console.WriteLine(data.msgType);
                    //根据收到的消息类型做出反应
                    switch (data.msgType)
                    {
                     
                        case MessageType.REQUEST_JOIN://请求加入
                      
                            break;
                        case MessageType.AGREE_JOIN:
                            break;
                        case MessageType.READY_BEGIN:
                         
                            break;
                        case MessageType.BEGIN_PLAY:
                            break; 
                        case MessageType.DEAL_CARDS:
                          
                            break;
                        
                        case MessageType.REFRESH_CARDS:
                            break;
                        case MessageType.SEND_CARDS:
                          
                            break;
                        case MessageType.MY_MESSAGE:
                          
                            break;
                        case MessageType.DESKTOP_CARDS:
                          
                            break;
                        default:
                            break;
                    }
                      
                }
                Thread.Sleep(100);

            }
        }
       
        /// <summary>
        /// 把玩家的信息广播给所有人
        /// </summary>
        public void SendAllPlayerMessageToAllClient()
        {
            //广播给所有人
            MessageData allPlayerData = new MessageData();
            allPlayerData.msgType = MessageType.ALLPLAYER_MESSAGE;
            allPlayerData.msg = LitJson.JsonMapper.ToJson(allPlayerList);
            SendMessageDataToAllClient(allPlayerData);
        }

        /// <summary>
        /// 广播信息,告诉所有用户有什么信息过来了!
        /// </summary>
        /// <param name="data"></param>
        void SendMessageDataToAllClient(MessageData data)
        {
            for (int i = 0; i < Program.clientControllerList.Count; i++)
            {
                try
                {
                    Program.clientControllerList[i].SendToClient(data);
                }
                catch (Exception)
                {
                    i--;
                }

            }
        }
        /// <summary>
        /// 广播消息,排除掉自己
        /// </summary>
        /// <param name="data"></param>
        void SendMessageDataToAllClientWithOutSelf(MessageData data)
        {
            //5   i=3
            for (int i = 0; i < Program.clientControllerList.Count; i++)
            {
                if (Program.clientControllerList[i] != this)
                {
                    Program.clientControllerList[i].SendToClient(data);
                }

            }
        }

        /// <summary>
        /// 把要发送给服务器的消息放入消息队列(告诉自己)
        /// </summary>
        /// <param name="data"></param>
        void SendToClient(MessageData data)
        {
            //放入发送队列
            sendQueue.Enqueue(data);
        }
        /// <summary>
        /// 发消息给客户端
        /// </summary>
        /// <param name="msgByte">需要发送的内容</param>
        void SendToClient()
        {
            while (true)
            {
                //发送
                try
                {
                    if (sendQueue.Count > 0)
                    {
                        //byte[] sendMsg = System.Text.Encoding.UTF8.GetBytes(JsonMapper.ToJson(data));
                        //出队列
                        MessageData data = sendQueue.Dequeue();
                        //把对象转换为json字符串
                        string msg = LitJson.JsonMapper.ToJson(data);
                        Console.WriteLine("服务器发送消息:" + msg);
                        //把json字符串转换byte数组
                        byte[] msgBytes = System.Text.Encoding.UTF8.GetBytes(msg);
                        Console.WriteLine("服务器开始发送信息长度:" + msgBytes.Length);

                        //粘包
                        MemoryStream ms = new MemoryStream();
                        //包头(这条信息的长度)
                        byte[] lengthBytes = BitConverter.GetBytes(msgBytes.Length + 4);
                        //拼接包头
                        ms.Write(lengthBytes, 0, lengthBytes.Length);
                        //拼接包体信息
                        ms.Write(msgBytes, 0, msgBytes.Length);

                        //将拼接好的信息转换为byte数组
                        byte[] allMsgBytes = ms.ToArray();



                        //资源释放
                        ms.Dispose();
                        ms.Close();

                        //确保发送的是拼接后的信息
                        int sendLength = clientSocket.Send(allMsgBytes);
                        Console.WriteLine("服务器发送信息结束,成功发送:" + sendLength);
                        Thread.Sleep(50);
                    }
                    else
                    {
                        Thread.Sleep(500);
                    }


                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                    Program.RemoveOutLineClient(this);
                    Console.WriteLine("当前还有" + Program.clientControllerList.Count + "个用户在线");
                    //抛出异常,可以进行捕获
                    throw new Exception();

                }

            }
        }
        /// <summary>
        /// 销毁自己,也就是释放资源
        /// </summary>
        public void DestroySelf()
        {
            //异常 空针针,null 数组越界 特殊情况引起的(1/0),可以进行捕获,不会影响整个系统的运行

            //错误 无法捕获,系统内部已经没有办法自动消化这个错误。
            try
            {
                receiveThread.Abort();

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            try
            {
                sendThread.Abort();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            try
            {
                handlerReceiveThread.Abort();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

        }

    }
}

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity是一款强大的游戏开发引擎,也被广泛用于开发大型多人在线角色扮演游戏(MMORPG)。在MMORPG开发中,网络架构设计与实现源码是非常重要的核心技术。首先,Unity客户端网络架构设计需要考虑到多个玩家之间的实时通讯和同步,以及与服务器的数据交换。 在网络架构设计上,首先需要考虑到如何处理玩家之间的实时通讯和同步。这部分源码需要实现基于UDP或TCP的网络通讯,同时考虑到玩家位置、动作、状态等数据的同步,并采用合适的压缩和插值算法来保证玩家之间的数据同步和流畅性。 其次,与服务器的数据交换也是Unity客户端网络架构设计的重要一环。这部分源码需要实现服务器的通讯协议,包括登录认证、游戏数据同步、战斗事件处理等。同时需要考虑到网络延迟和丢包等问题,采用合适的重试机制和数据校验算法来保证通讯的稳定性和可靠性。 在实现上,可以采用C#等语言编写网络架构设计的源码。由于Unity本身内置了一些网络通讯的API和组件,开发者可以基于这些API和组件进行开发,加快开发速度和降低成本。同时,还可以借助一些开源的网络库和框架来提高网络通讯的效率和性能。 总之,Unity客户端网络架构设计与实现源码是MMORPG开发中的重要核心技术,需要综合考虑玩家之间的实时通讯和同步,以及与服务器的数据交换。通过合理的网络架构设计和源码实现,可以提高游戏的网络性能和稳定性,带来更好的游戏体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值