SendBuffer는 어떤 데이터들을 감싸서 보내기 위해 필요한 듯. 특히나 패킷 형태로 보내기 위해서는 바이트 배열에 입력하고 자르고 해야하니까..
RecvBuffer와 SendBuffer의 다른점?
: RecvBuffer의 경우 Session 내부에 존재하여 들어오는 데이터들이 제대로 된 형태로 들어왔는지 확인하고 처리하는 역할임.(Session과 RecvBuffer가 1대1 관계) 그러나 SendBuffer의 경우 Session 내부에 존재하지 않고 외부에 존재하여 Send() 함수를 호출하기 전 SendBuffer에 데이터를 입력하여 추출하고 보내는 형태를 취해야 함.
그렇다면 왜 SendBuffer는 내부에 존재하면 안되는가?
: 복사횟수가 늘어나면서 성능적 이슈가 발생할 수 있음
(약 100명한테 보낸다고 치면 미리 만들어놓은 버퍼를 그냥 100번 전송하기 vs 데이터를 일일이 복사해 버퍼를 만든다음 보내기 x 100 / 당연히 전자가 성능적으로 나음)
public class SendBufferHelper
{
ThreadLocal<SendBuffer> CurrentBufferHelper = new ThreadLocal<SendBuffer>( () => { return null; } );
int ChunkSize { get; set; } = 65535;
public ArraySegment<byte> Open(int reservedSize)
{
if(CurrentBufferHelper.Value == null)
CurrentBufferHelper.Value = new SendBuffer(ChunkSize);
if(reservedSize > CurrentBufferHelper.Value.FreeSize)
CurrentBufferHelper.Value = new SendBuffer(ChunkSize);
return CurrentBufferHelper.Value.Open(reservedSize);
}
public ArraySegment<byte> Close(int usedSize)
{
return CurrentBufferHelper.Value.Close(usedSize);
}
}
public class SendBuffer
{
byte[] _buffer;
int _usedSize;
public void SendBuffer(int chunkSize)
{
_buffer = new byte[chunkSize];
}
public int FreeSize { get { return _buffer.Length - _usedSize;} }
public ArraySegment<byte> Open(int reservedSize)
{
//reservedSize가 ChunkSize보다 커버리면 null 반환하는 듯
if(reservedSize > FreeSize)
return null;
return new ArraySegment<byte>(_buffer.Array, _usedSize, reservedSize);
}
public ArraySegment<byte> Close(int usedSize)
{
ArraySegment<byte> segment = new ArraySegment<byte>(_buffer.Array, _usedSize, usedSize);
_usedSize += usedSize;
// open시 여유공간을 포함하여 segment를 받았으므로 데이터 입력 이후에는
// 사용한 크기만큼 잘라서 return하여 send 할 수 있도록 한다.
return segment;
}
}
위 코드에서 TLS 의 역할은 무엇인가
위와 같은 그림으로 표현할 수 있지 않을까?
자신의 스레드에 일정 공간을 가져와서 전역으로 사용한다. 는 식으로 설명할 수 있을 것 같다.
위 코드에서는 _buffer의 일부 공간을 SendBufferHelper에서 TLS 영역으로 삼아 가져온다.
가져온 영역은 각 스레드에서 독립적으로 사용된다. 즉, 그 공간의 값을 아무리 바꾸더라도 다른 스레드에 영향을 끼치지 않고 자신의 영역 내에서의 값만 바뀌는 것이다.
위와 같은 장점으로 인해 코드에 굳이 lock을 사용하지 않아도 된다! 여러 스레드가 영역에 동시에 접근하여 값을 건드릴 일이 없기 때문이다.
하지만 주의할 점이 있는데 "가져온 그 영역"만 TLS에 해당하여 독립적으로 사용되지만 _buffer 자체는 TLS로 감싸 처리 된 것이 아니기 때문에 _buffer는 모든 스레드가 공유하여 사용하는 영역이 된다.
쉽게 말하면 모두가 공유하는 공간을 일부씩 잘라와 자신의 영역에서 독립적으로 사용하는 것이다.
위와 같은 이유로 SendBuffer에는 RecvBuffer와 달리 Clean()이라는 함수가 존재하지 않는다.
만약 sendQueue 같은 곳에 send할 데이터가 모여있고 아직 보내지지 않은 상태일 때 Clean()함수로 밀어버린다면 이 데이터 값들 또한 영향을 받기 때문이다. ( 모두가 공유하는 _buffer를 건드렸기 때문에!! )
_buffer를 일부 잘라쓰는거에서 TLS를 활용하는거지 _buffer 자체가 경합에 대해서 무적이란 말은 아님.
_buffer의 일부를 크게 잘라갔을 때 그곳에서 이루어지는 Write에 대해선 lock을 걸 필요가 없어지니 경합을 줄이는데 의미가 있음(공용공간 접근횟수 줄임)
만약 TLS 안써서 Write를 진행했다면 Open() 후 Wirte()하고 Close()할 떄 까지 lock 걸려서 대기하게 됨
이는 Send가 몰릴 수록 더 심각해진다.
'C# > 네트워크 관련' 카테고리의 다른 글
스스로 네트워크 프로그래밍 #6 - Selialization (0) | 2025.03.02 |
---|---|
스스로 네트워크 프로그래밍 - 코드 정리용 (0) | 2025.02.28 |
스스로 네트워크 프로그래밍 #5 - ReceiveBuffer (0) | 2025.02.24 |
스스로 네트워크 프로그래밍 #4 - 비동기 Send (0) | 2025.02.13 |
스스로 네트워크 프로그래밍 #3 - 비동기 Recv (0) | 2025.01.22 |