silverlight客户端和服务端就像一对被微软家长阻隔的恋人,服务端提供了安全策略文件这个硬通货,就如同男人买了房,一切都开绿灯了。
这对恋人终于可以约会了,他们如何飞鸿传书呢?是写在枫叶上,还是封上了火漆的羊皮卷里。其实每个民族有自己的风俗,每个程序员也可以写出不同的协议。
我们把他们通讯的基本单位抽象成Message,你可以这么描述它:
{
/// <summary>
/// 处理信息的socket对象
/// </summary>
public System.Net.Sockets.Socket clientSocket { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public byte[] Content{get;set;}
/// <summary>
/// 消息长度
/// </summary>
public int Size{get;set;}
/// <summary>
/// 消息状态
/// </summary>
public byte Flag{get;set;}
/// <summary>
/// 消息类别
/// </summary>
public byte Class {get;set;}
也就是说服务端和客户端通讯的时候是一个个message为单位发送过去的,但是传输过程中,服务端不可能刚好完整就接到了一个Message,也可能接收到半个Message,所以需要加一个size来判断是否Message读取完毕。
在封装Message的时候我们要写一个MessageStream类来方便封装Message,一般网络游戏服务端和客户端都会有这样的类,来从byte数组中把数据读出,或者把数据写入到byte数组中,这样的类里一般都有ReadInt,ReadByte,ReadString,ReadFloat,WriteInt,WriteByte , WriteString,WriteFloat 这样的方法。
这里我给出一个很简单的封装类MessageStream
{
private byte[] buffer;
private int position;
private int length;
private int capacity;
public MessageStream()
{
buffer = new byte[0];
position = 0;
length = 0;
capacity = 0;
}
private byte ReadByte()
{
if (this.position >= this.length)
{
return 0;
}
return this.buffer[this.position++];
}
private int ReadInt()
{
int num = this.position += 4;
if (num > this.length)
{
this.position = this.length;
return -1;
}
return (((this.buffer[num - 4] | (this.buffer[num - 3] << 8)) | (this.buffer[num - 2] << 0x10)) | (this.buffer[num - 1] << 0x18));
}
private byte[] ReadBytes(int count)
{
int num = this.length - this.position;
if (num > count)
{
num = count;
}
if (num <= 0)
{
return null;
}
byte[] buffer = new byte[num];
if (num <= 8)
{
int num2 = num;
while (--num2 >= 0)
{
buffer[num2] = this.buffer[this.position + num2];
}
}
else
{
Buffer.BlockCopy(this.buffer, this.position, buffer, 0, num);
}
this.position += num;
return buffer;
}
public bool Read(out Message message)
{
message = null;
position = 0;
if (length > 6)
{
message = new Message();
message.Class = ReadByte();
message.Flag = ReadByte();
message.Size = ReadInt();
if (message.Size <= 0 || message.Size <= length - position)
{
if (message.Size > 0)
{
message.Content = ReadBytes(message.Size);
}
Remove(message.Size + 6);
return true;
}
else
{
message = null;
return false;
}
}
else
{
return false;
}
}
private void EnsureCapacity(int value)
{
if (value <= this.capacity)
return;
int num1 = value;
if (num1 < 0x100)
num1 = 0x100;
if (num1 < (this.capacity * 2))
num1 = this.capacity * 2;
byte[] buffer1 = new byte[num1];
if (this.length > 0)
Buffer.BlockCopy(this.buffer, 0, buffer1, 0, this.length);
this.buffer = buffer1;
this.capacity = num1;
}
public void Write(byte[] intbuffer, int offset, int count)
{
if (intbuffer.Length - offset < count)
{
count = intbuffer.Length - offset;
}
EnsureCapacity(length + count);
Array.Clear(this.buffer, length, capacity - length);
Buffer.BlockCopy(intbuffer, offset, this.buffer, length, count);
length += count;
}
{
if (length >= count)
{
Buffer.BlockCopy(buffer, count, buffer, 0, length - count);
length -= count;
Array.Clear(buffer, length, capacity - length);
}
else
{
length = 0;
Array.Clear(buffer, 0, capacity);
}
}
}
有了MessageStream这样的辅助类了,我们就可以在Message类加上一个添加消息和一个读取消息的方法
/// 把消息对象转化为byte[]
/// </summary>
/// <returns></returns>
public byte[] ToBytes()
{
byte[] _byte;
using (MemoryStream mem = new MemoryStream())
{
BinaryWriter writer = new BinaryWriter(mem);
writer.Write(Class);
writer.Write(Flag);
writer.Write(Size);
if (Size > 0)
{
writer.Write(Content);
}
_byte = mem.ToArray();
writer.Close();
}
return _byte;
}
/// <summary>
/// 从byte[]中读取一个message对象
/// </summary>
/// <param name="Buffer"></param>
/// <returns></returns>
public static Message FromBytes(byte[] Buffer)
{
Message message = new Message();
using (MemoryStream mem = new MemoryStream(Buffer))
{
BinaryReader reader = new BinaryReader(mem);
message.Class = reader.ReadByte();
message.Flag = reader.ReadByte();
message.Size = reader.ReadInt32();
if (message.Size > 0)
{
message.Content = reader.ReadBytes(message.Size);
}
reader.Close();
}
return message;
}
有了MessageStream,Message这两个忠实的仆人的帮助,SL客户端和服务端这对恋人就不用像发摩尔斯电码那样来交流了,他们只要口述旨意,让两个仆人去滴滴答答发电报就行了。见过电影里发电报的都知道,有人发电报,就有人收电报。所以客户端有这样两个仆人,服务端也得有这样两个仆人。那我们程序员的话说SL客户端有这样两个类,同样服务端也要有这样两个类。
当然以上我只是简单描述了一个Message对象,在真实项目里Message对象没这么简单。我举个例子:
格式:
循环 |
循环 | ||||||||||||||||||||||||||
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
不定长 | ||||||||||||
消息个数 |
消息总长度 |
用户UID |
用户CID |
单个消息指针起始位置 |
消息内容 | ||||||||||||||||||||||
消息内容协议 |
|||||||||||||||||||||||||||
0 |
1 |
2 |
3 |
4 |
5 |
不定长 |
|||||||||||||||||||||
消息Group |
消息具体type |
时间戳 |
消息逻辑内容 |
||||||||||||||||||||||||
HTTP命令区:
Message:1 (byte) 消息个数
MessageLength: 120 (int) byte数组总长度
UID:2 (int)用户标识ID
CID 1 (int)用户子ID
MessagePointer: 0 (short) 单个消息指针起始位置
HTTP 内容区:
GroupID:1 (byte) 消息组ID
GroupType:1 (byte) 消息组具体操作ID
Timestamp: 0 (int) 时间戳
ContentLength: 10 (short) 消息内容的长度(根据内容来定类型)
Content:”你好吗?” (string) 消息内容
这种协议一次可以发多个消息体,只带一个消息头,也带上用户的信息,因为mmorpg里参与最多是用户,发的消息也和用户编号直接相关。
有兴趣的同学可以写下这个种协议的写入和读取。到此为止,是不是服务端和客户端就可以踏入婚姻的殿堂了,可惜还远没有呢,这是各自准备了两个伴郎和伴娘,客户端急了:“老公,你房子什么时候才装修好啊?”
大家看到这里也在想,服务端什么时候才可以搭建好啊,就像装房子一样,急不得,还有好多问题。请看下一篇:
Silverlight MMORG WebGame游戏设计(四)-----Client:Server!房子啥时候装好?我急嫁人啊!