Server/Study

[Server] Socket - Listener 분리

GiveZero 2023. 5. 1. 19:16

Listener

1. _listenSocket.AcceptAsync(args); 이함수를 실행하면 비동기적으로 계속해서 연결요청이 있는지 확인

2. 연결 요청이 왔을시 내부에서 누군가가(닷넷)args.Completed.Invoke(sender, args) 실행

3. 등록했던 이벤트 OnAcceptCompleted 가실행됨

 

바로 접속하면 직접 OnAcceptCompleted 를 실행 (pending이 false일 경우)

=> Callback 함수를 실행할 수 없기 때문에

 

나중에 접속하면 args.Completed.Invoke()를 통해 실행

=> 클라이언트의 입장을 기다리다가 입장 신청을 하게되면 OnAcceptComplete를 콜백으로 실행

 

MSDN 참고자료

 

Socket.AcceptAsync 메서드 (System.Net.Sockets)

들어오는 연결을 허용합니다.

learn.microsoft.com

 

C#의 ThreadPool에는 2가지 thread

1. 일반적인 worker thread

2. I/O작업이 완료되었을때의 작업을 담당하는 (I/O) completion port thread

소켓통신은 CPU가 아니라 I/O단에서 이루어지는 작업

SocketAsyncEventArgs의 Complete eventHandler는 completion port thread가 담당하여 invoke

(Pending == true일때)

 

이와 같이 사용하는 이유

기다리지 않고 바로 처리

이전처럼 Socket clinetSocket = _listener.Accept(); 와 같이 사용한다면

실제 클라이언트 쪽에서 Connect라는 함수를 입장요청을 하기 전까지 return하지 않음

메인쓰레드를 만들었지만, 일을하지 않고 가만히 아무일을 하지 않는 쓰레드가 됨

유저가 많아졌을 경우 그 유저들을 처리하기 위해서 Receive와 Send를 왔다갔다 해야하는데,

무한 대기를 하면 안되기 때문

따라서 비동기(async) 함수를 사용해야함

using System.Net;
using System.Net.Sockets;

namespace ServerCore
{
    class Listener
    {
        private Socket _listenSocket;
        private Action<Socket> _onAcceptHandler;

        public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
        {
            _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            _onAcceptHandler += onAcceptHandler;
            
            // Bind
            _listenSocket.Bind(endPoint);
            
            // Listen
            // backlog : 최대 대기 수 
            _listenSocket.Listen(10);

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

        void RegisterAccept(SocketAsyncEventArgs args)
        {
            // 이벤트를 재사용 하기 때문에 null값 입력
            args.AcceptSocket = null; 
            
            bool pending = _listenSocket.AcceptAsync(args);
            if (!pending)
            {
                OnAcceptCompleted(null,args);
            }
        }

        void OnAcceptCompleted(object sender,SocketAsyncEventArgs args)
        {
            if (args.SocketError == SocketError.Success)
            {
                // Socket clientSocket = _listener.Accept(); 와 같은 역할
                _onAcceptHandler.Invoke(args.AcceptSocket);
            }
            else
            {
                Console.WriteLine(args.SocketError.ToString());
            }
            
            RegisterAccept(args);
        }
    }
}

 

Main

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

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

        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("Welcome to YoungJune Server!");
                clientSocket.Send(sendBuff);

                // 퇴장
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

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


            _listener.Init(endPoint, OnAcceptHandler);
            Console.WriteLine("Listening...");

            while (true)
            {

            }

        }
    }
}