使用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
创建方法
需要实现数据的序列化以及反序列化
- JSON对象和Json字符串的区别 https://www.cnblogs.com/agansj/p/9053547.html
- 将Json对象转换成Json字符串
- 将接收到的byte数据转换成Json对象
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);
}
}
}