Socket开发之通讯协议及处理

所谓的协议就是通讯双方协商并制定好要传送的数据的结构与格式。并按制定好的格式去组合与分析数据。从而使数据得以被准确的理解和处理。
那么我们如何去制定通讯协议呢?很简单,就是指定数据中各个字节所代表的意义。比如说:第一位代表封包头,第二位代表封类型,第三、四位代表封包的数据长度。然后后面是实际的数据内容。
如下面这个例子:
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;
        }

    } 
我们可以用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)
        {
            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[] buffer, int offset, int count)
        {
            if (buffer.Length - offset < count)
            {
                count = buffer.Length - offset;
            }
            EnsureCapacity(buffer.Length + count);
            Array.Clear(_buffer, _length, _capacity - _length);
            Buffer.BlockCopy(buffer, offset, _buffer, _length, count);
            _length += count;
        }

        private void Remove(int 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);
            }
        }
    }  
这个类的使用非常简单,你只要用Write(byte[] buffer, int offset, int count)将接收到的数据写入数据流中,并用bool Read(out Message message)将数据中的第一个数据包取出,如果函数返回True,就说明取回一个封包成功,如果返回False,则说明流中已经没有完整的封包,你需要继续接收后面的数据以组成一个完整的封包。
这们我们的数据分析就会变得非常简单。我们可以在ReceiveCallBack回调函数中将接收到的数据写入到流中并通知线程池中的工作者线程分析数据流并处理数据。我在前面的关于Socket异步操作的文章中的Analyzer函数就是用这两个类来分析处理数据的。这样的好处理就是,Socket工作线程只需要负责数据的接收,并将其写入流,其它的事情由其它的线程这处理,就不会因为处理的时间过长而导致接收操作被阻塞。从而影响Socket的性能。
本文所述方法只是协议处理的多种方法中的其中一种,而且可能并不是很优秀的方法,如果谁有更好的方法,还希望您能和我多多交流。好了,今天就到这里了,关于Socket的文章到这里可能就告一段落了,我现在在研究VS2008里面的新东西,如果有什么必得的话,我会继续写出来的。谢谢大家的支持。

你可能感兴趣的:(Socket开发之通讯协议及处理)