C#/네트워크

[c#][서버] Listener

goliot 2024. 6. 5. 16:51
반응형

주의할 점

  • 소켓 통신에서, accept, receive, send같은 입출력 계열의 함수는 비동기, 논블로킹 계열 함수로 만들어야 함

비동기 -> Accept 분리하기

  • Accept 요청과 완료를 분리하여 구현해야 함
using System.Net;
using System.Net.Sockets;
using System.Text;

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

    public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
    {
        _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _onAcceptHandler += onAcceptHandler; //연결 수락시 실행할 콜백 함수

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

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

        SocketAsyncEventArgs args = new SocketAsyncEventArgs(); //한번 만들면 계속 재사용
        args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted); //완료되면, OnAcceptCompleted 실행
        RegisterAccept(args);
    }

    //비동기에선 Accept 요청과 완료가 분리되어야 함
    //Register 와 Completed가 뺑뺑이를 돌면서 계속 실행됨
    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)
        {
            //TODO
            _onAcceptHandler.Invoke(args.AcceptSocket);
        }
        else
        {
            Console.WriteLine(args.SocketError.ToString());
        }

        RegisterAccept(args);
        //이번 꺼는 완료가 됐으므로, 다음 아이를 위해 재등록
    }
}
//------------------------------------------------------------------------------------------
class Program
{
    static Listener _listener = new Listener();

	//이 함수가 위의 _onAcceptHandler에 등록될 것
    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 MMORPG Server!");
            clientSocket.Send(sendBuff);
            //얘도 상대방이 받지 않으면 대기

            //쫓아낸다
            clientSocket.Shutdown(SocketShutdown.Both); //쫓아낼 것 예고
            clientSocket.Close(); //쫓아내기
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    static void Main(string[] args)
    {
        //DNS 사용
        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)
        {

        }
    }
}
반응형