인프런 커뮤니티 질문&답변

BBAKK님의 프로필 이미지
BBAKK

작성한 질문수

[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버

RecvBuffer와 SendBuffer 클래스 관련 질문드립니다.

작성

·

376

0

Open에서 공간이 부족하면 새로운 SendBuffer(ChunkSize)를 할당하여

ThreadLocal<SendBuffer> CurrentBuffer에 참조를 전달해주는 부분에서 궁금한 것이 있습니다.

1.

RecvBuffer 클래스에서는 buffer가 ArraySegment<byte> 라는 구조체이므로 스택에 할당되며

Clean() 매소드에서 처리된 패킷은 밀어버리고, 처리되지 않은 패킷만 시작위치로 복사하여 버퍼를 재사용한다고 기억하고있습니다.

이 때 복사과정에서 Array.Copy(_buffer.Array, _buffer.Offset + _readPos, _buffer.Array, _buffer.Offset, dataSize); 매소드를 콜할 때 전달되는 매개변수들( 특히 buffer.Array 배열)이 스택프레임에 각각 복사되어 올라가는 것이 맞나요?

2.

SendBuffer는 RecvBuffer와 다르게 buffer를 byte[] 로 힙에 할당하는 것으로 되어있는데요,

그 이유가 강의에서는 각 스레드 별로 Send가 완료 되었는지 알 수 없기 때문에 buffer의 앞부분를 밀어버리게되면 sendQueue에 전달된 내용이 날아가버리기 때문에 곤란하다고 하셨습니다.

이 부분을 RecvBuffer의 buffer처럼 구현하는 방법은 없는건가요?

있다면 어떤식으로 하는 것인지 궁금합니다.

3. 

SendBufferHelper 클래스에서 ThreadLocal로SendBuffer를 만드는 부분에 대해 궁금합니다.

검색해보니 ThreadLocal로 만들면 스레드 별로 독립된 저장공간으로 생성되어 정적멤버변수나 싱글톤에 사용하는 것으로 이해를 했습니다.

이 독립된 공간이 생성되는 시점과 어떻게 생성되는지 궁금합니다.

public static ThreadLocal<SendBuffer> CurrentBuffer = new ThreadLocal<SendBuffer>(() => { return null; }); 

여기서 valueFactory에 null 반환하는 것으로 구현한 이유가 궁금합니다.

추가로 4가지 생성자가 있던데 각각 어떤것인지 궁금합니다.

답변 3

1

Rookiss님의 프로필 이미지
Rookiss
지식공유자

일부분을 따로 복사하는건 아니고 그냥 덮어써서 원본을 훼손하는게 맞습니다.
SendBuffer와 다르게, RecvBuffer는 Session마다 하나씩 고유하게 갖고 있습니다.
그러니 Recv 완료 통지가 온 다음에는
이전에 사용하던 영역은 이제 100% 더 이상 활용하지 않을테니
원본을 쿨하게 훼손시켜도 무방합니다 (어차피 이미 처리한 영역이기 때문)
(SendBuffer의 경우 다수의 Session에 Send를 걸어놓은 상태라 모두 다 받았다고 보장 못함)



이해를 돕기 위해 그림 첨부합니다.
말 그대로 [남은 데이터]를 버퍼 처음 위치로 옮기는 것에 불과합니다.

0

BBAKK님의 프로필 이미지
BBAKK
질문자

1.

함수 호출에서 인자들은 스택 프레임이나 레지스터를 통해 전달됩니다만,
여기서 어떤 부분을 질문하시는지 잘 모르겠습니다.
buffer.Array가 통째로 복사되는건 아니고 참조값(사실 어셈블리로 까보면 그냥 주소)이
스택 프레임이나 레지스터를 통해 내부적으로 전달되어 최종적인 데이터 Copy가 일어납니다.

\

Copy해서 덮어씌운다는 것 자체가 원본을 훼손시키는거니까

복사해서 붙여넣을 부분 ( buffer.Array 의 일부분)을 따로 복사한뒤

buffer.Array에 덮어 띄우는 것인지 궁금했습니다.

복사해서 덮어씌운다는 개념자체가 이런게 아닐가 추측해봤습니다.

만약 그렇다면 이때 복사해둔 buffer.Array 일부분이 스택프레임이나 레지스터를 통해 전달된다는 것으로 이해를 해도되나요?

0

Rookiss님의 프로필 이미지
Rookiss
지식공유자

1. C#에서 스택과 힙은 신경 쓸 문제가 아니고, 복사냐 참조냐만 신경쓰면 됩니다.

이 때 복사과정에서 Array.Copy(_buffer.Array, _buffer.Offset + _readPos, _buffer.Array, _buffer.Offset, dataSize); 매소드를 콜할 때 전달되는 매개변수들( 특히 buffer.Array 배열)이 스택프레임에 각각 복사되어 올라가는 것이 맞나요?

함수 호출에서 인자들은 스택 프레임이나 레지스터를 통해 전달됩니다만,
여기서 어떤 부분을 질문하시는지 잘 모르겠습니다.
buffer.Array가 통째로 복사되는건 아니고 참조값(사실 어셈블리로 까보면 그냥 주소)이
스택 프레임이나 레지스터를 통해 내부적으로 전달되어 최종적인 데이터 Copy가 일어납니다.

2. 과거에 보낸 패킷이 완전히 전송 되었는지 알 수가 없기 때문에
RecvBuffer처럼 구현하기는 어렵습니다.
정말 꼭 그렇게 싶다면 SendBuffer를 외부에 두는게 아니라
Session마다 SendBuffer를 하나씩 두고 개별적으로 관리할 수 있습니다.
다만 이렇게 할 경우 Broadcasting할 때
[공용 SendBuffer를 한 번 채워주고 모든 Session한테 꽂아주는] 형태가 아니라,
[모든 Session들을 하나씩 순회하면서 각자의 SendBuffer에 복사]하는 식으로 구현해야 하는데
이 복사 비용을 무시할 수 없습니다!

3. TLS를 떼고 보면
그냥 static SendBuffer CurrentBuffer = null을 한 것이고
나중에 알맞는 사이즈를 만들어주기 위해 그냥 null값을 초기에 넣어준거지 큰 의미는 없습니다.
생성자 인자는 초기 값을 만들 Factory를 지정하거나,
다른 쓰레드 접근 가능 여부를 설정하거나 할 수 있습니다.
그냥 = null을 하지 않고 Func로 만든건 Lazy Initialization이라고
초기화 함수가 처음부터 실행되는게 아니라
ThreadLocal 변수를 처음으로 사용할 때
그 객체가 만들어지기 때문입니다.
참고로 TLS와 SendBuffer는 많은 분들에게 혼동의 여지를 드린 것 같아
Part 7에서 제거하고 Protobuf를 사용하는 방식으로 대체되니 참고 바랍니다.

https://docs.microsoft.com/en-us/dotnet/api/system.threading.threadlocal-1?view=netcore-3.1

BBAKK님의 프로필 이미지
BBAKK

작성한 질문수

질문하기