C#

Connect를 위한 Connector 생성

monstro 2024. 12. 25. 16:08
728x90
반응형

이번 포스트에서는 Listen을 수행하는 Listener 말고도

Connect를 수행하는 Connector를 만들어 보겠습니다.

 

이렇게 사용하는 이유는 사용 목적 - 사용 도구를 짝지을 수 있고

혹시 서버와 다른 서버를 연결하는 경우유동적으로 대응할 수 있기 때문입니다.

 

Connector의 코드는 다음과 같습니다.

1) Connector

public class Connector
{
    // Session을 생성하고 리턴하기 위한 Func
    Func<Session> _sessionFactory;

    // 연결을 수행하는 함수
    public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory)
    {
        // 서버 소켓 생성
        Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _sessionFactory = sessionFactory;

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += OnConnectCompleted;
        args.RemoteEndPoint = endPoint;         // Connect할 대상 주소
        args.UserToken = socket;                // 현재 사용자 정보

        RegisterConnect(args);
    }

    // Connect를 준비(=대기)
    void RegisterConnect(SocketAsyncEventArgs args)
    {
        // socket이 아닌 이벤트를 생성하는 이유는, Listen처럼 Connect도 여러 번 발생할 수 있기 때문
        Socket socket = args.UserToken as Socket;

        if (socket == null)
            return;

        bool pending = socket.ConnectAsync(args);
        // Connect가 대기없이 즉시 이뤄지면 OnConnectCompleted를 호출함
        if (pending == false)
            OnConnectCompleted(null, args);
    }

    // Connect가 문제없이 수행되는 경우, 호출
    void OnConnectCompleted(object handler, SocketAsyncEventArgs args)
    {
        // 문제없이 이뤄졌다면
        if (args.SocketError == SocketError.Success)
        {
            // Func인 _sessionFactory의 함수의 리턴결과를 Session의 인스턴스로 받고
            Session session = _sessionFactory.Invoke();
            // Session의 인스턴스의 Start를 호출하여 네트워크 통신 준비
            session.Start(args.ConnectSocket);
            // Session의 OnConnected 함수 호출
            session.OnConnected(args.RemoteEndPoint);
        }
        // 문제가 발생되었다면 해당 에러 출력
        else
        {
            Console.WriteLine($"OnConnectCompleted Fail : {args.SocketError.ToString()}");
        }
    }
}

 

코드는 위와 같이 구성되었습니다.

이제 기존의 구조를 조금 변경하도록 하겠습니다.

 

기존의 Server로 적혀진 코드는 ServerCore라는 엔진 단계의 클래스에서 동작합니다.

이제 Server 클래스를 추가하고 Server 와 Client 클래스컨텐츠 단계로 분리하여 동작을 하도록 하고

엔진 단계인 ServerCore에서는 동작하는데 필요한 행동정의하는 방식으로 구분하겠습니다.

 

1 - 1) 간단한 수정

ServerCore를 Console Application을 Class Library로 변경하도록 합니다.
변경된 ServerCore는 Application이 아닌 Class Library이므로 실행할 수 없게 됩니다.

Server를 우클릭하고 Add - Service Reference를 누르고 
Projects - Solution에서 ServerCore를 참조하도록 설정하겠습니다.

참조하는 경우, ServerCore의 메서드를 가져올 수 있습니다.

DummyClient를 우클릭하고 Add - Service Reference를 누르고 
Projects - Solution에서 ServerCore를 참조하도록 설정합니다.

마찬가지로, ServerCore의 메서드를 가져올 수 있습니다.

이후 기존의 ServerCore의 Program의 코드를 Server의 Program 코드로 이전하겠습니다.
위의 변경 사항 이후로 ServerCore는 엔진 코드가 되고, Server와 dummyClient는 컨텐츠 코드가 됩니다.

최종적으로 Startup Project를 Server와 Client로 설정합니다.

 

이제 변경되고 새로 작성된 Client와 Server 코드를 보겠습니다.

2) Client

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

        // 서버로 전송
        for (int i = 0; i < 5; i++)
        {
            byte[] sendBuffer = Encoding.UTF8.GetBytes($"Hello From Client {i}");
            Send(sendBuffer);
        }
    }

    // 연결이 끊기는 경우 호출
    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 Server] : {recvData}");
    }

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

class Program
{
    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);

        Connector connector = new Connector();
        connector.Connect(endPoint, () => { return new GameSession(); });

        while (true)
        {
            try
            {
               
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Thread.Sleep(100);
        }
    }
}

 

클라이언트의 경우도 연결을 위한 Session을 만들고 Connector를 생성하여 Connect를 수행합니다.

 

3) 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는 기존의 ServerCore의 코드를 복사하여 가져오겠습니다.

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

 

 

Connector가 하나밖에 생성되지 않아 Connect가 한번만 이뤄지고 종료되지만,

문제없이 클라이언트 - 서버간의 통신이 이뤄지는 것을 확인할 수 있습니다.

728x90
반응형

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

RecvBuffer와 SendBuffer  (0) 2024.12.30
TCP - UDP의 이해  (0) 2024.12.25
Session_2  (0) 2024.12.24
Session_1  (0) 2024.12.24
Listener  (0) 2024.12.18