C#

Session_2

monstro 2024. 12. 24. 16:48
728x90
반응형

이전 포스트에 이어서 계속 만들어보겠습니다.

이번에는 Session이 아닌 클라이언트의 Connect 요청에 집중하는 Server의 Listener를 수정해보겠습니다.

기존의 Main 코드에서 수행하던 Accept를 이제 Listener에서 수행해보도록 하겠습니다.

이를 통해, 클라이언트가 아닌 서버가 수행하는 구조를 만들 수 있습니다.

 

1) Listener

class Listener
{
    Socket _listenSocket;
    // Action과 다르게 리턴값이 존재하는 Func, 제네릭 자료형이 곧 리턴형이다 
    Func<Session> _sessionFactory;

    // Server의 Client 소켓의 초기 상태를 설정
    public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
    {
        _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        // _sessionFactory로부터 리턴된 Session에 대해 호출할 이벤트를 연결
        _sessionFactory += sessionFactory;
		
 		.
		.
		.
    }

	.
	.
	.

    // Accept하는 Event 발생 시에 호출될 콜백함수
    void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
    {
        // Connect를 Accept 하는 과정에서 문제가 없다면 Session에 연결된 함수를 실행하고, Session을 가동
        if (args.SocketError == SocketError.Success)
        {
            // _sessionFactory로부터 생성된 Session에 연결된 함수를 호출함
            Session session = _sessionFactory.Invoke();
            // 받아온 seesion의 Start 호출
            session.Start(args.AcceptSocket);
            // 기존의 Session 클래스에서 정의한 OnConnected 호출
            session.OnConnected(args.AcceptSocket.RemoteEndPoint);
        }
        // 문제가 있다면 해당 문제를 출력
        else
            Console.WriteLine(args.SocketError.ToString());

        // 클라이언트로부터 다음에 들어올 Connect 요청을 Accept하기 위해 다시 준비
        RegisterAccept(args);
    }
}

 

기존에 사용하던 Action의 경우,

리턴값이 존재하지 않았지만 Func를 통해 리턴값이 존재하는 방식을 사용합니다.

이때 _sessionFactory는 Session을 리턴하도록 합니다.

 

Listener의 Init 함수에서는

_sessionFactory로부터 리턴된 Session에 대해 호출할 이벤트를 연결하도록 수정합니다.

 

그리고 기존의 Main에서 수행하던 Accept를,

Listener에서 수행하도록 하기 위해 함수 OnAcceptCompleted를 생성하였습니다.

해당 함수에서는 정상적으로 Accept가 이뤄진 경우 

_sessionFactory에 연결된 함수를 호출하고, 

이어서 _sessionFactory에서 생성된 Session을 Start + Session의 OnConnected함수를 호출합니다.

 

2) Server

Listener에서 Accept를 수행하므로 Server의 코드양이 매우 작아지고 간결해집니다.

구성은 다음과 같습니다.

 

class GameSession : Session
{
    // 연결되는 경우 호출
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"[Connect from ] : {endPoint}");

        byte[] sendBuffer = Encoding.UTF8.GetBytes("Welcome to Server!!!");
        Send(sendBuffer);

        Thread.Sleep(1000);

        Disconnect();
    }

    // 연결이 끊기는 경우 호출
    public override void OnDisconnected(EndPoint endPoint)
    {
        Console.WriteLine($"[Disconnect from] : {endPoint}");
    }

    // 클라에서 데이터를 받는 경우 호출
    public override void OnRecv(ArraySegment<byte> buffer)
    {
        string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
        Console.WriteLine($"[From Client] : {recvData}");
    }

    // 클라로 데이터를 보내는 경우 호출
    public override void OnSend(int numOfBytes)
    {
        Console.WriteLine($"Transferred Bytes : {numOfBytes}");
    }
}

class Program
{
    // Listen 소켓을 생성할 새로운 Listener 인스턴스 생성
    static Listener serverListener = new Listener();

    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 함수에 인자인 DNS의 IPEndPoint와 Accept 이벤트 발생시 호출할 콜백함수를 넘겨줌
        serverListener.Init(endPoint, () => { return new GameSession(); });
        Console.WriteLine("Listening...");

        while (true)
        {

        }
    }
}

 

Server에서는 사용할 Session을 GameSession이라는 자식 클래스로 상속하여 만들었습니다.

이제 실제 실행결과를 확인해보겠습니다.

 

 

의도한 대로, 클라이언트는 데이터를 보내고 받고 연결을 끊고 있고

서버는 계속 동작하며 클라이언트로부터 받은 데이터를 출력하는 것을 볼 수 있습니다.

728x90
반응형

'C#' 카테고리의 다른 글

TCP - UDP의 이해  (0) 2024.12.25
Connect를 위한 Connector 생성  (0) 2024.12.25
Session_1  (0) 2024.12.24
Listener  (0) 2024.12.18
소켓 프로그래밍 기초  (0) 2024.12.16