Unity-网络编程 笔记2

使用LitJSON更好的组织网络数据

LitJSON 下载及安装

网址链接 https://litjson.net/

安装方式

服务端安装

点击source,进入litjson GitHub仓库 https://github.com/LitJSON/litjson

复制安装命令 

Install-Package LitJson -Version 0.19.0

打开程序包管理控制台  输入命令回车

 

 unity客户端安装

打开Service工程文件夹找到LitJSON.dll

将该文件拖入到unity工程中的Plugins文件夹中 

编写Service中Json的处理

创建文件夹Helper以及类文件JsonHelper.cs 

  

创建方法

需要实现数据的序列化以及反序列化

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

namespace Service.Helper
{
    internal class JsonHelper
    {
        /// <summary>
        /// 将接送数据转换成字符串
        /// </summary>
        /// <param name="json">json数据</param>
        /// <returns></returns>
        public static string ToJson(object json)
        {
            
        }

        /// <summary>
        /// 将字节数据转换成字符串
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="b"></param>
        /// <returns></returns>
        public static T ToObject<T>(byte[] b)
        {
            
        }

        /// <summary>
        /// 将字符串转换成json数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="str">json字符串</param>
        /// <returns></returns>
        private static T ToObject<T>(string str)
        {
            
        }

        /// <summary>
        /// 测试使用方法
        /// </summary>
        public static string TestDataToJson()
        {
            
        }
    }

    /// <summary>
    /// 测试类
    /// </summary>
    public class JsonTest
    {
        public int id;
        public string name;
    }
}

 实现转换逻辑

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

namespace Service.Helper
{
    internal class JsonHelper
    {
        /// <summary>
        /// 将接送数据转换成字符串
        /// </summary>
        /// <param name="json">json数据</param>
        /// <returns></returns>
        public static string ToJson(object json)
        {
            return JsonMapper.ToJson(json);
        }

        /// <summary>
        /// 将字节数据转换成字符串
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="b"></param>
        /// <returns></returns>
        public static T ToObject<T>(byte[] b)
        {
            string temp = Encoding.UTF8.GetString(b, 0, b.Length);
            return ToObject<T>(temp);
        }

        /// <summary>
        /// 将字符串转换成json数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="str">json字符串</param>
        /// <returns></returns>
        private static T ToObject<T>(string str)
        {
            return JsonMapper.ToObject<T>(str);
        }

        /// <summary>
        /// 测试使用方法
        /// </summary>
        public static string TestDataToJson()
        {
            JsonTest jsonTest = new JsonTest();
            jsonTest.id = 1;
            jsonTest.name = "test";

            //序列化是把对象转变成流。相反的过程就是反序列化。

            //将object对象转换成string
            var jsonStr = ToJson(jsonTest);

            //将字符串转换成object对象
            var jsonTestObj = ToObject<JsonTest>(jsonStr);

            //测试打印
            Console.Write( $"JsonTest的ID为:{jsonTestObj.id}----------JsonTest的name为:{jsonTestObj.name}");

            return jsonStr;
        }
    }

    /// <summary>
    /// 测试类
    /// </summary>
    public class JsonTest
    {
        public int id;
        public string name;
    }
}

测试转换效果

修改Client.cs中的Receive()方法

//收到数据之后,向客户端返回数据
//Send(Encoding.UTF8.GetBytes("服务端数据:09876543210"));
string jsonStr = JsonHelper.TestDataToJson();
Send(Encoding.UTF8.GetBytes(jsonStr));

编写unity中的Json处理

创建JsonHelper.cs

实现原理与Service中的一致

using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class JsonHelper
{ 
    /// <summary>
    /// 将接送数据转换成字符串
    /// </summary>
    /// <param name="json">json数据</param>
    /// <returns></returns>
    public static string ToJson(object json)
    {
        return JsonMapper.ToJson(json);
    }

    /// <summary>
    /// 将字节数据转换成字符串
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="b"></param>
    /// <returns></returns>
    public static T ToObject<T>(byte[] b , int length)
    {
        string temp = Encoding.UTF8.GetString(b, 0, length);
        return ToObject<T>(temp);
    }

    /// <summary>
    /// 将字符串转换成json数据
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="str">json字符串</param>
    /// <returns></returns>
    public static T ToObject<T>(string str)
    {
        return JsonMapper.ToObject<T>(str);
    }

    /// <summary>
    /// 测试使用方法
    /// </summary>
    public static string TestDataToJson()
    {
        JsonTest jsonTest = new JsonTest();
        jsonTest.id = 1;
        jsonTest.name = "test";

        //序列化是把对象转变成流。相反的过程就是反序列化。

        //将object对象转换成string
        var jsonStr = ToJson(jsonTest);

        //将字符串转换成object对象
        var jsonTestObj = ToObject<JsonTest>(jsonStr);

        //测试打印
        Console.Write($"JsonTest的ID为:{jsonTestObj.id}----------JsonTest的name为:{jsonTestObj.name}");

        return jsonStr;
    }
}

/// <summary>
/// 测试类
/// </summary>
public class JsonTest
{
    public int id;
    public string name;
}

最终的实现效果

粘包和拆包处理

为什么会粘包

如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。

因为TCP是面向流,没有边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。

如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。

如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。

粘包的处理办法

服务端的处理

创建脚本 MsgManager.cs 用于消息的处理
using Service.Helper;
using Service.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Service.Manager
{
    internal class MsgManager
    {
        int msgLength = 0;//消息长度
        byte[] data = new byte[4096];//消息缓存区
        Client client;
        public MsgManager(Client _client) 
        {
            client = _client;
        }

        /// <summary>
        /// 处理数据粘包的问题
        /// </summary>
        /// <param name="length">该条消息的长度</param>
        /// <param name="buffer">该条消息的内容数据</param>
        public void MsgHandler(int length , byte[] buffer)
        {
            //length:当前消息的长度,msgLength上一个消息的长度
            Array.Copy(buffer, 0, data, msgLength, length);
            msgLength += length;

            //制定消息规则
            //包体大小(4),协议ID(4),包体
            //   大小    ID        包体内容
            // | ---- | ---- |-----------------------
            if (msgLength >= 8)
            {
                //获取包体大小
                byte[] _size = new byte[4];
                Array.Copy(data, 0, _size, 0, 4);
                int size = BitConverter.ToInt32(_size, 0);
                //获取该条消息的长度
                var _length = 8 + size;

                if (msgLength >= _length)
                {
                    //获取包体ID
                    byte[] _id = new byte[4];
                    Array.Copy(data, 4, _id, 0, 4);
                    int id = BitConverter.ToInt32(_id, 0);

                    //获取包体
                    byte[] body = new byte[size];
                    Array.Copy(data, 8, body, 0, size);

                    //发生的粘包行为
                    //msgLength == _length:该条消息是一条完整的消息
                    //msgLength > _length:该条消息有多条消息组合
                    if (msgLength > _length)
                    {
                        //将已处理的包体数据从data中移除
                        for (int i = 0; i < msgLength - _length; i++)
                        {
                            data[i] = data[_length + i];
                        }
                    }

                    msgLength -= _length;
                    Console.WriteLine($"收到客户端请求包体大小:{size}--------收到客户端请求:{id}--------收到客户端数据:{Encoding.UTF8.GetString(body)}");

                    //向客户端返回数据
                    byte[] send_buffer = MsgHelper.MsgEncoding(10000, "Service:1234567890");
                    client.Send(send_buffer);

                    switch (id)
                    {
                        case 1001://注册请求
                            break;
                        case 1002://登录业务
                            break;
                        case 1003://聊天请求
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }
}
 创建MsgHelper.cs 用于消息的发送封装
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Service.Helper
{
    internal class MsgHelper
    {
        //消息封装
        public static byte[] MsgEncoding(int id, string msg)
        {
            //将包体转换为字节数据
            var body = Encoding.UTF8.GetBytes(msg);

            byte[] send_buffer = new byte[body.Length + 8];

            //包体大小转换为字节数据
            int size = body.Length;
            var _size = BitConverter.GetBytes(size);
            
            //包体ID转换为字节数据
            var _id = BitConverter.GetBytes(id);
            
            //开始封装
            Array.Copy(_size, 0, send_buffer, 0, 4);
            Array.Copy(_id, 0, send_buffer, 4, 4);
            Array.Copy(body, 0, send_buffer, 8, body.Length);

            return send_buffer;
        }
    }
}
修改Client.cs 文件

添加变量

MsgManager msgManager;

修改数据的接收

if (length > 0)
{
    //Console.Write($"接收到的数据长度:{length}--------");
    //Console.WriteLine($"接收到的数据长度:{Encoding.UTF8.GetString(buffer, 0, length)}");

    //收到数据之后,向客户端返回数据
    //Send(Encoding.UTF8.GetBytes("服务端数据:09876543210"));
    //string jsonStr = JsonHelper.TestDataToJson();
    //Send(Encoding.UTF8.GetBytes(jsonStr));

    msgManager.MsgHandler(length, buffer);
}

当前Client.cs代码

using Service.Helper;
using Service.Manager;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Service.Net
{
    internal class Client
    {
        TcpClient client;
        MsgManager msgManager;

        public Client(TcpClient tcpClient) 
        {
            client = tcpClient;
            msgManager = new MsgManager();
            Receive();
        }

        /// <summary>
        /// 接受消息
        /// </summary>
        public async void Receive()
        {
            try
            {
                while (client.Connected)
                {
                    byte[] buffer = new byte[4096];

                    int length = await client.GetStream().ReadAsync(buffer, 0, buffer.Length);
                    if (length > 0)
                    {
                        //Console.Write($"接收到的数据长度:{length}--------");
                        //Console.WriteLine($"接收到的数据长度:{Encoding.UTF8.GetString(buffer, 0, length)}");

                        //收到数据之后,向客户端返回数据
                        //Send(Encoding.UTF8.GetBytes("服务端数据:09876543210"));
                        //string jsonStr = JsonHelper.TestDataToJson();
                        //Send(Encoding.UTF8.GetBytes(jsonStr));

                        //length:当前消息的长度,msgLength上一个消息的长度
                        msgManager.MsgHandler(length, buffer);
                    }
                    else
                    {
                        //客户端关闭了
                        client.Close();

                        ClientManager.Instance.RemoveClient(this);

                        Console.WriteLine($"客户机{client.Client.RemoteEndPoint}断开连接");
                    }
                }
            }
            catch(Exception e)
            {
                Console.WriteLine($"Receive Error:{e.Message}");
                client.Close();
                ClientManager.Instance.RemoveClient(this);
            }
               
        }
        /// <summary>
        /// 发送消息
        /// </summary>
        public async void Send(byte[] data)
        {
            try
            {
                await client.GetStream().WriteAsync(data, 0, data.Length);
                Console.WriteLine("数据发送成功");
            }
            catch(Exception e)
            {
                client.Close();
                ClientManager.Instance.RemoveClient(this);
                Console.WriteLine($"Send Error:{e.Message}");
            }
        }
    }
}

unity客户端的处理

和服务端处理一样,创建两个脚本 MsgManager.cs与 MsgHelper.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class MsgManager 
{
    int msgLength = 0;//消息长度
    byte[] data = new byte[4096];//消息缓存区

    /// <summary>
    /// 处理数据粘包的问题
    /// </summary>
    /// <param name="length">该条消息的长度</param>
    /// <param name="buffer">该条消息的内容数据</param>
    public void MsgHandler(int length, byte[] buffer)
    {
        //length:当前消息的长度,msgLength上一个消息的长度
        Array.Copy(buffer, 0, data, msgLength, length);
        msgLength += length;

        //制定消息规则
        //包体大小(4),协议ID(4),包体
        //   大小    ID        包体内容
        // | ---- | ---- |-----------------------
        if (msgLength >= 8)
        {
            //获取包体大小
            byte[] _size = new byte[4];
            Array.Copy(data, 0, _size, 0, 4);
            int size = BitConverter.ToInt32(_size, 0);
            //获取该条消息的长度
            var _length = 8 + size;

            if (msgLength >= _length)
            {
                //获取包体ID
                byte[] _id = new byte[4];
                Array.Copy(data, 4, _id, 0, 4);
                int id = BitConverter.ToInt32(_id, 0);

                //获取包体
                byte[] body = new byte[size];
                Array.Copy(data, 8, body, 0, size);

                //发生的粘包行为
                //msgLength == _length:该条消息是一条完整的消息
                //msgLength > _length:该条消息有多条消息组合
                if (msgLength > _length)
                {
                    //将已处理的包体数据从data中移除
                    for (int i = 0; i < msgLength - _length; i++)
                    {
                        data[i] = data[_length + i];
                    }
                }

                msgLength -= _length;
                Debug.Log($"收到服务端发送的包体大小:{size}--------收到服务端发送的ID:{id}--------收到服务端发送的数据:{Encoding.UTF8.GetString(body)}");

                switch (id)
                {
                    case 1001://注册请求结果
                        break;
                    case 1002://登录请求结果
                        break;
                    case 1003://聊天请求结果
                        break;
                    default:
                        break;
                }
            }
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public static class MsgHelper
{
    public static byte[] MsgEncoding(int id, string msg)
    {
        //将包体转换为字节数据
        var body = Encoding.UTF8.GetBytes(msg);
        byte[] send_buffer = new byte[body.Length + 8];

        //包体大小
        int size = body.Length;
        var _size = BitConverter.GetBytes(size);
        var _id = BitConverter.GetBytes(id);

        Array.Copy(_size, 0, send_buffer, 0, 4);
        Array.Copy(_id, 0, send_buffer, 4, 4);
        Array.Copy(body, 0, send_buffer, 8, body.Length);

        return send_buffer;
    }
}
修改客户端Client.cs
if (length > 0)
{
    //Debug.Log(Encoding.UTF8.GetString(buffer,0,length));
    //Debug.Log($"接收到的数据了长度为:{length}");
    //var jsonObj = JsonHelper.ToObject<JsonTest>(buffer, length);
    //Debug.Log($"{jsonObj.id} / {jsonObj.name}");
    msgManager.MsgHandler(length, buffer);
}
修改消息发送

每秒向服务端发送数据

using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEditor.PackageManager;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    public float waitTime = 1;

    void Start()
    {
        Client.Instance.Start();
        StartCoroutine(SendMsg());
    }

    IEnumerator SendMsg()
    {
        while (true)
        {
            yield return new WaitForSeconds(waitTime);
            string str = "Client:1234567890jksagahghalhglkahgla";
            byte[] send_buffer = MsgHelper.MsgEncoding(10001, str);
            Client.Instance.Send(send_buffer);
        }
    }
}

 最终效果图

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shumake

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值