네트워크 프로그래밍
지난 시간에는 ReceiveBuff를 만들어서 Read, Write Pos(=Segment)를 생성하였다. 이러한 과정에서 byte를 받아오고, 또 받아온 byte를 처리하는 로직을 작성하였다. 또한 중간중간 R/W Pos를 초기화해주는 Clear 함수, 작업이 완료 된 이후 OnRead, OnWrite 처리를 하는 메서드 또한 만들었다.
기존 Send의 문제점
기존 Send는 Encoding을 진행한 후, byte 타입으로 전환하여 이를 넘겨주고, 넘겨준 Send Queue에 저장하여 처리하는 방식으로 진행하였다. 이번 시간에는 기존에 Encoding으로 처리하던 방법을 이번에는 실질적인 패킷 데이터로 가정하고 넘겨 받기 위해 Send Buffer를 만들어 줄 것이다. 😎
Receive와 Send의 차이점
하지만 Receive와 Send의 가장 큰 차이점은 고유 버퍼의 여부이다. Receive 메서드는 Session 마다 자기 고유의 리시브 버퍼를 가지고 있다. 생각해보자. 유저마다 서버에 요청하는 정보는 모두 다르다. 그렇기 때문에 Receive 메서드는 세션마다 자기 고유의 리시브 버퍼를 가지고 있다. 즉 1:1 관계를 가지기 때문에 함수 내부에서 세션을 가지며 구현을 할 수 있는 것이다.
그렇지만 Send는 그럴 수 없다. 함수 내부에 Session을 만들어 적용하여 처리하게 될 경우 곧바로 성능 이슈 문제에 바로 즉면하게 된다. 예를 들어 게임에 100명의 유저가 존재한다고 가정해보자. 이 중 1명의 유저가 움직이면 나머지에게 99명의 유저에게 1명의 유저가 이동했다는 이동 패킷을 전송해야 한다. 이러한 처리를 Receive와 같이 하나씩 생성하여 처리한다고 하면 100명이 동시에 움직일 경우 100 * 100으로 거의 10,000번 가까이 새로 생성해야 한다.
즉 성능 이슈 문제는 어찌 보면 당연한 처사일 수 밖에 없다. 따라서 Send는 함수 내부에서 세션을 만들어 처리하지 않고, 외부에서 선언해 이를 생성하고 사용한다. 그 이후 생성한 갯수만큼 반복문을 통해 넘겨주기만 하면 되기 때문에 성능적으로 크게 이득을 볼 수 있다.
SendBuffer 제작 🐱💻
Buffer Size 설정
패킷을 보내줄 때마다 각자의 패킷이 몇 바이트를 사용할 것인지 결정해서 보내주면 좋겠지만, 그럴 수 없다. string 같은 변수는 글자의 갯수, 한/영에 따라 크기가 가변적이기 때문에 몇 바이트를 사용할 것인지 고정하기 어렵기 때문이다. 그렇기 때문에 미리 큰 값을 만들어 선언해두고, 그 중 일부를 ArraySegment를 이용해 사용하는 방식으로 구현할 예정이다.
즉. 가게에서 소세지를 구워 판매할 것인데 가게에 손님이 얼마나 올지 모르므로 엄~청나게 큰 소세지를 미리 사서 손님이 이 올때마다 인원수에 맞춰 제공하는 것과 같은 방식이다. 😁
먼저 서버코어에 SendBuffer.cs 스크립트를 새로 만들어준다.
이 후, buffer로 활용해줄 byte 배열과 recvBuffer에서 WritePos와 같이 현재 사용한 영역을 표기한 usedSize, 그리고 남은 공간의 크기를 나타내는 FreeSize를 선언해준다.
Open
사용하고자 하는 공간 만큼의 버퍼를 요청하고, 이것이 가능하면 뱉어주고 그렇지 않을 경우 넘겨주지 않는 구조로 구성한다.
Close
Open 이후 사용한 사이즈만큼 받아와서, 지정해준 뒤 다시 뱉어주어 정확한 사용량을 체크하여 _usedSize에 대입하여 준다.
Receive와 Send의 또 다른 차이점
SendBuffer는 RecvBuffer와 달리 Clean의 개념을 가진 함수가 존재하지 않는다. send를 여러명에게 보내기에 다른 누군가가 sendQueue에 담아두고 참조하고 있을 수 있기 때문에 재사용 구조를 만들기 어렵다. 명심하자. 우리는 지금 멀티스레딩 환경을 구축하고 있다.
SendBufferHelper 제작
SendBuffer의 내부 함수는 건들지 않고 사용하기 위해 SendBufferHelper라는 새로운 클래스를 만들어주고, 이 때 멀티 스레딩 환경에서 전역변수처럼 사용할 수 있지만, 나만의 스레드에서만 고유하게 사용할 수 있는 ThreadLocal을 사용해 만들어준다.
이 때, ChunkSize라는 큰 덩어리를 생성하여 다 사용할 때 까지 쭉 사용할 공간을 만들어준다.
Helper의 Open
이 부분에서도 Open, Close를 활용할 수 있도록 제작하고, Open 시에는 TLS의 값 체크를 이용하여 아직 한 번도 사용하지 않았을 때 새로운 공간을 만들어준다. 이 후, 남은 공간이 새로 요청한 공간보다 작을 경우, 즉. 여유 공간이 없을 경우 새로운 Buffer를 만들어서 전달해준다. 물론 두 조건에 걸리지 않고 버퍼가 여유 공간이 있을 경우 TLS Value의 Open을 해주어 전에 만든 곳에 사이즈를 넘겨준다.
Helper의 Close
버퍼를 사용한 후에는 크게 제약사항이 없기 때문에 바로 값을 넘겨준다.
Session의 Send
이제 아래와 같이 직접 SendBuff를 만들어 넘겨주는 영역을 수정할 것이다.
예시를 위해 임시로 Knight 클래스를 생성해주고 hp, attack 등을 추가하여 값을 넣어준다. SendBufferHelper.Open(4096)을 사용하여 Chunk 사이즈의 ArraySegment를 생성하고, BitConverter.GetBytes()를 사용하여 hp, attack에 담아준 100이라는 값과 10이라는 값을 byte[] 타입으로 변환하여 넣어준다.
이 후, Array.Copy()를 이용해 큰 공간을 만들어둔 openSegment에 복사하여 넣어준다. 첫 번째 Copy는 offset 값을 넘겨주고 이 후 두 번째 Copy에서는 Offset의 buffer1의 Length 만큼 넣어 그 다음 공간으로 들어가게 작업을 처리해준다.
그 뒤 사용한 만큼 buffer1 ,2의 Length만큼 알려준 뒤 Close 해주고, 해당 배열을 Send 해주면 진짜 실질적인 버퍼의 크기만큼 전송이 완료되는 것을 볼 수 있다.
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 4-4-1. 패킷 직렬화 : Serialization #1 (1) | 2023.11.30 |
---|---|
Part 4-3-13. 네트워크 프로그래밍 : PacketSession (0) | 2023.11.08 |
Part 4-3-11. 네트워크 프로그래밍 : RecvBuffer (1) | 2023.11.02 |
Part 4-3-10. 네트워크 프로그래밍 : TCP/UDP (0) | 2023.10.31 |
Part 4-3-9. 네트워크 프로그래밍 : Connector (1) | 2023.10.30 |