在Socket应用开发中,还有一个话题是讨论的比较多的,那就是数据接收后如何处理的问题。这也是一个令刚接触Socket开发的人很头疼的问题。
因为Socket的TCP通讯中有一个“粘包”的现象,既:大多数时候发送端多次发送的小数据包会被连在一起被接收端同时接收到,多个小包被组成一个大包被接收。有时候一个大数据包又会被拆成多个小数据包发送。这样就存在一个将数据包拆分和重新组合的问题。那么如何去处理这个问题呢?这就是我今天要讲的通讯协议。
所谓的协议就是通讯双方协商并制定好要传送的数据的结构与格式。并按制定好的格式去组合与分析数据。从而使数据得以被准确的理解和处理。
那么我们如何去制定通讯协议呢?很简单,就是指定数据中各个字节所代表的意义。比如说:第一位代表封包头,第二位代表封类型,第三、四位代表封包的数据长度。然后后面是实际的数据内容。
如下面这个例子:
01
|
01
|
06 00
|
01 0f ef 87 56 34
|
协议类别
|
协议代码
|
数据长度
|
实际数据
|
前面三部分称之为封包头,它的长度是固定的,第四部分是封包数据,它的长度是不固定的,由第三部分标识其长度。因为我们的协议将用在TCP中,所以我没有加入校验位。原因是TCP可以保证数据的完整性。校验位是没有必要存在的。
接下来我们要为这个数据封包声明一个类来封装它:
public class Message
......{
private byte _class;
private byte _flag;
private int _size;
private byte[] _content;
public byte[] Content
......{
get ......{ return _content; }
set ......{ _content = value; }
}
public int Size
......{
get ......{ return _size; }
set ......{ _size = value; }
}
public byte Flag
......{
get ......{ return _flag; }
set ......{ _flag = value; }
}
public byte Class
......{
get ......{ return _class; }
set ......{ _class = value; }
}
public Message()
......{
}
public Message(byte @class, byte flag, byte[] content)
......{
_class = @class;
_flag = flag;
_size = content.Length;
_content = content;
}
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;
}
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;
}
}
......{
private byte _class;
private byte _flag;
private int _size;
private byte[] _content;
public byte[] Content
......{
get ......{ return _content; }
set ......{ _content = value; }
}
public int Size
......{
get ......{ return _size; }
set ......{ _size = value; }
}
public byte Flag
......{
get ......{ return _flag; }
set ......{ _flag = value; }
}
public byte Class
......{
get ......{ return _class; }
set ......{ _class = value; }
}
public Message()
......{
}
public Message(byte @class, byte flag, byte[] content)
......{
_class = @class;
_flag = flag;
_size = content.Length;
_content = content;
}
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;
}
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;
}
}
我们可以用Tobytes()和FromBytes()将封包转换成二进制数组和从二进制数组转换回来。
事情看起来已经解决了,但……真的是这样子吗?不然,我们知道,TCP数据是以流的形式被传送的,我们并不知道一个数据包是否被传送完毕,也不知道我们接收回来的数据包中是否有多个数据包,如果直接使用FromBytes()来转换的话,很可能会因为数据不完整而出现异常,也有可能会因为数据中含有多个数据包而导致数据丢失(因为你并不知道这些数据中含有多少个数据包)。那我们该怎么办?这也不难,我们先把接收回来的数据写入一个流中。然后分析其中是否有完整的数据包,如果有,将其从流中取出,并将这部分数据从流中清除。直到流中没有完整的数据为止,以后接收回来的数据就将其写入流的结尾处,并从头继续分析。直到结束。
让我们来看看这部分的代码:
public class 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)
......{
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)