12. 원격지 프락시 구현

12. 4 구현

프락시 패턴 중에 원격지 프락시에 대한 예제 프로그램을 구현하는 순서는 서버 측을 구현한 후에 클라이언트 측을 구현할게요. 서버 측은 ITake, ICamera, ListenServer와 Stub 순으로 구현해 봅시다.

12.4.1 ITake와 Camera

ITake에서는 단순히 TakeAPicture, ChangeMode, GetMode 메서드를 약속합시다.

▶ ITake.cs

namespace RemoteClient
{
    interface ITake //실제 개체인 카메라가 수행할 수 있는 기능 약속
    {
        string TakeAPicture();
        void ChangeMode(bool mode);
        bool GetMode();
    }
    enum MsgId //실제 개체에 내릴 수 있는 명령 종류
    {
        TAKE=1,
        CHANGE,
        GET
    }
}

그리고 Camera에서는 ITake에 약속된 각 메서드를 구현하면 됩니다.

▶ Camera.cs

using System;
namespace RemoteProxy
{
    class Camera:ITake
    {
        bool mode = false; //true: 수동 모드, false: 자동 모드

        public string TakeAPicture()
        {
            Console.WriteLine("사진을 찍습니다.");
            if (mode)
            {
                return "수동 모드로 찍힌 사진";
            }
            return "자동 모드로 찍힌 사진";
        }
        public void ChangeMode(bool mode)
        {
            this.mode = mode;
            if (mode)
            {
                Console.WriteLine("수동 모드로 변환되었습니다.");
            }
            else
            {
                Console.WriteLine("자동 모드로 변환되었습니다.");
            }
        }
        public bool GetMode()
        {
            if (mode)
            {
                Console.WriteLine("수동 모드입니다.");
            }
            else
            {
                Console.WriteLine("자동 모드입니다.");
            }
            return mode;
        }
    }
}

참고로, 원격지에 있는 개체가 실제 행위가 수행되는 것을 시각적으로 확인할 수 있게 각 메서드에 출력 구문을 추가하였습니다.

12.4.2 ListenServer 와 Stub

ListenServer에서는 소켓을 생성하여 클라이언트의 연결을 기다리는 부분이 필요할 것입니다. 그리고 클라이언트 측에서 연결 요청이 와서 이를 수락해야 할 시점이 오면 Stub 개체를 생성합니다. 그리고 접속한 클라이언트와 작업하는 쓰레드를 생성할 때 Stub 개체를 인자로 전달합니다. 해당 쓰레드에서는 Stub 개체를 통해 클라이언트 측의 요청에 맞게 Camera 개체를 제어하면 될 것입니다. 소켓 통신에 대한 설명은 하지 않겠습니다.

▶ ListenServer.cs

using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace RemoteProxy
{
    static class ListenServer //클라이언트 연결을 수락하는 서버
    {
        static public void StartListen(ITake itake)
        {
            Socket sock = new Socket(AddressFamily.InterNetwork,
                                               SocketType.Stream, ProtocolType.Tcp);
            sock.Bind(new IPEndPoint(IPAddress.Any, 10200));
            sock.Listen(5);
            while (true)
            {
                Stub stub = new Stub(sock.Accept(), itake); //클라이언트와 통신을 담당
                Thread thread = new Thread(stub.Do);
                thread.Start();
            }
        }
    }
}

Stub 개체는 클라이언트 측에서 요청하는 것이 무엇인지 확인하여 요청대로 Camera 개체를 사용하고 결과가 필요한 경우에 다시 클라이언트 측에게 전달하면 되겠죠.

▶ Stub.cs

using System;
using System.Net.Sockets;
using System.Text;
namespace RemoteProxy
{
    class Stub //클라이언트 명령을 받아 실제 개체를 제어하는 서버 측 클래스
    {
        Socket dosock; //클라이언트와 통신할 소켓
        ITake itake; //클라이언트의 요청에 맞게 기능을 수행할 실제 개체

        public Stub(Socket dosock, ITake itake)
        {
            this.dosock = dosock;
            this.itake = itake;
        }
        private void GetProc()
        {
            bool mode= itake.GetMode(); //실제 개체에게 모드를 얻어옴
            byte[] msg = new byte[1]; //모드를 전송할 때 필요한 버퍼
            msg[0] = Convert.ToByte(mode);
            dosock.Send(msg);  //요청한 모드를 전송
        }
        private void TakeProc()
        {
            string picture = itake.TakeAPicture(); //실제 개체로 사진을 찍음
            byte[] msg = Encoding.UTF8.GetBytes(picture); //사진을 전송할 버퍼로 변환
            dosock.Send(msg); //실제 개체가 찍은 사진을 전송
        }
        private void ChangeProc()
        {
            byte[] buffer = new byte[1]; //모드를 수신할 때 필요한 버퍼
            bool mode;
            dosock.Receive(buffer); //요청한 모드를 받음

            mode = Convert.ToBoolean(buffer[0]);
            itake.ChangeMode(mode); //실제 개체에게 모드 변경 요청
        }

        public void Do()
        {
            byte[] buffer = new byte[1];
            int n = dosock.Receive(buffer); //명령 종류를 얻어옴

            MsgId msgid = (MsgId)buffer[0];
            Console.WriteLine(msgid);

            switch (msgid) //수신한 명령에 맞는 기능 수행
            {
                case MsgId.TAKE: TakeProc(); break;
                case MsgId.CHANGE: ChangeProc(); break;
                case MsgId.GET: GetProc(); break;
            }
            dosock.Close();
        }
    }
}

▶ Program.cs

namespace RemoteProxy
{
    class Program
    {
        static void Main(string[] args)
        {
            Camera camera = new Camera(); //실제 개체인 카메라 생성

            //실제 개체를 제어를 요청할 클라이언트를 대기하는 서버 가동
            ListenServer.StartListen(camera);  
        }
    }
}

12.4.3 클라이언트 측

클라이언트 측에서는 원격 프락시 개체인 RemoteController를 통해 서버 측의 Stub과 소켓 통신을 하여 서버 측에 있는 실제 개체인 Camera 개체를 제어할 것입니다.

클라이언트 측의 원격 프락시 개체인 RemoteController의 경우 ITake에 약속된 메서드들을 구현해야 할 것입니다. (ITake.cs 파일은 클라이언트 측에도 있어야 합니다.)

 ▶ RemoteController.cs

using System;
using System.Text;
using System.Net.Sockets;
namespace RemoteClient
{
    class RemoteController:ITake
    {
        public string TakeAPicture()
        {
            Socket sock = Connect(MsgId.TAKE);
            byte[] buffer = new byte[256];
            int n = sock.Receive(buffer);
            sock.Close();
            return Encoding.UTF8.GetString(buffer, 0, n);
        }
        public void ChangeMode(bool mode)
        {
            Socket sock = Connect(MsgId.CHANGE);            
            byte[] msg = new byte[1];
            msg[0] = Convert.ToByte(mode);
            sock.Send(msg);
            sock.Close();
        }
        public bool GetMode()
        {
            Socket sock = Connect(MsgId.GET);
            byte[] buffer= new byte[1];
            int n = sock.Receive(buffer);
            return Convert.ToBoolean(buffer[0]);
        }
        Socket Connect(MsgId msgid)
        {            
            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            
            sock.Connect("서버 IP 주소로 바꾸세요.",10200);
            byte[] msg = new byte[1];
            msg[0] = (byte)msgid;
            sock.Send(msg);
            return sock;
        }
    }
}

 ▶ Program.cs

using System;
namespace RemoteClient
{
    class Program
    {
        static void Main(string[] args)
        {            
            RemoteController remocon = new RemoteController();
            string s = remocon.TakeAPicture();
            Console.WriteLine(s);
            remocon.ChangeMode(true);            
            s = remocon.TakeAPicture();
            Console.WriteLine(s);
            remocon.ChangeMode(false);
            s = remocon.TakeAPicture();
            Console.WriteLine(s);
            bool mode = remocon.GetMode();
            if (mode)
            {
                Console.WriteLine("수동 모드로 변환되었습니다.");
            }
            else
            {
                Console.WriteLine("자동 모드로 변환되었습니다.");
            }
        }
    }
}
[그림] 원격지 프락시 실행 예
[그림] 원격지 프락시 실행 예

 여기에서는 소켓 통신을 이용하여 프락시 패턴에 대해 구현해 보았는데 여러분들은 .NET 리모팅에 대해 학습을 해 보십시오. 효과적으로 프로그래밍을 할 수 있음을 알 수 있을 것입니다.