Serialization #1

CJB_ny·2022년 2월 23일
0

Unity_Server

목록 보기
34/55
post-thumbnail

지난 시간까지

클라 <- 패킷 -> 서버

이렇게 통신을 해보았다

왼쪽 클라 Pro

오른쪽 Server Pro

이렇게 Packet이라는 것을 만들어서 흘려보내고 있었는데

1. 직렬화 Serialization

"직렬화"라는 용어를 사용하게 된다 이제부터.

꼭 "네트워크"에만 종속 적인 단어는 아니다.

우리가 Packet을 어떻게 보냈는지 보면은

여기서 패킷을 만들어서

Array.Copy를 통해 openSegment에 밀어넣고 최종적으로 이것을 Send(sendBuff);를

하고있었다.

그래서 어떤 패킷이든 어떤 식으로든 패킷어 밀어 넣어서 통신을 해야하는 상황이 온다.

그래서 이런식으로 메모리 상에 Instance로 존재하는 애를 "납작하게" 만들어가지고

"Buffer"안에 밀어 넣는 작업이 "직렬화"라고 생각하면된다.


그리고 역으로 바이트 배열에 있는 녀석을 꺼내쓰는 것도 하고있었는데

바로

이부분 이였는데

이부분은 "역직렬화" 라고 한다. DeSerialization이라고함.

아무튼 파일정리 등등 여러곳에서 직렬화라는 개념을 쓰니까

ㅈㄴ 중요하다!!! 공부해라


2. 파일 정리

우리가 지금 Dummy의 GameSession이라는 녀석을 굉장히 자주 사용할거라

Dummy, Server둘다 파일을 정리를 하자.

이렇게 만드는데

"대리자"를 Session이라고 했었는데

클라의 대리자는 Server에 가있는 것이고

Server의 "대리자"는 클라에 가있는 식이다.

그래서 Dummy

이 위에부분 다 짤라서 ServerSession으로 보내자.

더미쪽은 이렇게

서버쪽은 이렇게 해주자.

그리고 Session이 나중에 여러개가 될 수 있기 때문에 이름을 정확하게 지어주자.


먼저 샘플 패킷을 만들어서 어떤식으로 보낼지 생각을 하면서 코드를 만들고

그 만든 부분을 나중에 자동화를 해주자.

일단은 패킷을 샘플로 만들어 보도록 하자.

이렇게 두개를 만드는데

PlayerInfoOk는 서버 -> 클라 로 답변 주는 것.

PlayerInfoReq는 클라 -> 서버 로 플레이어 정보 알고싶다고 요청하는것.

그 요청한 정보는 playerId라고 요청을 한것.

나중에 가면은 패킷마다 아이디가 있을 것이다.

이 두녀석은 Packet을 상속 받고있으니까 당연히 size, packetId는 들어 갈것이다.

Packet이 Header이고,

실제 패킷의 고유정보는 뒤에다가 붙여주는 식이다.

근데 Packet의 packetId는 실제로 Packet들을 구분 할 수 있어야되니까

이렇게 enum으로 만들어주자.


그런데 이 정보들은 클라에서만 가지고 있으면 안되고 서버에서도 똑같이 가지고 있어야

나중에 파싱이 가능하다.

이렇게 하자.


일단 보내는 것부터 해보도록 하자.

그런데 나중에 "실제" 패킷을 보냈을 때, 어떻게 바뀔지 생각을 하면서 작업을 하는 것이다.

그러면

PlayerInfoReq라는 패킷을 보내고싶다라고 가정을 하면은

서버에 붙자마자 OnConnected가 호출이 될텐데

이때바로 player의 정보를 줘! 라고 PlayerInfoReq라는 패킷을 서버에 요청을 하는 것이다.

그리고 packetId

이렇게 바꿔주자.


그 다음에 이어서

PlayerInfoReq를 보내는 작업을 해보도록 하자.

Open(4096)로 openSegment를 만들어 주는 것까지는 동잏하고

BitConverter.GetBytes 를 통헤 packet.size, packet.packeId를 받아주고 있었는데

이부분이 좀 마음에 안든다.

(나중에 수정을 하도록 하고)

playerId를 하나 더 추가를 해주자.


아! 그리고 그전에 playerId

이렇게 넣어 주자!


지금 밑줄친 부분이 중요한데,

나중에 이녀석을 자동화를 해줄때 어떤 부분이 들어가야할지 생각을 해보자.


(지금 PlayerInfoReq라고 서버에다가 플레이어 아이디를 요청한 상황이다.

그러면 서버에서는 이 패킷정보가 필요하니까, packet.size, packet.packetId, packet.playerId

이런식으로 요청을 하는 상황임)


생각을 해보자. ->

지금 Copy안에다가 작업한 부분만큼 Offset을 밀어주고 있었다.

openSegment.Offset -> openSegment.Offset + size.Length 이런식으로

그러면 playerId도 들어가면 또다시 하드코딩으로 밀어 줘야함

openSegment.Offset -> openSegment.Offset + size.Length -> openSegment.Offset + size.Length + playerId.Length 이런식이 될것.

즉, 지금까지 우리가 몇바이트를 썻는지 "계속" 추적을 해야될 것.

그래서


이런식으로 될텐데

이런식으로 계속 숫자를 넣어주기가 힘드니까

count라는 변수를 두어서 몇바이트씩 넣어줬는지 추적을 하도록 하자.

이런식으로 추적을 하면 된다는 말이다. ( openSegment -> s )


이렇게 추적을 하면 될 것이고

이제, 받는 쪽에서는 이것을 어떻게 받으면 될지 수정을 하도록 하자.

clientSession에서

buffer에는 완성된 크기의 패킷이 들어와 있을 것이다.

그래서

이렇게 똑같이 count로 추적을 하고

id를 받아왔으니 id에 따라 어떤 패킷인지 구분하기 위해

이렇게 switch로 구분을 해주자

(나중에는 패킷이 하나가 아니니까 case, case, case . . . 이렇게 늘어 날 것이다)

offset은 buffer의 4부터 니까 이렇게 Parsing 하고

파싱하자마자

이렇게하면 아다리가 맞을 것이다.

( 나중에 또 파싱을 해야할 경우를 대비를 해서 count 늘림 )


우리가 지금 하고있는 것은

자동화 하기에 앞서서 어떤 식으로 보내고 받아줄 지를 생각하는 중이다.


테스트를 해보면

playerInfoReq가 잘 들어온것을 볼 수 있다.

3. 개선할 부분 찾기

보내기

직렬화하고 비직렬화 하는 과정은 앞으로도 정말정말 많이 사용할 것이기 때문에

신경을 써서 최적화 할 수 있는 부분은 하고 가자.

받는 부분은 이런식으로 뽑아 오는 것은 문제가 별로 없는데

보내는 부분! 에서

클라 -> 서버 보낼때, 이게 굉장히 마음에 안든다.

Bitconverter로 ushort타입을 받아서 바이트 배열로 받아주고 있는데

즉, 바이트 배열로 byte[] b = new byte[]; 이런식으로 중간중간에 만들었다는 얘기이다.

이부분이 굉장히 찝찝하다.

애초에 우리가 열린 버퍼를

이렇게 만들었었는데

여기다가 막바로 집어 넣으면 좋을거 같다라는 생각이 든다.

(긍까 애초에 보낼려고만든 ㅈㄴ 큰 버퍼에다가 넣지, 왜 굳이 중간중간에

byte[] b = new byte[];

이렇게 만들어서 이것을 다시 s에다가 넣냐 이말이다. )

그래서 효율성 떨어짐.

해결하기 위한 방법, 버젼은 굉장히 많다.


그래서 TryWritebytes라는게 있는데

Span 목적지, 값을 넣어주는데

s.Array, s.Offset, s.Count 스판 바이트 배열에다가 , 추출 해야할것을 넣어 줘야함

그게 packet.size이다.

이녀석이 성공을 했냐 실패를 했냐는 bool로 받아 주면된다.

and연산으로 받아줘서 false가 나오면 success도 false가 될 테니까 이렇게 해주자.


그래서 TryWriteBytes가 실패를 할 수 있는 경우를 생각해보면

이부분에서 s.Count는 2바이트 짜리였는데

뒤에 넣어주는 packet.size가 4바이트 or 8바이트 이런 식이여가지고

공간이 모자르게 되면 실패를 할 것이다.

그래서

이렇게 count를 이부분에 늘려줄 것이다.

그리고 두번째 TryWrtiteBytes 부터 Span바이트 배열안의 내용을 바꿔 주어야 한다.

이렇게 10바이트짜리 배열이 있다고 하면은

처음에는 시작 위치를 넣어줌 ( == s.Offset ) 그다음 10바이트 짜리라는 것을 넣어 줬는데

이제는 2바이트 만큼 넣어 주었으면

커서가

이렇게 앞으로 와서 이런식이 될 것이다.

유효범위가 이렇게 되고 Buffer의 크기도 2만큼 줄어들어야 한다.

그래서 두번째 부분은 s.Offset + count를 해줘야 하고

s.Count - count를 해주어야 맞는 말이 될 것이다.

잠깐 수정하자면 이런식으로 하면 될 것이다

밑에 부분 필요 없으니 삭제 ㄱㄱ


그래서

BitConverter.GetBytes를 이용하기 보다는

TryWriteBytes를 이용해서 한방에 넣어주도록 하자.

그래서

이렇게 보내주면 똑같이 받을 수 있는지 보면되는데

그전에 지금 좀 이상한게 지금

packet의 전체 사이즈는

이런식으로 다 s에다가 밀어주고 (12바이트) 나서 count가

보낼 패킷의 사이즈가 될텐데

지금

여기서 미리 정해주고 있기 때문에

(나중에 추가적으로 패킷의 정보(크기)가 달라질 수 있기때문에 )

나중에 보낼 사이즈를 정해주는 것이 맞을 것이다.

그래서

여기서 packe.size를 못하고

맨 마지막에 s의 맨처음 Offset에다가 넣어 줘야 할 것이다.


ushort 타입으로 넣어주고 있으니 잘 봐야한다

혹시 라도 int형으로 넣어주게 되면 자동으로 다른 버젼 사용하게되서 뻑날것이다.


우리가 엄청 대단한것을 한건 아니지만

나중에 자동화를 해줄때를 생각을해서

count와 같은 변수를 넣고 어떤식으로 코드를 짜줄지를 생각을 한것이다.


여담으로

패킷을

이런식으로 클래스로 관리를 가끔하기도하고

일반적인 경우에는 Json파일, XML파일에다가 패킷을 정의하는 파일을 만들어서

자동화를 처리해주는 경우가 많다.

profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글