c# socket 解決粘包,半包

處理原理:編程

半包:即一條消息底層分幾回發送,先有個頭包讀取整條消息的長度,當不知足長度時,將消息臨時緩存起來,直到知足長度再解碼數組

粘包:兩條完整/不完整消息粘在一塊兒,通常是解碼完上一條消息,而後再判斷是否有剩餘字節,有的話緩存起來,循環半包處理緩存

客戶端接收代碼: socket

    private void callReceived(object sender, SocketAsyncEventArgs args)
    {
        var socket = sender as Socket;
        var bb = args.UserToken as ByteBuffer; 
        if (args.SocketError == SocketError.Success)
        {      
          bb.WriteBytes(args.Buffer, args.Offset, args.BytesTransferred);
          bb.MarkReaderIndex();
          int headLength = bb.ReadInt();
          int msgLength = headLength;//長度已-4          
          int readByteLength = bb.ReadableBytes();
          //解決半包
          if (msgLength > readByteLength)
          {
              //還原讀取索引記錄
              bb.ResetReaderIndex(); 
          }
          else {
              //是否去掉包頭
              byte[] filthyBytes= bb.ToArray(); 
              System.Console.WriteLine(System.Text.Encoding.UTF8.GetString(filthyBytes));
              
              //解決粘包剩餘
              bb.Clear();
              int useLength = filthyBytes.Length;
              int lastOffSetLength = filthyBytes.Length - useLength;
              if (lastOffSetLength > 0) {
                  bb.WriteBytes(filthyBytes, lastOffSetLength, filthyBytes.Length);
              }              
          }
        }
        else {
            //丟去byte處理
            System.Console.WriteLine("error callReceived");
        }
        _socket.ReceiveAsync(args);
    }

 

服務端發送代碼:ui

            ByteBuffer bb = ByteBuffer.Allocate(50);
            byte[] sendBytes=System.Text.Encoding.UTF8.GetBytes("1234567890abcdefg");
            Console.WriteLine("send msg length : " + sendBytes.Length);
            bb.WriteInt(sendBytes.Length);
            bb.WriteBytes(sendBytes);
            Send(bb.ToArray(), _clients.ToArray());


    public void Send(byte[] msg, params  SocketAsyncEventArgs[] sockets)
    {
        System.Console.WriteLine(" Send msg :");
        foreach (SocketAsyncEventArgs s in sockets)
        {
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SetBuffer(msg, 0, msg.Length);
            //args.RemoteEndPoint = s.RemoteEndPoint;
            args.AcceptSocket = s.AcceptSocket;

            args.Completed += new EventHandler<SocketAsyncEventArgs>(this.callSended);
            System.Console.WriteLine(" AcceptSocket :" + s.AcceptSocket.RemoteEndPoint.ToString());

            args.AcceptSocket.SendAsync(args);
        }
       
    }
ByteBuffer 類基礎跟netty相同,網上覆制的

using System;
 
public class ByteBuffer
{
    //字節緩存區
    private byte[] buf;
    //讀取索引
    private int readIndex = 0;
    //寫入索引
    private int writeIndex = 0;
    //讀取索引標記
    private int markReadIndex = 0;
    //寫入索引標記
    private int markWirteIndex = 0;
    //緩存區字節數組的長度
    private int capacity;
 
    /**
     * 構造方法
     */
    private ByteBuffer(int capacity)
    {
        buf = new byte[capacity];
        this.capacity = capacity;
    }
 
    /**
     * 構造方法
     */
    private ByteBuffer(byte[] bytes)
    {
        buf = bytes;
        this.capacity = bytes.Length;
    }
 
    /**
     * 構建一個capacity長度的字節緩存區ByteBuffer對象
     */
    public static ByteBuffer Allocate(int capacity)
    {
        return new ByteBuffer(capacity);
    }
 
    /**
     * 構建一個以bytes爲字節緩存區的ByteBuffer對象,通常不推薦使用
     */
    public static ByteBuffer Allocate(byte[] bytes)
    {
        return new ByteBuffer(bytes);
    }

    /**
     * 翻轉字節數組,若是本地字節序列爲低字節序列,則進行翻轉以轉換爲高字節序列
     */
    private byte[] flip(byte[] bytes)
    {
        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(bytes);
        }
        return bytes;
    }


    /**
 * 肯定內部字節緩存數組的大小
 */
    private int FixSizeAndReset(int currLen, int futureLen)
    {
        if (futureLen > currLen)
        {
            //以原大小的2次方數的兩倍肯定內部字節緩存區大小
            int size = FixLength(currLen) * 2;
            if (futureLen > size)
            {
                //以未來的大小的2次方的兩倍肯定內部字節緩存區大小
                size = FixLength(futureLen) * 2;
            }
            byte[] newbuf = new byte[size];
            Array.Copy(buf, 0, newbuf, 0, currLen);
            buf = newbuf;
            capacity = newbuf.Length;
        }
        return futureLen;
    }

    /**
     * 根據length長度,肯定大於此leng的最近的2次方數,如length=7,則返回值爲8
     */
    private int FixLength(int length)
    {
        int n = 2;
        int b = 2;
        while (b < length)
        {
            b = 2 << n;
            n++;
        }
        return b;
    }
 
 
    /**
     * 將bytes字節數組從startIndex開始的length字節寫入到此緩存區
     */
    public ByteBuffer WriteBytes(byte[] bytes, int startIndex, int length)
    {
        lock (this)
        {
            int offset = length - startIndex;
            if (offset <= 0) return this;
            int total = offset + writeIndex;
            int len = buf.Length;
            FixSizeAndReset(len, total);
            for (int i = writeIndex, j = startIndex; i < total; i++, j++)
            {
                this.buf[i] = bytes[j];
            }
            writeIndex = total;
        }
        return this;
    }
 
    /**
     * 將字節數組中從0到length的元素寫入緩存區
     */
    public ByteBuffer WriteBytes(byte[] bytes, int length)
    {
        return WriteBytes(bytes, 0, length);
    }
 
    /**
     * 將字節數組所有寫入緩存區
     */
    public ByteBuffer WriteBytes(byte[] bytes)
    {
       return WriteBytes(bytes, bytes.Length);
    }
 
    /**
     * 將一個ByteBuffer的有效字節區寫入此緩存區中
     */
    public ByteBuffer Write(ByteBuffer buffer)
    {
        if (buffer == null) return this;
        if (buffer.ReadableBytes() <= 0) return this;
       return WriteBytes(buffer.ToArray());
    }
 
    /**
     * 寫入一個int16數據
     */
    public ByteBuffer WriteShort(short value)
    {
       return WriteBytes(flip(BitConverter.GetBytes(value)));
    }
 
    /**
     * 寫入一個uint16數據
     */
    public ByteBuffer WriteUshort(ushort value)
    {
        return WriteBytes(flip(BitConverter.GetBytes(value)));
    }

    /**寫入字符串*/
    public ByteBuffer WriteString(string value)
    {
        int len = value.Length;
        WriteInt(len);
        //System.Text.Encoding.BigEndianUnicode.GetBytes
        WriteBytes(System.Text.Encoding.UTF8.GetBytes(value));
        return this;
    }
    /**讀取字符串*/
    public String ReadString()
    {
        int len =ReadInt();
        byte[] bytes =new byte[len];
        ReadBytes(bytes,0,len); 

        return System.Text.Encoding.UTF8.GetString(bytes);
    }

 
    /**
     * 寫入一個int32數據
     */
    public ByteBuffer WriteInt(int value)
    {
        //byte[] array = new byte[4];
        //for (int i = 3; i >= 0; i--)
        //{
        //    array[i] = (byte)(value & 0xff);
        //    value = value >> 8;
        //}
        //Array.Reverse(array);
        //Write(array);
       return WriteBytes(flip(BitConverter.GetBytes(value)));
    }
 
    /**
     * 寫入一個uint32數據
     */
    public ByteBuffer WriteUint(uint value)
    {
        return WriteBytes(flip(BitConverter.GetBytes(value)));
    }
 
    /**
     * 寫入一個int64數據
     */
    public ByteBuffer WriteLong(long value)
    {
       return WriteBytes(flip(BitConverter.GetBytes(value)));
    }
 
    /**
     * 寫入一個uint64數據
     */
    public ByteBuffer WriteUlong(ulong value)
    {
      return  WriteBytes(flip(BitConverter.GetBytes(value)));
    }
 
    /**
     * 寫入一個float數據
     */
    public ByteBuffer WriteFloat(float value)
    {
       return WriteBytes(flip(BitConverter.GetBytes(value)));
    }
 
    /**
     * 寫入一個byte數據
     */
    public ByteBuffer WriteByte(byte value)
    {
        lock (this)
        {
            int afterLen = writeIndex + 1;
            int len = buf.Length;
            FixSizeAndReset(len, afterLen);
            buf[writeIndex] = value;
            writeIndex = afterLen;
        }
        return this;
    }
     
    /**
     * 寫入一個double類型數據
     */
    public ByteBuffer WriteDouble(double value) 
    {
        return WriteBytes(flip(BitConverter.GetBytes(value))); 
    }

 
 
    /**
     * 讀取一個字節
     */
    public byte ReadByte()
    {
        byte b = buf[readIndex];
        readIndex++;
        return b;
    }
 
    /**
     * 從讀取索引位置開始讀取len長度的字節數組
     */
    private byte[] Read(int len)
    {
        byte[] bytes = new byte[len];
        Array.Copy(buf, readIndex, bytes, 0, len);
        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(bytes);
        }
        readIndex += len;
        return bytes;
    }
 
    /**
     * 讀取一個uint16數據
     */
    public ushort ReadUshort()
    {
        return BitConverter.ToUInt16(Read(2), 0);
    }
 
    /**
     * 讀取一個int16數據
     */
    public short ReadShort()
    {
        return BitConverter.ToInt16(Read(2), 0);
    }
 
    /**
     * 讀取一個uint32數據
     */
    public uint ReadUint()
    {
        return BitConverter.ToUInt32(Read(4), 0);
    }
 
    /**
     * 讀取一個int32數據
     */
    public int ReadInt()
    {
        return BitConverter.ToInt32(Read(4), 0);
    }
 
    /**
     * 讀取一個uint64數據
     */
    public ulong ReadUlong()
    {
        return BitConverter.ToUInt64(Read(8), 0);
    }
 
    /**
     * 讀取一個long數據
     */
    public long ReadLong()
    {
        return BitConverter.ToInt64(Read(8), 0);
    }
 
    /**
     * 讀取一個float數據
     */
    public float ReadFloat()
    {
        return BitConverter.ToSingle(Read(4), 0);
    }
 
    /**
     * 讀取一個double數據
     */
    public double ReadDouble() 
    {
        return BitConverter.ToDouble(Read(8), 0);
    }
 
    /**
     * 從讀取索引位置開始讀取len長度的字節到disbytes目標字節數組中
     * @params disstart 目標字節數組的寫入索引
     */
    public void ReadBytes(byte[] disbytes, int disstart, int len)
    {
        int size = disstart + len;
        for (int i = disstart; i < size; i++)
        {
            disbytes[i] = this.ReadByte();
        }
    }
 
    /**
     * 清除已讀字節並重建緩存區
     */
    public void DiscardReadBytes() 
    {
        if(readIndex <= 0) return;
        int len = buf.Length - readIndex;
        byte[] newbuf = new byte[len];
        Array.Copy(buf, readIndex, newbuf, 0, len);
        buf = newbuf;
        writeIndex -= readIndex;
        markReadIndex -= readIndex;
        if (markReadIndex < 0)
        {
            markReadIndex = readIndex;
        }
        markWirteIndex -= readIndex;
        if (markWirteIndex < 0 || markWirteIndex < readIndex || markWirteIndex < markReadIndex)
        {
            markWirteIndex = writeIndex;
        }
        readIndex = 0;
    }
 
    /**
     * 清空此對象
     */
    public void Clear()
    {
        buf = new byte[buf.Length];
        readIndex = 0;
        writeIndex = 0;
        markReadIndex = 0;
        markWirteIndex = 0;
    }
 
    /**
     * 設置開始讀取的索引
     */
    public void SetReaderIndex(int index)
    {
        if (index < 0) return;
        readIndex = index;
    }
 
    /**
     * 標記讀取的索引位置
     */
    public int MarkReaderIndex()
    {
        markReadIndex = readIndex;
        return markReadIndex;
    }
 
    /**
     * 標記寫入的索引位置
     */
    public void MarkWriterIndex() 
    {
        markWirteIndex = writeIndex;
    }
 
    /**
     * 將讀取的索引位置重置爲標記的讀取索引位置
     */
    public void ResetReaderIndex() 
    {
        readIndex = markReadIndex;
    }
 
    /**
     * 將寫入的索引位置重置爲標記的寫入索引位置
     */
    public void ResetWriterIndex() 
    {
        writeIndex = markWirteIndex;
    }
 
    /**
     * 可讀的有效字節數
     */
    public int ReadableBytes()
    {
        return writeIndex - readIndex;
    }
 
    /**
     * 獲取可讀的字節數組
     */
    public byte[] ToArray()
    {
        byte[] bytes = new byte[writeIndex];
        Array.Copy(buf, 0, bytes, 0, bytes.Length);
        return bytes;
    }
 
    /**
     * 獲取緩存區大小
     */
    public int GetCapacity()
    {
        return this.capacity;
    }
}

 

最後小結一下:this

相信你們都看過TCP編程的書,書本上的理論廢話長篇,說了一堆又給出一堆無用的代碼。只有看源碼才知道原理是怎樣,源碼實現一切。。。spa

在看的過程發現原做者寫得也不是很好,簡單的事情老是搞得那麼複雜。。。。。netty

這行真是水深火熱啊code

相關文章
相關標籤/搜索