发送自定义类的2进制信息
1.进程BaseData类
2.实现其中的序列化、反序列化、获取字节数等
3.发送自定义类数据时,序列化
4.接受自定义类数据时,反序列化
区分消息
为发送的消息添加标识,比如添加消息ID(类似于网络传输过程中的头文件)
在所有发送的消息的头部加上消息ID
举例说明:
消息构成
如果选用int类型作为消息ID的类型
前4个字节为消息ID
后面的字节为数据类的内容
####***************************
这样每次收到消息时,先把前4个字节取出来解析为消息ID
再根据ID进行消息反序列化即可
1.创建消息基类,基类继承BaseData,基类添加获取消息ID的方法或者属性
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
/// <summary>
/// 玩家数据类
/// </summary>
public class PlayerData : BaseData
{
public string name;
public int atk;
public int lev;
/// <summary>
/// 获取玩家类的二进制的字节长度
/// </summary>
/// <returns>返回的是字节长度</returns>
public override int GetBytesNum()
{
return 4+4+4+Encoding.UTF8.GetBytes(name).Length;
}
/// <summary>
/// 读取玩家类的字节长度
/// </summary>
/// <param name="bytes">字节</param>
/// <param name="beginIndex">开始的位置</param>
/// <returns>占了多长的字节</returns>
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;
}
/// <summary>
/// 写入所要存进去的数据
/// </summary>
/// <returns>所转换好的二进制的字符串</returns>
public override byte[] Writeing()
{
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;
}
}
2.让想要被发送的消息继承该类,实现序列化反序列化方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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[] Writeing()
{
throw new System.NotImplementedException();
}
/// <summary>
/// 头文件编号
/// </summary>
/// <returns>头文件的编号,每个编号对应的一个数据类型,这个编号对应的数据类型可以自己来决定</returns>
public virtual int GetID()
{
return 0;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 给要传输的文件加入头文件
/// </summary>
public class PlayerMsg : BaseMsg
{
public int playerID;
public PlayerData playerData;
public override byte[] Writeing()
{
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 + 4 + playerData.GetBytesNum();
}
/// <summary>
/// 自定义的消息ID 主要用于区分是哪一个消息类
/// </summary>
/// <returns>头文件的编号,每个编号对应的一个数据类型,这个编号对应的数据类型可以自己来决定</returns>
public override int GetID()
{
return 1001;
}
}
3.修改客户端和服务端收发消息的逻辑
对客户端的修改
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
public class Lesson6 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//1.创建套接字Socket
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.用Connect方法与服务端相连
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); //这里面的IP地址和端口号指的是服务端的而不是客户端的
try
{
socket.Connect(ipPoint);
}
catch(SocketException e)
{
if (e.ErrorCode == 10061)
print("服务器拒绝连接");
else
print("连接服务器失败" + e.ErrorCode);
return;
}
//3.用Send和Receive相关方法收发数据
//接收数据
byte[] receiveBytes = new byte[1024];
int receiveNum = socket.Receive(receiveBytes);
//首先解析消息的ID
//使用字节数组中的前四个字节 得到ID
int msgID = BitConverter.ToInt32(receiveBytes, 0);
//如果id与之匹配客户端就执行下面的命令
switch (msgID)
{
case 1001:
PlayerMsg msg = new PlayerMsg();
msg.Reading(receiveBytes, 4);
print(msg.playerID);
print(msg.playerData.name);
print(msg.playerData.atk);
print(msg.playerData.lev);
break;
}
print("收到服务端发来的消息:" + Encoding.UTF8.GetString(receiveBytes, 0, receiveNum));
//发送数据
socket.Send(Encoding.UTF8.GetBytes("你好,我是客户端"));
//4.用Shutdown方法释放连接
socket.Shutdown(SocketShutdown.Both);
//5.关闭套接字
socket.Close();
}
// Update is called once per frame
void Update()
{
}
}
对服务端的修改
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace AllData
{
class Program
{
static void Main(string[] args)
{
//1.创建套接字Socket
Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.用Bind方法将套接字与本地地址绑定(注:这里用try、catch是为了防止端口号已经被使用了
try
{
//指定服务器的IP地址和端口号
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socketTcp.Bind(ipPoint);
}
catch (Exception e)
{
Console.WriteLine("绑定报错" + e.Message);
return;
}
//3.用Listen方法监听,参数代表可以接入客户端最大的数量
socketTcp.Listen(1024);
Console.WriteLine("服务端绑定监听结束,等待客户端连入");
//4.用Accept方法等待客户端连接
//5.建立连接,Accept返回新套接字
Socket socketClient = socketTcp.Accept(); //该函数的执行,会一直坚持到有客户端加入才会继续到下一步
Console.WriteLine("有客户端连入了");
//6.用Send和Receive相关方法收发数据
//发送
//socketClient.Send(Encoding.UTF8.GetBytes("欢迎加入服务端"));
//写入数据
PlayerMsg msg = new PlayerMsg();
msg.playerID = 196;
msg.playerData = new PlayerData();
msg.playerData.name = "缘笙箫";
msg.playerData.atk = 100;
msg.playerData.lev = 20;
socketClient.Send(msg.Writeing());
//接收
byte[] result = new byte[1024];
//返回值为接受到的字节数
int receiveNum = socketClient.Receive(result);
Console.WriteLine("接受到了{0}发来的消息:{1}",
socketClient.RemoteEndPoint.ToString(),
Encoding.UTF8.GetString(result, 0, receiveNum));
//7.用Shutdown方法释放连接
socketClient.Shutdown(SocketShutdown.Both);
//8.关闭套接字
socketClient.Close();
Console.WriteLine("按任意键退出");
Console.ReadKey();
}
}
}