Socket Listener

Eunho Bae·2022년 5월 3일
0

설명

블록킹 계열 함수인 Accept() 함수를 사용하는 것은 그 라인에서 클라이언트가 연결 요청을 받을 때까지 계속 대기를 할 수 있기 때문에, 비동기적으로 처리를 하기 위해 AcceptAsync() 함수를 사용하도록 코드를 구성한다.

ServerCore에서 Listener의 Init() 함수를 호출할때 콜백함수로 onAcceptHandler를 전달하고 있는데, 클라이언트가 언제 연결요청을 할지 모르니 언제라도 요청을 받을때 스스로 함수를 호출하도록 Action에 등록을 해준다.

그러면 아래 주석에 있는 설명 과정을 거친 후 OnAcceptCompleted가 될 때 Invoke() 함수를 호출시켜줄 때, 연결된 클라이언트 소켓 정보를 넘겨주면서 클라이언트 측에 문자열 정보를 Send() 함수로 넘겨준다.

참고로 ServerCore에서 while문에서 무한루프를 계속 도는 쓰레드는 주 쓰레드이고, 클라이언트 요청에 의해 콜백함수가 호출되면 작업자 쓰레드가 따로 실행이 된다.


코드

ServerCore.Listener

namespace ServerCore
{
    /*
     *  처음 Listener.Init() 함수에서 args에 클라이언트에서 연결 요청이 들어올 경우 실행시킬 함수(OnAcceptCompleted) 등록
     *  그리고 바로 RegisterAccept() 함수를 호출하면서 처음 Init할때 클라이언트에서 요청이 왔는지 확인.
     *  확인 후 요청이 왔으면 pending == false가 되며 OnAcceptCompleted가 명시적으로 호출이 될 것이고,
     *  요청이 현재 없다고 하면 pending == true가 되어 언젠가 요청이 들어오면 콜백방식으로 OnAcceptComplted가 호출될 것이다.
     * 
    */
    internal class Listener
    {
        Socket _listenSocket;
        Action<Socket> _onAcceptHandler;

        public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
        {
            // 통신 소켓 설정
            _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // v4 or v6, TCP로 할경우 Stream, Tcp
            _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)
        {
            // null이 아니면 크래시
            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());

            RegisterAccept(args); // 다음 클라이언트 요청을 받기 위해 다시 등록
        }

        /*
        // 클라가 접속을 안해도 그냥 넘어가는데, 만약 접속을 한다면 서버에서 접속한 놈한테 알려줘야 하는데 그때 콜백으로 알려줘야 함
        public Socket Accept()
        {
            return _listenSocket.Accept(); // 클라가 접속(Connect())할 때까지 여기서 멈춤 (블록킹 계열 함수)
        }
        */
    }
}

ServerCore.Program

namespace ServerCore
{
    class Program
    {
        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); // recvBuff에 0부터 recvBytes까지 가져와서 리턴
                Console.WriteLine($"[From Client] {recvData}");

                byte[] sendBuff = Encoding.UTF8.GetBytes("저희 서버에 오신것을 환영합니다!!");
                clientSocket.Send(sendBuff);

                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        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)
            {
                ;
            }
        }
    }
}

DummyClient.Program

namespace DummyClient
{
    class Program
    {

        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);

            while (true)
            {
                Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                try
                {
                    socket.Connect(endPoint);
                    Console.WriteLine($"Connected To {socket.RemoteEndPoint.ToString()}"); // 누구한테 연결되었는지

                    byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World"); // 할 말을 먼저 UTF8 형식으로 변환
                    int sendBytes = socket.Send(sendBuff);

                    byte[] recvBuff = new byte[1024];
                    int recvBytes = socket.Receive(recvBuff);
                    string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                    Console.WriteLine($"[From server] {recvData}");

                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                Thread.Sleep(100);
            }
        } 
    }
}
profile
개인 공부 정리

0개의 댓글

관련 채용 정보