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