1数据包
在传输网络数据的时候,接收方一次收到的数据长度可能是不确定的,比如客户端发送了100个字节给服务器,服务器有可能一次收到100个字节,也可能先收到20个,再收到80个。为了知道到底一个数据的长度是多少,我们将首先创建一个类,用于管理序列化的数据流,序列化、反序列化对象。
NetPacket这个类提供的功能主要包括两部分:一部分是将序列化的数据写入,并加入4个字节作为数据的“头”;另一部分是从byte数组的前4个字节解析数据长度,再读取相应长度的数据。这里是把protobuf的序列化处理。
1.1protobuf序列化
1.protobuf序列化为byte数组
/// <summary>
/// protobuf序列化成byte
/// </summary>
/// <typeparam name="T">protobuf的类</typeparam>
/// <param name="t">protobuf值</param>
/// <returns>protobuf序列化后的byte数组</returns>
public byte[] ProtoRuntimeSerialize<T>(T t)
{
byte[] bs;
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
{
ProtoBuf.Meta.RuntimeTypeModel model = ProtoBuf.Meta.RuntimeTypeModel.Create();
model.Serialize(stream, t);
bs = stream.ToArray();
}
return bs;
}
2.byte数组写入数据流
// 直接写入一个proto序列化的对象
public void WriteProtoObject<T>(T t)
{
byte[] bs = ProtoRuntimeSerialize<T>(t);
WriteStream(bs);
}
/// <summary>
/// 写入byte数组
/// </summary>
public void WriteStream(byte[] bs)
{
WriteInt(bs.Length);
// 压入数据流
bs.CopyTo(bytes, m_headLen + m_bodyLen);
m_bodyLen += bs.Length;
}
整型数据转换为byte
byte[] bs = System.BitConverter.GetBytes(number);
1.2protobuf反序列化
把byte转换为对应的protobuf
// 反序列化
public T ProtoRuntimeDeserialize<T>(byte[] bs)
{
T t = default(T);
using (System.IO.MemoryStream stream = new System.IO.MemoryStream(bs))
{
ProtoBuf.Meta.RuntimeTypeModel model = ProtoBuf.Meta.RuntimeTypeModel.Create();// ChatSerializer.Create();
t = (T)model.Deserialize(stream, null, typeof(T));
}
return t;
}
//读出proto
public T ReadProtoObject<T>()
{
byte[] bs = ReadStream();
if (bs == null)
{
return default(T);
}
return ProtoRuntimeDeserialize<T>(bs);
}
2网络功能
TCPPeer类封装TCP/IP协议,比如接收和发送数据
2.1发送数据
开始向流异步写入。每次发送的是byte数组。
public void Send( Socket sk, NetPacket packet )
{
NetworkStream ns;
lock (sk)
{
ns = new NetworkStream(sk);
if (ns.CanWrite)
{
try
{
ns.BeginWrite( packet.bytes, 0, packet.Length, new System.AsyncCallback(SendCallback), ns);
}
发送回调
private void SendCallback(System.IAsyncResult ar)
{
NetworkStream ns = (NetworkStream)ar.AsyncState;
try
{
ns.EndWrite(ar);
ns.Flush();
ns.Close();
}
2.2接收数据
1.当连接建立好时,开始从连接的Socket中异步接收数据。每次读取4个字节长度(代表消息的实际长度)。
client.BeginReceive(packet.bytes, 0, NetPacket.m_headLen, SocketFlags.None, new System.AsyncCallback(ReceiveHeader), packet);
2.接收消息头。
void ReceiveHeader(System.IAsyncResult ar)
{
NetPacket packet = (NetPacket)ar.AsyncState;
try
{
// 返回网络上接收的数据长度
int read = packet.socket.EndReceive(ar);
// 已断开连接
if (read < 1)
{
// 通知丢失连接
//AddInternalPacket("OnLost", packet.socket);
networkMgr.OnLost(packet);
return;
}
packet.readLength += read;
// 消息头必须读满4个字节
if (packet.readLength < NetPacket.m_headLen)
{
packet.socket.BeginReceive(packet.bytes,
packet.readLength, // 存储偏移已读入的长度
NetPacket.m_headLen - packet.readLength, // 这次只读入剩余的数据
SocketFlags.None,
new System.AsyncCallback(ReceiveHeader),
packet);
}
else
{
// 获得消息体长度
packet.DecodeHeader();
packet.readLength = 0;
// 开始读取消息体
packet.socket.BeginReceive(packet.bytes,
NetPacket.m_headLen,
packet.m_bodyLen,
SocketFlags.None,
new System.AsyncCallback(ReceiveBody),
packet);
}
说明:
1.消息头是4个字节,如果没读满4个字节做偏移量读4-已读的字节,再调用异步接收函数,读取剩下的。
2.当这条消息读满4个字节,解析出前4个字节代表的消息体的长度,再调用异步接收函数,读取消息体的信息。
3.接收消息体
void ReceiveBody(System.IAsyncResult ar)
{
NetPacket packet = (NetPacket)ar.AsyncState;
try
{
// 返回网络上接收的数据长度
int read = packet.socket.EndReceive(ar);
// 已断开连接
if (read < 1)
{
// 通知丢失连接
//AddInternalPacket("OnLost", packet.socket);
networkMgr.OnLost(packet);
return;
}
packet.readLength += read;
// 消息体必须读满指定的长度
if ( packet.readLength < packet.m_bodyLen )
{
packet.socket.BeginReceive(packet.bytes,
NetPacket.m_headLen + packet.readLength,
packet.m_bodyLen - packet.readLength,
SocketFlags.None,
new System.AsyncCallback(ReceiveBody),
packet);
}
else
{
// 将消息传入到逻辑处理队列
networkMgr.AddPacket(packet);
// 下一个读取
packet.Reset();
packet.socket.BeginReceive(packet.bytes,
0,
NetPacket.m_headLen,
SocketFlags.None,
new System.AsyncCallback(ReceiveHeader),
packet);
}
}
说明:
1.没读满消息体的长度,做偏移,从(头4个字节偏移+已读长度)开始,偏移量为(消息体长度-已读长度),接着调用ReceiveBody,读取剩下的消息体。
2.当读满消息体长度时,将消息传入到逻辑处理队列,接着ReceiveHeader,读取下条消息。
3网络管理类
负责把网络接收到的消息,分发到各个消息处理函数。
3.1数据包入队
// 数据包入队
public void AddPacket( NetPacket packet )
{
lock (Packets)
{
Packets.Enqueue(packet);
}
}
3.2消息委托
public delegate void OnReceive( NetPacket packet );
// 注册消息
public void AddHandler(string msgid, OnReceive handler)
{
handlers.Add(msgid, handler);
}
说明:1个string为key,委托函数为value,外部函数注册好消息的处理。
3.2处理更新
public void Update()
{
NetPacket packet = null;
for (packet = GetPacket(); packet != null; )
{
string msg = "";
// 获得消息标识符
packet.BeginRead(out msg);
OnReceive handler = null;
if (handlers.TryGetValue(msg, out handler))
{
// 根据消息标识符找到相应的OnReceive代理函数
if (handler != null)
handler(packet);
}
说明:
1.从消息队列中取队头的
2.读取消息开始时的string为消息id
消息id找到相应的OnReceive委托函数