C#/네트워크

[c#][서버] RecvBuffer, SendBuffer

goliot 2024. 6. 7. 15:55
반응형

TCP 패킷 조립 - RecvBuffer

  • TCP의 특성상, 흐름제어로 인해 패킷이 분리되어 도착할 수 있음
  • 그러므로 패킷이 나뉘어 왔을 때 전부 올 때까지 대기하다가 조립하는 동작을 추가해주자
  • readPos
    • 처리할지 말지 결정하는 커서 
      • writePos가 패킷의 끝까지 왔다면 read
      • 아니라면 대기
  • writePos
    • 지금까지 받은 바이트 커서
    • 마지막 바이트 뒤에 위치
  • 버퍼 공간이 부족해지면?
    • read와 write를 앞으로 보낼 수 있을 만큼 보냄
      • 항상 read <= write
      • 현재 wirte - read 만큼의 공간은 유지해야 함
public class RecvBuffer
{
    ArraySegment<byte> _buffer;
    int _readPos;
    int _writePos;

    public RecvBuffer(int bufferSize) //생성자
    {
        _buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
    }
    
    public int DataSize { get { return _writePos - _readPos; } } //유효 버퍼
    public int FreeSize { get { return _buffer.Count - _writePos; } } //남은 버퍼

    public ArraySegment<byte> WriteSegment //현재까지 받은 데이터의 유효 범위(read용)
    {
        get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _readPos, DataSize); }
    }

    public ArraySegment<byte> ReadSegment //남은 버퍼 유효 범위(write용)
    {
        get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _writePos, FreeSize); }
    }

    public void Clean() //버퍼 앞으로 당겨서 공간 만들기
    {
        int dataSize = DataSize;
        if(dataSize == 0) // r == w
        {
            //남은 데이터가 없으니 복사하지 않고 커서 위치만 리셋
            _readPos = _writePos = 0;
        }
        else
        {
            //남은게 있으면 시작 위치로 복사
            Array.Copy(_buffer.Array, _buffer.Offset + _readPos, _buffer.Array, _buffer.Offset, dataSize);
            _readPos = 0;
            _writePos = dataSize;
        }
    }

    public bool OnRead(int numOfBytes) //성공적 데이터 처리(read)
    {
        if (numOfBytes > DataSize)
            return false;

        _readPos += numOfBytes;
        return true;
    }

    public bool OnWrite(int numOfBytes) //성공적 recv(Write)
    {
        if (numOfBytes > FreeSize)
            return false;

        _writePos += numOfBytes;
        return true;
    }
}

 

SendBuffer

  • RecvBuffer와는 철학이 다름
    • Recv는 세션마다 자기 고유 RecvBuffer를 갖고 있음
    • 각 클라이언트마다 요청이 다르기 때문
    • 또한, RecvBuffer - Session 1대1이기 때문에, 버퍼를 전역변수로 사용 가능
  • Send의 경우
    • 버퍼를 전역변수로 사용한다면?
      • 사람이 100명이라면, 100명의 움직임을 받아서 100명에게 뿌려야 한다 -> 100 * 100번 복사 연산
    • 하지만 Send마다 그때그때 만든다면?
      • 그냥 생긴 100개를 보내주면 끝
    • 하지만 Send마다 크기가 모두 다를 텐데 버퍼크기를 어떻게 설정하나?
      • 아주 큰 덩어리를 하나 만들어 놓고, 잘라먹듯이 조금씩 쓰면 된다.
public class SendBufferHelper
{
    //각 쓰레드별로 SendBuffer를 따로 두어 경합 방지
    public static ThreadLocal<SendBuffer> CurrentBuffer = new ThreadLocal<SendBuffer>(() => { return null; });

    public static int ChunkSize { get; set; } = 4096 * 100;

    public static ArraySegment<byte> Open(int reserveSize)
    {
        if (CurrentBuffer.Value == null) //새삥
            CurrentBuffer.Value = new SendBuffer(ChunkSize);

        if (CurrentBuffer.Value.FreeSize < reserveSize) //여유 공간 부족
            CurrentBuffer.Value = new SendBuffer(ChunkSize); //새로 하나 판다

        return CurrentBuffer.Value.Open(reserveSize);
    }

    public static ArraySegment<byte> Close(int usedSize)
    {
        return CurrentBuffer.Value.Close(usedSize);
    }
}

public class SendBuffer //여기에서는 읽기만 하므로 락이 필요 없음
{
    byte[] _buffer; //큰 덩어리
    int _usedSize = 0; //잘려나간 크기(writePos의 역할)

    public int FreeSize { get { return _buffer.Length - _usedSize; } } //남은 공간

    public SendBuffer(int chunkSize) //덩어리 생성자
    {
        _buffer = new byte[chunkSize];
    }
    
    public ArraySegment<byte> Open(int reserveSize/*요청크기*/)
    { //작업 공간 할당
        if (reserveSize > FreeSize)
            return null;

        return new ArraySegment<byte>(_buffer, _usedSize, reserveSize);
    }

    public ArraySegment<byte> Close(int usedSize/*실제사용한크기*/)
    { // 작업 공간 반환
        ArraySegment<byte> segment = new ArraySegment<byte>(_buffer, _usedSize, usedSize);
        _usedSize += usedSize;

        return segment;
    }
}
반응형

'C# > 네트워크' 카테고리의 다른 글

[c#][서버] Serialization  (0) 2024.06.09
[c#][서버] PacketSession  (1) 2024.06.07
[c#][서버] TCP vs UDP  (0) 2024.06.07
[c#][서버] Connector  (0) 2024.06.07
[c#][서버] Session  (1) 2024.06.06