Listener

원래벌레·2022년 8월 17일
0
post-custom-banner

🌞 Listener의 class화

  • 전 포스팅 소켓프로그래밍 입문 부분에서 우리는 Main에 모든 행동을 다 넣어버렸다. 하지만 이렇게 하게되면 나중에 유지보수가 너무나도 힘들어지기 때문에 분리 할 수 있는 부분은 분리 하는 것이 좋다. 그래서 Listener를 분리하겠다.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ServerCore
{
    class Listener
    {
        Socket _listenerSocket;
        public void Init(IPEndPoint endPoint)
        {
            //문지기
            Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            //문지기 교육
            listenSocket.Bind(endPoint);

            //영업시작
            //backlog : 최대 대기수
            listenSocket.Listen(10);
        }

        public Socket Accept()
        {
            return _listenerSocket.Accept();
        }
    }
}
  • Main에 있었던 Listener 소켓을 만들어주고 바인딩 하고 Listen을 실행하는 부분까지를 분리해준다.

🌞 블로킹 함수

  • 블로킹 함수란 것은 Accept 메소드와 같이 실행이 될 때 까지 자신의 제어권을 양보하지 않고 자기가 가지고 있어서, 다른 함수들은 실행이 되지 않고 대기상태에 빠지게 하는 함수를 이야기 한다.

이러한 함수들은 게임서버에서는 최대한 피해야 하는 함수들이다.

  • 이것이 왜 문제가 되는가 ? : Receive와 Send 메소드도 블로킹 함수이다. 만약에 동접자 수가 1만명이 된다고 할 때, 어느 순간에 Send 또는 Receive를 받지 못하고 무한정 대기 상태에 빠진다 하면, 유저들이 원활한 플레이를 할 수가 없을 것이다.

비동기식 함수가 필요하다!


🌞 비동기식

  • 비동기식이란, 어떤 일을 한다 했을 때, 그 일을 하던 중에 잠깐 다른 일을 하고, 또 다시와서 그 일을 하는 멀티태스크의 개념이다.

Accept가 동기식 메소드 였다면 AcceptAsync 는 비동기식 메소드이다. 이 메소드에 대해서 짧게 이야기 하면, AcceptAsync는 인수로 SocketAsyncEventArgs를 받는다. 이 인수는 Completed 라는 eventHandler를 가지고 있는데, 이 eventHandler는 AcceptAsync가 종료될 때 실행 된다.

AcceptAsync -> Sleep -> 다른 일 -> AccepAsync -> AcceptAsync 완료 -> SocketAsyncEventArgs.Completed 실행 -> AcceptAsync 다시실행

bool AccepAsync(SocketAsyncEventArgs)
return값은 bool로 만약에 운좋게 Accept가 바로 이루어진 경우에는 false값을, Accept가 이루어지지 않았으면 true를 return한다.

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ServerCore
{
    class Listener
    {
        Socket _listenerSocket;
        Action<Socket> _onAcceptHandler;
        public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
        { 
            //문지기
            _listenerSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            _onAcceptHandler += onAcceptHandler;

            //문지기 교육
            _listenerSocket.Bind(endPoint);

            //영업시작
            //backlog : 최대 대기수
            _listenerSocket.Listen(10);

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
            RegisterAccept(args);
        }

        void RegisterAccept(SocketAsyncEventArgs args)
        {
            args.AcceptSocket = null;

            bool pending = _listenerSocket.AcceptAsync(args);
            if (pending == false)
                OnAcceptCompleted(null, args);
                   
        }

        void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            if (args.SocketError == SocketError.Success)
            {
                _onAcceptHandler.Invoke(args.AcceptSocket);
            }
            else
                Console.WriteLine(args.SocketError.ToString());

            RegisterAccept(args);
        }


        public Socket Accept()
        {
            return _listenerSocket.Accept();
        }
    }
}


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ServerCore
{
    class Program
    {
        static Listener _listener = new Listener();

        static void OnAccetHandler(Socket clientSocket)
        {
            try
            {
                //받는다.
                byte[] recvBuff = new byte[1024];
                int recvBytes = clientSocket.Receive(recvBuff);
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                Console.WriteLine($"[From Client] {recvData}");

                //보낸다.
                byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server !");
                clientSocket.Send(sendBuff);

                //쫓아낸다.
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
            }

        }
        static void Main(string[] args)
        {
            //DNS(Domain Name Service
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

            _listener.Init(endPoint,OnAccetHandler);
                
            while (true)
            {
                ;
            }
        }

    }
}

cf) Action의 그냥 실행과 Invoke 실행의 차이
Invoke 실행 : Action이 null 일때는 실행하지 않음
그냥 실행 : Action이 null인경우 NullReference 반환

cf2) Class 매개변수는 값을 복사하는 형식이 아닌 참조형이다.
args.AcceptSocket = null; 을 해주는 이유

profile
학습한 내용을 담은 블로그 입니다.
post-custom-banner

0개의 댓글