이번 시간에는 엔진단과 컨텐츠단을 분리하는 연습을 할 것이다.

먼저 Session 클래스를 추상 클래스로 정의하여 추상함수를 다음과 같이 만든다.

 abstract class Session
    {
        Socket _socket;
        int _disconnected = 0;

        object _lock = new object();

        Queue<byte[]> _sendQueue = new Queue<byte[]>();
        List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();

        SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
        // 이런 식으로 하면 RegisterSend 할 때 마다 Argument를 인자로 전달할 필요 없음
        
        SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();

        abstract public void OnConnected(EndPoint endPoint);
        
        abstract public void OnRecv(ArraySegment<byte> buffer);

        abstract public void OnSend(int numOfBytes);

        abstract public void OnDisconnected(EndPoint endPoint);

그 다음 Session을 상속 받은 GameSession 이라는 클래스를 만든다.

MMORPG Session, AOS Session, FPS Session 등 게임에 따라 각기 다른 Session을 가져야 할 수 도 있다. 상속은 그런 개념이다.

class GameSession : Session // 엔진딘과 컨텐츠단를 분리
    {
        public override void OnConnected(EndPoint endPoint) // 연결됬을 때 할 것 
        {
            Console.WriteLine($"On connected : {endPoint}");

            byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server");
            Send(sendBuff);

            Thread.Sleep(1000);

            // 쫒아낸다. 
            Disconnect();
        }

        public override void OnDisconnected(EndPoint endPoint)
        {
            Console.WriteLine($"On Disconnected : {endPoint}");
        }

        public override void OnRecv(ArraySegment<byte> buffer)
        {
            string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
            Console.WriteLine($"[From Client] {recvData}");

        }

        public override void OnSend(int numOfBytes)
        {
            Console.WriteLine($"Transferd Bytes {numOfBytes}");
        }

    }

무엇보다 OnConnected 추상 메소드에서 Send( )를 쓴 것 처럼 Session 클래스의 메소드에 쉽게 접근할 수 있는 것이 상속의 장점이다.

On~~ 함수를 만들어서 OnEventCompleted 함수의 코드 중 내부에 위치할 필요가 없는 코드를 밖으로 빼낸다.


Listener 클래스도 조금 손을 보자.

Main(외부)에서 세션의 종류를 선택할 수 있게 아래와 같이 Listener.Init을 수정하자

        static void Main(string[] args)
        {
            //DNS(Domain Name System) 
            string host = Dns.GetHostName(); // DESKTOP-S7EG95G
            IPHostEntry ipHost = Dns.GetHostEntry(host); // System.Net.IPHostEntry
            IPAddress ipAddr = ipHost.AddressList[0];  // fe80::b599:93ca:869a:5736%18
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // 주소와 포트번호
            

            _listener.Init(endPoint, ()=> { return new GameSession(); });
            // 외부에서는 어떤 세션을 만들지(GameSession)만 결정해준다.
            // 전체적인 처리는 내부에서 처리한다.

Init의 매개변수를 Action 대신에 Func로 수정하였다.

        public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
        {
            _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            // 첫 번째 매개변수: IP 버전4 인지 버전6인지 
            // 그 다음 매개변수: TCP를 쓸건지 UDP를 쓸건지 

            _sessionFactory += sessionFactory;

            // 문지기 교육 
            _listenSocket.Bind(endPoint); // 문지기의 핸드폰에 비밀식당의 주소를 알려주는 것. 

            // 영업시작 
            _listenSocket.Listen(10);


            for (int i = 0; i < 10; i++) // 문지기를 여러개 생성
            {
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
                RegisterAccept(args);
            }

        }

리스너가 Accept 할 때 대리인에게 소켓을 건네주는 동작을 바로 하자.

        public void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            if (args.SocketError == SocketError.Success)
            {
                // 실제로 유저가 왔으면 할 것
                Session session = _sessionFactory.Invoke(); // 강제로 GameSession으로 만들어줘? 
                session.Start(args.AcceptSocket);
                session.OnConnected(args.AcceptSocket.RemoteEndPoint);

                // 대리인에게 소켓을 주는 역활을 한다. args.AcceptSocket 한테 대리인의 소켓을 넣어준다. 

                
            }
            else
                Console.WriteLine(args.SocketError.ToString());

            RegisterAccept(args); // 낚시대를 바다에 다시 던진다. 
        }

이런 식으로 엔진단의 코드는 내부에서 관리하고

외부에서는 컨첸츠에 따라 간단한 수정만 필요하게 수정을 하자.

profile
POSTECH EE 18 / Living every minute of LIFE

0개의 댓글