用c#实现了一个网络通信聊天的功能。服务端使用异步接收信息方式。当客户端发送 “发送信息”请求,经服务端接收请求进行数据处理后,再发送给客户端。在这里,我用的是 NetWorkStream.read() 来进行包信息的解析(虽然目前未发现包丢失问题)。现在我想实现一个环形缓冲区来读写流,进行数据解析。类和write,read方法已经写好,但是怎样应用在程序里不是很清晰。大家给个思路。

解决方案 »

  1.   

    1:实现环型队列类。
    2:把你socket读到了数据写入到队列中。
    3:从队列中读出数据转发出去。
      

  2.   

       服务端是异步操作,是异步不会出现丢包的情况吗?因为当前我的实现方式是,头包结构包含两个属性,命令和长度,然后序列化为字节对象byteHeader,然后计算它的长度byteHeader.Length,每次读包时只要读取了byteHeader.Length个字节,就判定它为头包,但这样的处理方式有问题,如果某一时刻丢了包,其后所有的数据将无法正常解析。所以现在想通过给包头再定义一个特征标示,表明它是包头,然后通过环形缓冲流机制读写,以解决丢包容错问题。(这是我目前所了解的思路)
      

  3.   

    既然是防止丢包、混包现象,那么服务器和客户端都应该用到
    大体思路是
    1、建立一个缓冲类,取名 MessageStream
    2、类中包含字段 byte[] buffer , int length; int capcity;
      分别表示数据缓冲区、当前缓冲区含有实际字节数、以及缓冲区总容量
    3、将每次发送的消息格式化:如 消息=消息类型+消息长度+消息内容,具体自己设计
    4、为客户端的Socket绑定一个MessageStream 类对象,为服务器端 每个 对应的 Socket 绑定 一个MessageStream类对象,具体怎样绑定,LZ自己设计(可以将Socket和MessageStream类对象封装在一个类中)
    5、每次每个 Socket 接受到数据时,先将 接收到的 byte数据 添加到 该Socket对应的MessageStream类对象中去,然后循环读取消息,直到没有一条完整的消息为止,继续接受数据,一次循环上个源码吧 参考class MessageStream  //消息流类
        {
            private byte[] buffer;
            private int length;
            private int capacity;        public MessageStream()//构造函数
            {
                buffer = new byte[0];
                length = 0;
                capacity = 0;
            }        public bool ReadMessage(out Message msg)//从流中读取完整Message对象
            {
                Message temp = new Message();
                if (length >= 5)
                {
                    MemoryStream stream = new MemoryStream(buffer);
                    BinaryReader reader = new BinaryReader(stream);                temp.Head = reader.ReadByte();
                    temp.Length = reader.ReadInt32();                if (temp.Length <= (length - 5))
                    {
                        temp.Content = reader.ReadBytes(temp.Length);
                        reader.Close();
                        Remove(temp.Length + 5);
                        msg = temp;
                        return true;
                    }
                    else
                    {
                        msg = null;
                        return false;
                    }            }
                else
                {
                    msg = null;
                    return false;
                }
            }
            
            //移出队列前面的byte
            public void Remove(int count)
            {
                if (count <= length)
                {
                    Buffer.BlockCopy(buffer, count, buffer, 0, length - count);
                    length -= count;
                    Array.Clear(buffer, length, capacity - length);
                }
                else
                {
                    length = 0;
                    Array.Clear(buffer, 0, capacity);
                }
            }        public void Write(byte[] bufferEx, int offset, int count) //写数据(byte类型的数据)
            {
                if (count > bufferEx.Length - offset)
                {
                    count = bufferEx.Length - offset;
                }
                EnsureCapacity(length + count);//再写入之前,判断容量大小            Buffer.BlockCopy(bufferEx, offset, buffer, length, count);
                length += count;        }        //确保缓冲区足够大
            public void EnsureCapacity(int count)
            {
                if (count <= capacity)
                {
                    return;
                }
                if (count < 2 * capacity)
                {
                    count = 2 * capacity;
                }            byte[] bufferEx = new byte[count];
                capacity = count;
                Buffer.BlockCopy(buffer, 0, bufferEx, 0, length);
                buffer = bufferEx;
            }
        }
      

  4.   

       对,我现在就是想要采用这样的思路,消息流类也有了。我发送和接收包信息分两段,消息 = 包头(消息类型,消息长度) + 消息体。但有人建议还应该给包头设置一个标示,用于区分某个包是否接收完毕。但是我现在不是很清楚,write方法和read 方法应该加在什么地方合适。
      

  5.   

    1:看来楼主是要做一个自定义的应用层协议。
    2:采有环形缓冲区的想法是对了。因为应用层协议收到数据后,应用不一定会处理,那么采用环形的话就可以覆盖了。
    3:至于环形队列,那是在服务器和客户端都得使用。(说真的,如果是应用层协议,其实最好是同一份代码)
    4:环形队列说白了是个缓冲区。理论上与你用的byte[] buffer没有太大的区别,只是这个缓冲区空间是稳定的。5:至于应用层协议如何设计,其实还是有些技巧,比如要考虑不同传输介质,如何与业务分离等。
      

  6.   

    第一,这个问题跟是不是 异步接受 没有任何关系,这只是涉及到接受到数据之后,怎样处理的问题,不涉及是否异步通讯。
    第二,没有必要用环形缓冲区,我给出的MessageStream类中包含一个buffer缓冲区,它不是环形队列,而只是一个byte数组,这个数组的长度是可以根据每次写入的数据的大小来判断是否应该增加的( EnsureCapacity方法就是这个作用,每次Write之前,都要判断buffer是否足够大,如果不够大,那么就加长buffer的大小)。
    第三,每次接受到数据(从Socket接受到的byte数据)时,都需要先将数据写入对应的MessageStream内,也就是说这个时候调用Write方法,然后马上循环调用Read方法,看能否从MessageStream中取得一条完整的消息(这是根据你包头中的消息长度来判断的)。
    第四,不管你是异步还是同步,只需要在每次接受到数据的时候先调用Write,再调用Read。同步时,在Socket.Receive()之后调用;异步时,在Socket.EndReceive()之后调用。
    第五 ,我个人觉得,不需要给包头设一个标示,具体原因上面已经给出(即根据包头里面的消息长度就可以判断是否有完整消息)。
      

  7.   

    用环形队列 起到一个 数据缓冲的作用
    这和 长度可以动态增长的非环形队列 作用一样类似
    byte[] buffer=new byte[100];buffer[(index+1)%100]=newByte;//环形//先确保长度足够
    buffer[index]=newByte; //非环形
      

  8.   

      1. 明白了,我刚开始的理解有误。
       2. 它在read之后,会不会马上丢弃已读的空间,假如客户端数量上来了,一个线程维护一个缓冲区,对内存的开销比较大。
      3,4.恩,明白了。
      5. 我也是这样想的,我开始就是卡死缓冲区长度来接数据,但是如果发生丢包或其它异常,系统将不能容错。用标示好比一个校验码,让我更清楚什么时候一个包完了,开始下一个包的接收,和精确地取出这个包的包头信息。
      

  9.   

    如果只是聊天功能的话
    用开源网络组件ALAZ.SystemEx.NetEx 
    两个小时完全可以搞定
    有拖拉机就不用手推车,有火车就不用拖拉机.
    毕竟是C#又不是C\C++,开发速度是第一的;另外不要让自己的代码反编译出来很难看.
      

  10.   


    请问,要是处理你这个MessageStream中buffer的数据没有被处理的话,你想过会出什么问题吗?打个最简单的比方,Socket有个缓冲区,两个SocketM&N连接成功。M向N不停的发送数据,N不做事(即,从来不调用N的Read功能)。Socket是不会爆的,但是要是用你的MessageStream,同时“数组的长度是可以根据每次写入的数据的大小来判断是否应该增加”,那么N的缓冲区是否会无限扩张?