Folders and files Name Name Last commit message
Last commit date
parent directory
View all files
설명
서버 기능
아키텍처
주요 기능 구현
피드백
트러블 슈팅
Game Socket 서버입니다.
오목 게임에 관한 로직을 담당하고 있습니다.
멀티 스레드로 돌아가고 있으며, 스레드의 용도는 다음과 같습니다.
로직 스레드 : 싱글 스레드로써, 클라이언트의 패킷을 직접적으로 처리하는 스레드입니다.
데이터베이스 스레드 : 멀티 스레드로써, 데이터베이스의 처리를 비동기적으로 작업하기 위한 스레드입니다.
레디스 스레드 : 멀티 스레드로써, 레디스의 처리를 비동기적으로 작업하기 위한 스레드입니다.
매칭 스레드 : 싱글 스레드로써, 매칭 API 서버와 레디스로 통신을 담당하는 스레드입니다.
객체간의 연동을 최소화하기 위해 싱글톤을 전혀 사용하지 않고 델리게이트 형식으로 넘기는 방식으로 구현되어 있습니다.
MemoryPack 라이브러리를 통해서 데이터를 직렬화하거나 역직렬화합니다.
종류
라이브러리
Network
SuperSocketLite
Binary
MemoryPack
Database
MySqlConnector, SqlKata
Redis
CloudStructures
Logger
NLog
기능
완료 여부
유저 로그인
완료
유저 로그 아웃
기능
완료 여부
세션 생성
완료
세션 삭제
완료
방 입장
완료
방 퇴장
완료
방 채팅
완료
게임 시작
완료
돌 두기
완료
오목 로직 기능
완료
게임 종료
완료
세션 로그인 체크
완료
하트 비트
완료
룸 체크
완료
기능
완료 여부
유저 데이터 가져오기
완료
유저 우승 정보 업데이트
완료
기능
완료 여부
토큰 정보 검증
완료
매칭 기능
완료
설정파일 을 통해 특정 시간마다 연결되어 있는 클라이언트들이 정말 연결되어 있는지 체크하는 기능
타이머마다 접속한 모든 유저에게 하트비트 패킷을 전송함.
응답이 오게 되면 해당 시간을 기록함.
체크하는 과정에서 시간을 비교하여 특정 시간 이상으로 하트비트 응답이 오지 않은 경우 세션 종료 처리함.
한번에 모든 유저를 체크하는 경우 부하가 발생할 수 있기 때문에 특정 카운트만큼 처리하도록 구현함.
System.Threading.Timer를 통해 반복적으로 실행됨.
설정파일 을 통해 특정 시간마다 모든 룸의 상태를 체크하는 기능
시간, 체크 횟수 등을 을 통해 설정할 수 있음.
다음과 같은 것들을 체크함.
매칭되어 있는 방의 경우 시간을 측정하여 설정 시간보다 지났는데도 게임이 시작되지 않았거나 유저가 들어와있지 않으면 룸을 클리어
게임이 시작했는데 유저가 2명보다 작을 시 룸을 클리어
게임이 시작되었는데 특정 시간보다 더 오래 게임을 진행하고 있을 시 룸을 클리어(값이 매우 큼)
오목 게임 체크
턴을 가진 유저가 설정된 시간을 넘어서 두지 않을 경우 턴이 넘어감
특정 카운트만큼 한 유저가 턴을 강제 넘겨지게 되면 게임 종료
한번에 모든 유저를 체크하는 경우 부하가 발생할 수 있기 때문에 특정 카운트만큼 처리하도록 구현함.
룸 체크의 경우 System.Threading.Timer를 통해 반복적으로 실행됨.
각 스레드별로 내부 패킷을 만들어서 전송하는 식으로 하여, 각 스레드 간의 간섭을 최소화함.
헤더 + 데이터 형식으로 패킷을 만들어 주고 받음.
전송받은 패킷은 각 스레드의 핸들러에서 처리하도록 구현.
빈 방이 존재할 경우, 매칭 서버에서 넘어오는 데이터를 받음.
서버에 대한 정보와 빈 방의 Room Number를 Redis를 매칭 서버에게 전달.
룸 정보는 매칭 스레드에서 따로 하나더 들고 있는 형식으로 스레드 동기화.
싱글톤 패턴을 사용하지 않고, Action이나 Func 델리게이트 형식으로 전달받아서 사용해라.
객체 간의 연동은 최대한 느슨하게 하는 것이 좋다.
최대한 심플하게, 유지보수가 쉬운 코드를 작성해라
방어적인 코드를 작성해라
좀비 객체를 조심해라
데이터만 바꿔도 기능이 바뀌도록 구현하는 것이 가장 좋다.
MO 서버의 경우, 하나의 스레드로 만들어도 하나의 컴퓨터에 여러 개의 서버를 동시에 띄우면 된다.
서버에는 기본적으로 Sleep, Wait가 있으면 안된다.
데이터의 양이 적은 경우, Dictionary보다 List가 성능이 더 좋을 수도 있다.
실행 중의 IO 작업은 모두 비동기로 작동해야 한다.
연결이 끊기면 다시 재접속해주는 코드가 필요하다.
내가 실수해도 서버가 잘 돌아갈 수 있도록 방어적인 코드를 추가해야 한다.
매칭의 경우 빈 방이 존재하는지를 체크해야하며, 또한 빈 방이 존재한다면 해당 방의 정보를 전달해주어야 한다.
이 과정에서 로직 스레드 와 매칭 스레드 가 룸 객체에 동시에 접근하는 경우가 발생.
예를 들어 매칭 스레드가 빈 방인지를 체크하는 도중에 로직 스레드에서 방 입장 처리를 한다던가 등
해결 방법은 두 가지 정보 생각
룸 객체에 접근할 때 Lock을 사용한다. -> 바로 접근하기 때문에 속도를 보장할 수 있는 있으나 Lock을 어떻게 사용하느냐에 따라서 달라짐.
InnerPacket으로 만들어 정보를 전달한다. -> 동기화는 보장하나 InnerPacket을 만들어 전달하고 처리하는 과정에서 처리 속도가 느림.
Lock을 사용하는 순간 코드들이 많아지면서 유지보수에 어려움을 느낌.
따라서 룸 정보를 매칭 스레드에서 하나 더 들고 있고, 데이터 동기화를 InnerPacket 형태로 구현.
Redis의 Pub/Sub 이나 List를 통해 매칭 서버와 통신을 해야 하는 상황
매칭 로직은 여러 개의 게임서버와 하나의 매칭 서버가 레디스를 통해 통신을 하며, 매칭 서버에서 매칭 성공 요청이 오면 대기 중인 게임 서버 중에서 제일 빠른 서버가 그 요청을 받아서 처리하는 로직
첫 번째로 Pub/Sub으로 기능을 구현하였으나 모든 Subscribe에 Publish되는 특성 으로 인해 제일 먼저 받아서 처리하는 로직을 구현하는 것에 어려움을 느낌.
Subscribe한 서버들은 각자 자신의 서버 정보와 빈 방의 정보를 레디스를 통해 매칭 서버로 전달, 이 과정에서 해당 정보를 Dictionary로 들고 있음.
매칭 서버에선 가장 먼저 온 요청을 성공으로 처리하고 해당 정보를 다시 서버들에게 전송, 이외에 데이터는 무시
전송했던 서버들을 해당 정보를 받고 자신이 성공했다면 성공 처리, 실패했다면 다시 빈 방으로 만듬.
이런 방식으로 만들다 보니 받을 필요가 없는 서버까지 데이터를 전달받고 처리하는 불필요한 과정이 추가됨.
이러한 내용들을 실장님에게 전달드렸고, List를 추천받아서 테스트를 해봄.
List를 사용해보니 위와 같은 과정없이 Queue처럼 사용할 수 있어서 훨씬 간단하게 처리할 수 있어서 로직을 전면 수정함.
You can’t perform that action at this time.