RecvBuffer 알아보러 가기
public abstract class Session
{
RecvBuffer _recvBuffer = new RecvBuffer(1024);
}
RecvBuffer같은 경우
클라이언트 또는 서버가 메시지를 수신하는데 사용된다. 이는 서로 다른 요청사항을 보내기 때문에 각각의 세션 마다 개별적인 RecvBuffer가 필요하다. 그러므로 내부에서 설정한다.
SendBuffer같은 경우
외부에서 설정한다.
1. 문자열을 바이트 배열로 만들기
public override void OnConnected(EndPoint endPoint)
{
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to piggy Server");
}
2. 패킷 정보로 보내기
01 객체의 멤버 변수 설정
class Pig
{
public int weight;
public int eat;
}
public override void OnConnected(EndPoint endPoint)
{
Pig pig = new Pig() { weight = 50, eat = 10 };
}
02 BitConverter.GetBytes
멤버 변수들을 byte배열로 변환하여 저장
byte[] sendBuff = new byte[1024];
byte[] buffer = BitConverter.GetBytes(pig.weight);
byte[] buffer2 = BitConverter.GetBytes(pig.eat);
03 buffer배열의 데이터를 sendBuff배열의 0번째 인덱스부터 buffer.Length만큼 복사
Array.Copy(buffer, 0, sendBuff,0,buffer.Length);
04 buffer2 배열의 데이터를 sendBuff배열의 buffer.Length 인덱스부터 buffer2.Length만큼 복사
Array.Copy(buffer2, 0, sendBuff, buffer.Length, buffer2.Length);
Sendbuffer를 외부에서 하는 이유
01 내부에서 처리된 경우
데이터를 보내기 전에 각 유저의 데이터를 내부적으로 버퍼에 복사하고 해당 버퍼를 통해 네트워크로 데이터를 전송한다.
100명의 유저가 있다고 가정해보자
100명이 동시에 움직였을때 이 움직임에 대한 각 유저의 데이터를 버퍼에 복사하고 네트워크로 전송하다. 그렇게 되면 100명의 데이터가 복사되고 100번의 데이터 전송 작업이 필요하다. 이는 많은 복사작업과 데이터 전송이 발생하게 된다.
02 외부에서 처리된 경우
각 유저의 데이터를 외부에서 직접 버퍼에 입력하고 이 버퍼를 통해 네트워크로 데이터를 전송한다.
100명의 유저가 있다고 가정해보자
각 유저의 데이터를 외부에서 직접 버퍼에 쓰기 때문에 복사 작업이 필요하지 않다. 그리고 100명의 유저가 동시에 움직였을 때 외부에서 각 유저의 데이터를 직접쓰면 되므로 100번의 데이터 쓰기 작업만 필요하다.
이로 인해 복사작업이 없어지므로 불필요한 데이터 전송이 줄어든다.
버퍼사이즈는 어떻게 해야할까?
위에 예시에서는 8byte가 이상적이다. 하지만 가변적인 데이터(string,List)같은 경우 사이즈 예측이 어렵다.
class Pig
{
public int weight;
public int eat;
public string name;
public List<string> foodList = new List<string>();
}
해결 방법
처음에 큰 버퍼를 할당하고 데이터를 조금씩 사용하여 이용하면 된다.
01 변수 설정
byte[] _buffer;
int _usedSize = 0;
02 현재 남은 버퍼의 크기 반환
public int FreeSize { get { return _buffer.Length - _usedSize; } }
03 버퍼의 남은 크기를 체크하고 가능한 경우 범위를 반환 받는다.
public ArraySegment<byte> Open(int reserveSize)
{
if (reserveSize > FreeSize)
return null;
return new ArraySegment<byte>(_buffer, _usedSize, reserveSize);
}
ArraySegment 참고
public ArraySegment<byte>(byte[] array, int offset, int count);
04 실제로 데이터를 버퍼에 쓴 뒤 해당 데이터 실제 범위 반환
public ArraySegment<byte> Close(int usedSize)
{
// 실제 범위 확인
ArraySegment<byte> segment = new ArraySegment<byte>(_buffer, _usedSize, usedSize);
// 데이터가 실제로 버퍼에 쓰였으므로 ㅎㄴ재 사용된 버퍼의 크기 변경
_usedSize += usedSize;
return segment;
}
recv와 달리 앞으로 되돌아가서 재사용하지 않는다.
05 SendBuffer 클래스의 객체에서 _buffer사용하도록 설정
public SendBuffer(int chunckSize)
{
_buffer = new byte[chunckSize];
}
SendBufferHandler
ThreadLocal을 사용하여 각 쓰레드마다 독립적인 SendBuffer관리하기
01 각 쓰레드마다 독립적인 SendBuffer를 저장
각 쓰레드에서만 접근 가능하다.
public static ThreadLocal<SendBuffer> CurrentBuffer = new ThreadLocal<SendBuffer>(() => { return null; });
02 처음 큰 사이즈 버퍼
public static int ChunckSize { get; set; } = 4096 * 100;
03 버퍼크기 확보하기
public static ArraySegment<byte> Open(int reserveSize)
{
// 현재쓰레드가 처음으로 버퍼를 사용하는 경우 새로운 SendBuffer생성
if (CurrentBuffer.Value == null)
CurrentBuffer.Value = new SendBuffer(ChunckSize);
// 남은 버퍼크기가 데이터의 크기보다 작을 경우 새로운 SendBuffer생성
if (CurrentBuffer.Value.FreeSize < reserveSize)
CurrentBuffer.Value = new SendBuffer(ChunckSize);
// 데이터를 버퍼에 쓸 준비 및 reserveSize 전달
return CurrentBuffer.Value.Open(reserveSize);
}
04 데이터를 쓴 것으로 처리하면서 실제 데이터의 크기를 전송
public static ArraySegment<byte> Close(int usedSize)
{
return CurrentBuffer.Value.Close(usedSize);
}
원래 코드
byte[] sendBuff = new byte[4096];
byte[] buffer = BitConverter.GetBytes(pig.weight);
byte[] buffer2 = BitConverter.GetBytes(pig.eat);
Array.Copy(buffer, 0, sendBuff,0,buffer.Length);
Array.Copy(buffer2, 0, sendBuff, buffer.Length, buffer2.Length);
수정된 코드
ArraySegment<byte> openSegment = SendBufferHandler.Open(4096);
byte[] buffer = BitConverter.GetBytes(pig.weight);
byte[] buffer2 = BitConverter.GetBytes(pig.eat);
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset+ buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHandler.Close(buffer.Length + buffer2.Length);
참고 : 본 내용은 MMORPG PART4 강의를 수강하여 작성하였습니다.
https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4
'cs공부 > 네트워크프로그래밍' 카테고리의 다른 글
네트워크프로그래밍 - 패킷 자동화 처리(TryWriteBytes,c#에서 포인터 사용하는 법) (0) | 2023.08.02 |
---|---|
네트워크 프로그래밍 - PacketSession (0) | 2023.07.31 |
네트워크프로그래밍 - RecvBuffer 개선하기 (0) | 2023.07.19 |
네트워크프로그래밍 - TCP vs UDP (0) | 2023.07.19 |
네트워크프로그래밍 - Session이란 (0) | 2023.07.15 |
댓글