Deep Studying

[프라우드넷] 채팅 서버 만들기(2) RMI 통신하기 본문

게임서버

[프라우드넷] 채팅 서버 만들기(2) RMI 통신하기

miniSeop 2022. 1. 11. 17:49

 

이번 포스트부터는 본격적으로 서버 프로그램을 만들어보도록 하겠습니다.

기본적인 RMI통신 내용을 설명하기 위해 아주 간단한 서버와 클라이언트를 만드는 것으로 시작하겠습니다.

 

프로젝트 초기 설정이 안되있다면 이전 포스트를 읽고 다시 봐주시기 바랍니다.

이전 포스트: 채팅 서버 만들기(1) 프로젝트 초기 설정하기

 

 

 

요구사항

- 메세지는 시스템 메세지채팅 메세지 두 종류이다.

- 클라이언트가 접속하면 모든 클라이언트에게 시스템 메세지를 보낸다.

- 클라이언트가 접속을 종료하면 모든 클라이언트에게 시스템 메세지를 보낸다.

- 클라이언트가 채팅을 입력하면 모든 클라이언트에게 채팅 메세지를 전달한다.

 

위 세 가지 기능에 필요한 요소들을 살펴보면

 

- 클라이언트가 접속하면 모든 클라이언트에게 시스템 메세지를 보낸다.

- 클라이언트가 접속을 종료하면 모든 클라이언트에게 시스템 메세지를 보낸다.

- 클라이언트가 채팅을 입력하면 모든 클라이언트에게 채팅 메세지를 전달한다.

 

빨간 글씨에 적힌 내용들이 중요 요소들입니다.

 

1. 클라이언트의 접속 처리

2. 클라이언트의 접속 종료 처리

3. 클라이언트에게 시스템 메세지 전송 ( S2C, Server to Client )

4. 서버에게 채팅 메세지 전송 ( C2S, Client to Server )

5. 클라이언트에게 채팅 메세지 전송 ( S2C, Server to Client )

 

목표는 위 다섯 가지 통신 요소를 처리하는 것으로 잡을 수 있습니다.

1번과 2번은 기본적으로 시스템이 처리해주는 기능입니다. 핸들러를 통해 로직을 추가해야합니다.

나머지는 PIDL에 등록하여 직접 stub과 proxy를 사용하는 기능들입니다.

 

PIDL파일 등록

다음과 같이 파일을 작성합니다.

global C2S 2000
{
	Chat([in] string str);
}
global S2C 3000
{
	NotifyChat([in] string str);
	SystemChat([in] string str);
}

우선은 세가지 메소드 모두 string을 주고 받는 것으로 정의하겠습니다.

다 작성하셨다면 PIDL.bat 파일을 실행해줍니다.

 

> PIDL.bat 실행

 

Stub 등록하기

우선 내부 로직을 구현하기 전에 빈 함수로 Stub을 구성해줍니다.

S2C에 작성한 함수의 Stub은 클라이언트에서, C2S에서 작성한 함수의 Stub은 서버에서 처리합니다.

RPC 통신의 특성으로 요청을 받는 쪽에서 Stub을 실행, 로직을 처리한다고 기억하시면 됩니다.

 

Client > Program.cs

        ...
        static void InitializeStub()
        {
            S2CStub.SystemChat = (HostID remote, RmiContext rmiContext, string str) =>
            {
                return true;
            };
            S2CStub.NotifyChat = (HostID remote, RmiContext rmiContext, string str) =>
            {
                return true;
            };
        }
        ...

Server > process/Process.cs

using Nettention.Proud;

namespace Server.process
{
    internal class CommonProcess
    {
        static S2C.Proxy S2CProxy = new S2C.Proxy();
        static C2S.Stub C2SStub = new C2S.Stub();

        public void InitStub()
        {
            // Stub에 등록
            C2SStub.Chat = Chat;

            ServerLauncher.NetServer.AttachProxy(S2CProxy);
            ServerLauncher.NetServer.AttachStub(C2SStub);
        }
        // Chat 함수 로직 작성
        static public bool Chat(HostID remote, RmiContext rmiContext, string str)
        {
            return true;
        }

    }
}

 

SystemChat 구현

시스템 채팅을 받으면 클라이언트에서 해줄 일은 채팅을 출력해주는 것이 전부입니다.

 

Client > Program.cs

        static void InitializeStub()
        {
            S2CStub.SystemChat = (HostID remote, RmiContext rmiContext, string str) =>
            {
                lock (g_critSec)
                {
                    Console.WriteLine("[System] {0}", str);
                }
                return true;
            };
            
            ...
            
        }

여기서 lock은 사실 필요하진 않습니다. 공유 자원에 접근하는 일이 따로 없기 때문이죠.

하지만 일단은 Stub을 처리할 때는 lock을 넣는다 라고 생각하고 넘어가셔도 좋을 것 같습니다.

물론 lock을 빼고 Console.WriteLine( ) 함수만 작성하셔도 문제될게 전혀 없습니다.

 

이제 서버에서 S2CProxy.SystemChat( ) 함수를 실행하면 클라이언트에게 메세지가 전송될 것입니다.

해당 함수를 외부에서도 호출할 수 있도록 public 메서드를 하나 더 작성해줍니다.

 

Server > process/Process.cs

internal class CommonProcess{

        ...
        
        public void SystemChat(string str)
        {
            S2CProxy.SystemChat(ServerLauncher.NetServer.GetClientHostIDs(), RmiContext.ReliableSend, str);
        }
        
}

서버 어디서든 이 함수를 호출하면 모든 클라이언트에게 시스템 메세지를 전송할 수 있습니다.

NetServer.GetClientHostIDs( ) 는 ProudNet에서 제공하는 메서드로 접송중인 모든 HostID를 배열로 리턴합니다.

 

접속 / 접속종료 핸들러 등록

Handler.cs를 열어보면 미리 작성해둔 여러 핸들러들이 있습니다.

 그 중에 사용할 것은 ClientJoinHandler와 ClientLeavehandler 입니다. 각각 클라이언트가 접속 / 접속종료 했을 때 서버에서 실행하는 핸들러들입니다. 아래 내용과 같이 메서드 안을 채워줍니다.

Server > Handler.cs

internal class Handler{

    ...
    
    public void ClientJoinHandler(NetClientInfo clientInfo)
    {
        string message = string.Format("Host{0} entered", clientInfo.hostID);
        Console.WriteLine(message);
        Process.SystemChat(message);
    }

    public void ClientLeaveHandler(NetClientInfo clientInfo, ErrorInfo errorinfo, ByteArray comment)
    {
        string message = string.Format("Host{0} leaved", clientInfo.hostID);
        Console.WriteLine(message);
        Process.SystemChat(message);
    }
    
    ...
    
}

동작은 단순합니다. 서버 콘솔에 메세지를 출력하고, 클라이언트에게 전송합니다.

 

실행

클라이언트 접속

왼쪽 위부터 클라이언트 프로그램을 실행하였습니다.

클라이언트를 켤 때마다 시스템 메세지가 출력되는 것을 볼 수 있습니다.

 

클라이언트 종료

마찬가지로 클라이언트를 종료할 때마다 시스템 메세지가 출력됩니다.

 

Chat 구현

 이제 채팅 메세지를 처리를 해보겠습니다. 먼저 로직을 작성하기 전에 클라이언트에서 이를 테스트할 수 있게 하겠습니다. 클라이언트 Main문의 loop에 다음과 같은 내용을 작성합니다.

Client > Program.cs

...

while (keepWorkerThread)
{
    string userInput = Console.ReadLine();
    
    if (userInput == "q")
    {
    	keepWorkerThread = false;
    }
    else
    {
    	C2SProxy.Chat(HostID.HostID_Server, RmiContext.ReliableSend, userInput);
    }
}

...

키보드 input을 받고 C2SProxy를 통해 Chat메세지를 서버로 보내는 내용입니다.

HostID.HostID_Server 은 서버의 Host ID로 해당 값을 인자로 넣으면 서버로 요청이 가도록 되어있습니다.

 

다음은 서버에서 Chat요청을 처리해줄 Stub을 작성해야합니다.

Server > process / Process.cs

internal class CommonProcess{

	...
    
    
    static public bool Chat(HostID remote, RmiContext rmiContext, string str)
    {
        Console.WriteLine(str);
        S2CProxy.NotifyChat(ServerLauncher.NetServer.GetClientHostIDs(), rmiContext, str);
        return true;
    }
    
    ...
    
}

Chat 메세지를 받으면 시스템 메세지를 보내는 것 처럼 모든 유저에게 내용을 보냅니다.

이제 다시 클라이언트에서 NotifyChat을 받으면 처리할 stub을 구현해줍니다.

 

Client > Program.cs

class Program{

        ...
        
        static void InitializeStub()
        {
            S2CStub.SystemChat = (HostID remote, RmiContext rmiContext, string str) =>
            {
                lock (g_critSec)
                {
                    Console.WriteLine("[System] {0}", str);
                }
                return true;
            };
            S2CStub.NotifyChat = (HostID remote, RmiContext rmiContext, string str) =>
            {
                lock (g_critSec)
                {
                    Console.WriteLine("> {0}", str);
                }
                return true;
            };
        }
        
        ...
        
}

내용은 역시 단순 출력 밖에 없습니다. SystemChat과 똑같으나 구분을 위해 출력 포맷만 다르게했습니다.

 

실행

오른쪽 콘솔에서 키보드 입력을 넣으면

서버는 물론 다른 클라이언트들도 메세지를 출력하는 것을 볼 수 있습니다.

 

 

마무리

이번 포스트에서는 가장 단순한 형태의 RMI메세지를 처리하는 것을 해보았습니다.

또한 서버에서 클라이언트에게 메세지를 BroadCast하는 것도 해보았습니다.

 

그러나 채팅서비스에는 유저의 닉네임이 들어가야 누가 메세지를 보냈는지 알 수 있습니다.

이를 구현하기 위해 다음 포스트에서는 유저 클래스를 추가하고 이를 이용해 RMI 통신을 해보겠습니다.

 

다음 포스트: 채팅 서버 만들기(3) Marshaler 이용하기