首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

高性能網絡編程:DataBuffer 與 BufferPool 零拷貝緩衝區實現全解析

     編輯:關於C語言

設計目標

高併發網絡服務中,頻繁 malloc/free 或 new/delete 緩衝區會導致:

  • 堆鎖爭用、碎片化、GC 壓力(托管語言)
  • 緩衝區大小不一,難以復用
  • 跨層傳遞(接收→解碼→業務→編碼→發送)產生多次拷貝

本文實現**池化緩衝區**,支援**引用計數/歸還池**、**ArraySegment 零拷貝切片**、**多池負載均衡**。

核心類:DataBuffer(緩衝區實體)

public class DataBuffer : IDisposable {
    public byte[] Data;           // 底層字節數組
    private int _length;          // 數組容量
    private int _position;        // 讀寫游標
    internal int _count;          // 有效數據長度
    internal BufferPool Pool;     // 所屬池(歸還用)

    public DataBuffer(int length) { // 分配新緩衝區
        _length = length;
        Data = new byte[length];
        _position = 0;
        _count = length;
    }
    public DataBuffer(byte[] data) { // 包裝現有數組
        Data = data;
        _length = data.Length;
        _count = data.Length;
    }

    // 寫入:返回實際寫入字節數,自動處理邊界
    public int Write(byte[] src, int offset = 0) {
        int avail = _length - _position;
        int toWrite = Math.Min(src.Length - offset, avail);
        if (toWrite > 0) {
            Buffer.BlockCopy(src, offset, Data, _position, toWrite);
            _position += toWrite;
            _count = Math.Max(_count, _position);
        }
        return toWrite;
    }

    // 讀取:返回 ArraySegment,零拷貝,推進游標
    public ArraySegment<byte> Read(int count) {
        int avail = _count - _position;
        int toRead = Math.Min(count, avail);
        var seg = new ArraySegment<byte>(Data, _position, toRead);
        _position += toRead;
        return seg;
    }

    // 重置游標,不清零數據
    public void Seek(int pos = 0) => _position = pos;

    // 獲取整個有效區段
    public ArraySegment<byte> GetSegment() => new ArraySegment<byte>(Data, 0, _count);

    // 歸還池
    public void Dispose() {
        if (Pool != null) {
            _position = 0;
            _count = 0;
            Pool.Push(this);
            Pool = null;
        }
    }
}

緩衝池:BufferPool(多池、線程安全、自動擴容)

public class BufferPool : IDisposable {
    private static List<BufferPool> _pools = new List<BufferPool>();
    private static int _nextPoolIndex = 0;
    private readonly Queue<DataBuffer> _buffers = new Queue<DataBuffer>();
    private readonly int _bufferLength;
    private bool _disposed;

    // 靜態工廠:創建多個池,分散鎖競爭
    public static void Setup(int poolCount, int buffersPerPool, int bufferLength = 2048) {
        lock (_pools) {
            for (int i = 0; i < poolCount; i++)
                _pools.Add(new BufferPool(buffersPerPool, bufferLength));
        }
    }
    public static void Clean() {
        lock (_pools) {
            foreach (var p in _pools) p.Dispose();
            _pools.Clear();
        }
    }
    // 輪詢獲取池,簡單負載均衡
    public static BufferPool GetPool() {
        lock (_pools) {
            if (_pools.Count == 0) return null;
            var pool = _pools[_nextPoolIndex];
            _nextPoolIndex = (_nextPoolIndex + 1) % _pools.Count;
            return pool;
        }
    }

    private BufferPool(int count, int bufferLength) {
        _bufferLength = bufferLength;
        for (int i = 0; i < count; i++)
            _buffers.Enqueue(new DataBuffer(bufferLength) { Pool = this });
    }

    // 取緩衝區:池空則自動分配新的(避免阻塞)
    public DataBuffer Pop() {
        lock (_buffers) {
            if (_buffers.Count > 0) return _buffers.Dequeue();
        }
        return new DataBuffer(_bufferLength) { Pool = this }; // 擴容
    }

    // 歸還緩衝區
    public void Push(DataBuffer buf) {
        lock (_buffers) {
            if (!_disposed) _buffers.Enqueue(buf);
        }
    }

    public void Dispose() {
        lock (this) {
            if (_disposed) return;
            _disposed = true;
            lock (_buffers) {
                while (_buffers.Count > 0) {
                    var b = _buffers.Dequeue();
                    b.Pool = null; // 斷開關聯,防止再次歸還
                }
            }
        }
    }
}

使用模式:接收-解碼-業務-編碼-發送 零拷貝流水線

// 1. 接收端:從池取緩衝區,直接 Socket.Receive 寫入
var pool = BufferPool.GetPool();
var buf = pool.Pop();
int received = socket.Receive(buf.Data, 0, buf.Data.Length, SocketFlags.None);
buf._count = received;
buf._position = 0;

// 2. 解碼器:Read() 返回 ArraySegment,無拷貝傳給協議層
while (buf._position < buf._count) {
    var segment = buf.Read(messageHeaderSize);
    // 解析頭部,獲取 body 長度
    var bodySeg = buf.Read(bodyLen);
    // 直接將 segment 遞交給業務處理器(引用計數或複製決策)
    ProcessMessage(bodySeg);
}

// 3. 業務處理完,Dispose() 歸還池
buf.Dispose();

// 4. 發送端:同理 Pop -> Write -> Socket.Send(seg) -> Dispose
var sendBuf = pool.Pop();
sendBuf.Write(headerBytes);
sendBuf.Write(bodyBytes);
socket.Send(sendBuf.GetSegment());
sendBuf.Dispose();

關鍵設計決策解析

決策理由
ArraySegment<byte> 而非 byte[]精確描述「緩衝區內的一段有效數據」,支援零拷貝切片、多段組裝(Scatter/Gather IO)
IDisposable + Pool 回引用using (var buf = pool.Pop()) 模式,確保異常時也能歸還,防洩漏
多池 + 輪詢單一隊列在高併發下鎖競爭劇烈,分池將鎖粒度降低 N 倍
Pop 自動擴容池耗盡時分配新緩衝區,避免業務阻塞;歸還時自動回收,池大小動態適應峰值
內部字段 _position/_count封裝讀寫狀態,外部不可隨意修改,保證緩衝區一致性

進階擴展方向

  1. BufferWriter / BufferReader:封裝 WriteInt32、ReadString 等類型化讀寫,支援大小端轉換、可變長整數。
  2. 引用計數:_refCount,允許同一緩衝區在多層處理器間共享,最後一釋放歸還池。
  3. 大對象堆 (LOH) 優化:>85KB 緩衝區直接分配在 LOH,池化更關鍵;可用 ArrayPool<byte>.Shared (CoreCLR) 替代自建。
  4. 內存對齊 / 預取:緩衝區大小設為 4KB/64KB 倍數,利用頁對齊與硬體預取。
  5. 零拷貝發送:Linux sendfile/splice、Windows TransmitFile,配合緩衝區指針直達網卡。

總結

DataBuffer + BufferPool 構成了高性能網絡層的**內存管理基石**。核心價值:**池化消除分配開銷、ArraySegment 實現零拷貝切片、IDisposable 保證歸還安全、多池消除鎖競爭**。在 .NET Core 2.1+ 中,推薦直接使用 System.Buffers.ArrayPool<byte>.Shared 替代自建池,並配合 Memory<byte>/ReadOnlySequence<byte> 實現更現代的管道處理(System.IO.Pipelines)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved