로그인을 한다고 가정을 하고 어떤 스텝으로 패킷 전송이 이루어지는지 생각해보자
클라이언트가 일반적으로 서버에 접속을 할 때 일반적인 MMORPG를 생각을 하면은
일단은 서버에 접속을 하면 아이디와 비밀번호를 입력할 수 있는 창이 나오고
로그인 창에서 인증을 성공해서 통과를 하면 로비로 넘어오게 된다.
로비에서 캐릭터를 선택해서 들어오는 스텝
일단 당장 위 부분을 구현하기에 애매한 부분은 로그인을 하는 웹 서버를 만드는 경우가 많다.
왜냐면 게임서버처럼 실시간으로 데이터를 들고 있어야 할 것이 아니라 그때그때 요청을 오면
DB에 해당 정보가 있는지 확인하고 통보를 시켜주건 거부를 하건 하면 된다.
따라서 지금 당장 로그인 서버를 만드는 것보다 해당 부분은 뒤로 미루도록 하자
Account 정보를 클라에서 서버로 넘겨준다고 생각을 해보자
어떤 식으로?
일단 Unique할 것이다.
인증서버를 서버를 통해서 인증을 받아서 토큰을 전달해주는 방법 등
일단 테스트 하기 쉽게 클라에서 남들과 겹치지 않는 데이터를 전달해준다고 가정을 해보자
실제로 유니티에서도 겹치지 않는 키가 있다고 가정하고 시작
처음에 로그인을 할 때 서버에서 어떤 식으로 작업이 진행이 되는지 살펴보자
현재는 ClientSession.OnConnected에서 방에도 넣어주는 작업이 진행되고 있지만
Login을 Connected가 되자마자 바로 하는 것이 아니라 해당 클라이언트가 진짜 권한이 있는지
캐릭터가 있는지 등을 체크를 하고 통과를 해줘야 한다.
패킷 설계
클라이언트가 서버에 붙으면 “너 지금 서버에 붙었어” 패킷을 서버가 클라에게 보내주게 된다.
그러면 클라에서 “나 유니크 아이디로 로그인을 하고 싶어” 라는 정보를 다시 서버쪽에 보내주게 된다.
그럼 서버쪽에서 확인 후 로그인을 할 수 있는지 여부를 체크하는 일련의 방식으로 진행을 하게 된다.
참고로 proto 파일이 너무 커지게 되면 분리를 하는 것도 좋은 방법이다.
enum MsgId {
S_ENTER_GAME = 0;
S_LEAVE_GAME = 1;
S_SPAWN = 2;
S_DESPAWN = 3;
C_MOVE = 4;
S_MOVE = 5;
C_SKILL = 6;
S_SKILL = 7;
S_CHANGE_HP = 8;
S_DIE = 9;
S_CONNECTED = 10;
C_LOGIN = 11;
S_LOGIN = 12;
}
message S_Connected {
}
message C_Login {
string uniqueId = 1;
}
message S_Login {
int32 loginOk = 1;
}
실습 코드
-
클라가 서버에 접속한 후 Connect Packet 발송 (Server: S_Connected ⇒ Client)
public override void OnConnected(EndPoint endPoint) { Console.WriteLine($"OnConnected : {endPoint}"); { S_Connected connectedPacket = new S_Connected(); Send(connectedPacket); } // TODO : 로비에서 캐릭터 선택 MyPlayer = ObjectManager.Instance.Add<Player>(); { MyPlayer.Info.Name = $"Player_{MyPlayer.Info.ObjectId}"; MyPlayer.Info.PosInfo.State = CreatureState.Idle; MyPlayer.Info.PosInfo.MoveDir = MoveDir.Down; MyPlayer.Info.PosInfo.PosX = 0; MyPlayer.Info.PosInfo.PosY = 0; StatInfo stat = null; DataManager.StatDict.TryGetValue(1, out stat); // protobuf에서 제공해주는 기능 => 두 데이터를 합쳐준다. MyPlayer.Stat.MergeFrom(stat); MyPlayer.Session = this; } // TODO : 입장 요청 들어오면 GameRoom room = RoomManager.Instance.Find(1); room.Push(room.EnterGame, MyPlayer); }
-
S_Connected를 수신 후 LoginPacket 발송 (Client: C_Login ⇒ Serer)
public static void S_ConnectedHandler(PacketSession session, IMessage packet) { Debug.Log("S_ConnectedHandler"); C_Login loginPacket = new C_Login(); // 이렇게 하면 로컬 컴퓨터에서 여러 클라로 접속을 할 때 문제가 될 수가 있다. loginPacket.UniqueId = SystemInfo.deviceUniqueIdentifier; Managers.Network.Send(loginPacket); }
-
C_Login Packet을 수신 후 DB에 Login 정보 저장 및 LoginOk 패킷 발송 (Server S_Login: Client)
public static void C_LoginHandler(PacketSession session, IMessage packet) { C_Login loginPacket = packet as C_Login; ClientSession clientSession = session as ClientSession; System.Console.WriteLine($"UniquedId({loginPacket.UniqueId}"); // ToDo : 이런 저런 보안 체크 // ToDo : 문제가 있긴 하다 using (AppDbContext db = new AppDbContext()) { AccountDb findAccount = db.Accounts .Where(a => a.AccountName == loginPacket.UniqueId).FirstOrDefault(); if (findAccount != null) { S_Login loginOk = new S_Login() { LoginOk = 1 }; clientSession.Send(loginOk); } else { AccountDb newAccount = new AccountDb() { AccountName = loginPacket.UniqueId }; db.Accounts.Add(newAccount); db.SaveChanges(); S_Login loginOk = new S_Login() { LoginOk = 1 }; clientSession.Send(loginOk); } } }
-
S_Login 패킷을 수신 후 Log 찍기