(C# : Server) Listner

Posted by : at

Category : Charp-Server



코드를 조금 정리해 보자

// ServerCore
// Listener.cs

namespace ServerCore
{
    public class Listener
    {
        Socket _listenSocket;

        public void Init(IPEndPoint endPoint)
        {
            _listenSocket = new Socket(endPoint.AddressFamily,
                                        SocketType.Stream, ProtocolType.Tcp); /* TCP로 사용하겠다는 선언 */

            _listenSocket.Bind(endPoint);

            _listenSocket.Listen(10);
            // 10 : 최대 대기수, 초과시 입장을 제한한다
        }

        public Socket Accept()
        {
            return _listenSocket.Accept();
        }
    }
}
// ServerCore
// Program.cs

class Program
{
    static Listner _listener = new Listner();

    static void Main(string[] args)
    {
        // DNS(Domain Name System)을 사용할 예정
        // ip addr를 안쓰고 도메인네임을 쓰겠다는 말.
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777/* Port */);

        try
        {

            while(true)
            {
                // 에코서버로 구현예정
                Console.WriteLine("Listening...");

                // 클라이언트가 입장하지 않으면 여기서 블락됨.(나중에 개선예정)
                Socket clientSocket = listenSocket.Accept();

                // 받는 부분
                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("Welcom to MMORPG Server ");
                clientSocket.Send(sendBuff);

                // 소켓을 닫는다
                clientSocket.Shutdown(SocketShutdown.Both); /*소켓을 닫는다를 미리 공지*/
                clientSocket.Close();   /*실제로 닫는 부분*/
            }
        }
        catch(Exception e)
        {

        }
    }
}

코드를 조금 더 정리하기 전
현재 가장 시급한 문제는 아래이다.

class Listener
{
    // ...

    public Socket Accept()
    {
        // 여기가 accept 전 까지 블록되기에 이 부분을 해결해보자
        return _listenSocket.Accept();
    }
}

논블로킹으로 수정해보자

class Listener
{
    Socket _listenSocket;
    Action<Socket> _onAcceptHandler;

    public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
    {
        _listenSocket = new Socket(endPoint.AddressFamily, 
                                    SocketType.Stream, ProtocolType.Tcp);
                                    /* TCP로 사용하겠다는 선언 */

        // Accept시 델리게이트 선언
        _onAcceptHandler += onAcceptHandler;

        _listenSocket.Bind(endPoint);

        _listenSocket.Listen(10);
        // 10 : 최대 대기수, 초과시 입장을 제한한다

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

    void RegisterAccept(SocketAsyncEventArgs args)
    {
        // 기존 소켓정보를 날린다
        args.AcceptSocket = null;

        bool pending = _listenSocket.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());
        }

        // 다음 클라를 위해서 다시 Register한다
        RegisterAccept(args);
    }
}
// ServerCore
// Main.cs

class Program
{
    static Listner _listener = new Listner();

    static void OnAcceptHandler(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("Welcom to MMORPG Server ");
            clientSocket.Send(sendBuff);

            // 소켓을 닫는다
            clientSocket.Shutdown(SocketShutdown.Both); /*소켓을 닫는다를 미리 공지*/
            clientSocket.Close();   /*실제로 닫는 부분*/
        }
        catch(Exception e)
        {

        }
    }

    static void Main(string[] args)
    {
        // DNS(Domain Name System)을 사용할 예정
        // ip addr를 안쓰고 도메인네임을 쓰겠다는 말.
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777/* Port */);

        _listener.Init(endPoint, OnAcceptHandler);

        while(true)
        {
        }
    }
}

추가사항

class Listener
{
    // ...

    public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
    {
        // ...

        // 이벤트를 여러개 둬서 받을 수 있음
        for(int i = 0; i < 10; i++)
        {
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);

            /*
            Main에서 while이 무한으로 돌고 있는데
            OnAcceptCompleted이 어느 타이밍에 호출되는가?
            -> 별도의 Thread가 생성되어 OnAcceptCompleted가 호출된다.
            -> 멀티쓰레드로 동작한다는 말(멀티쓰레드 세이프하게 만들어야함)
            */

            RegisterAccept(args);
        }
    }

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links