본문 바로가기
cs공부/네트워크프로그래밍

네트워크프로그래밍 - Session부분과 Listener부분 분리하기(엔진과 컨텐츠 분리하기)

by 코딩하는 돼징 2023. 7. 14.
반응형

Session Send코드 알아보러가기

 

네트워크 프로그래밍 -Send코드 개선하기

Send 코드 참조 네트워크 프로그래밍 - C# Non - blocking을 사용한 Server 소켓프로그래밍의 Send Receive코드 알아보러 가기 네트워크 프로그래밍 - C# Non - blocking을 사용한 Server 소켓프로그래밍의 Receive Li

code-piggy.tistory.com


엔진과 컨텐츠 분리하기

이벤트 핸들러는 엔진에서 발생하는 이벤트를 처리하고 이벤트가 발생할 때 컨텐츠에 알림을 전달한다.

아래 메서드들을 통해 이벤트 핸들러와 연동될 수 있다.

public void OnConnected(EndPoint endPoint) {}
public void OnReceive(ArraySegment<byte> buffer){}
public void OnSend(int numofBytes){}
public void OnDisconnected(EndPoint endPoint) { }

01 OnConnected

클라이언트 접속을 알리는 시점이다. endPoint는 연결된 주소를 나타낸다.

02 OnReceive

데이터가 수신된 경우 호출된다. buffer는 수신한 데이터를 담고 있다.

03 OnSend

데이터를 전송한 후 호출된다. numofBytes는 전송된 데이터의 바이트 수를 나타낸다.

04 OnDisconnected

네트워크가 종료되었을때 호출된다. endPoint는 연결이 종료된 주소를 나타낸다.


Event 받아주는 방식

1. EventHandler를 만들어서 연결

class Sessionhandler
{
    public void OnConnected(EndPoint endPoint) { }
    public void OnReceive(ArraySegment<byte> buffer) { }
    public void OnSend(int numofBytes) { }
    public void OnDisconnected(EndPoint endPoint) { }
}

2. Session을 상속 받아서 만드는 방법

Session.cs

public abstract void OnConnected(EndPoint endPoint);
public abstract void OnReceive(ArraySegment<byte> buffer);
public abstract void OnSend(int numofBytes);
public abstract void OnDisconnected(EndPoint endPoint);

각 메서드들은 해당 이벤트에 대한 사용자가 정의한 동작을 구현한다.

Program.cs

class GameSeesion : Session
{
    public override void OnConnected(EndPoint endPoint)
    {         
    }

    public override void OnDisconnected(EndPoint endPoint)
    {          
    }

    public override void OnReceive(ArraySegment<byte> buffer)
    {       
    }

    public override void OnSend(int numofBytes)
    {          
    }
}

01 GameSession클래스를 이용해서 인스턴스화하여 사용

이제는 Session을 바로 만들 수 없고 상속 받은  GameSession을 이용해야 한다.

그 이유는 Session클래스가 추상클래스가 되었고 추상 클래스는 직접 인스턴스화를 할 수 없기 때문이다.

변경 전

static void onAcceptHandler(Socket clientSocket)
{
    try
    {               
        Session session = new Session();
    }
}

변경 후

static void onAcceptHandler(Socket clientSocket)
{
    try
    {               
        GameSession session = new GameSession();
    }
}

연동하는 방법

01 OnDisconnected

컨텐츠에 연결이 종료되었음을 알리는 역할

Session.cs

public void Disconnect()
{
    if (Interlocked.Exchange(ref _disconnected, 1) == 1)
        return;
    OnDisconnected(_socket.RemoteEndPoint);
     _socket.Shutdown(SocketShutdown.Both);
    _socket.Close();
}

02 OnReceive

컨텐츠에 데이터가 수신되었음을 알리는 역할

Session.cs

 void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
    if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
    {
        try
        {
            OnReceive(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));
            RegisterRecv();
        }

      ... 생략
}

args.Buffer - 수신된 데이터가 저장된 바이트 배열

args.Offset - 배열의 시작위치

args.ByteTrasnferred - 수신된 바이트의 수

 

원래 있던 부분은 컨텐츠 쪽으로 이동한다.

public override void OnReceive(ArraySegment<byte> buffer)
{
    string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
    Console.WriteLine($"[From Client] {recvData}");
}

03 OnSend

컨텐츠에 데이터가 전송 되었음을 알리는 역할

void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
    lock (_lock)
    {
        if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                _sendArgs.BufferList = null;
                _pendlingList.Clear();
                OnSend(_sendArgs.BytesTransferred);

                        
         ... 생략

원래 있던 부분은 컨텐츠 쪽으로 이동한다.

public override void OnSend(int numofBytes)
{
    Console.WriteLine($"Transfoerred bytes : {numofBytes}");
}

OnAcceptHandler & OnAcceptCompleted 보수 작업하기

 

01 OnAcceptHandler

원래 OnAcceptHandler 에서 클라이언트 소켓을 매개변수로 받아와서 해당 클라이언트와 통신을 처리하는 GameSession을 생성하고 시작하게 하였다.

Program.cs

static void onAcceptHandler(Socket clientSocket)
{
    try
    {
        GameSession session = new GameSession();
        session.Start(clientSocket);
    ... 생략

02 OnAcceptCompleted

비동기로 클라이언트가 서버에 접속하고자 할 때 서버가 클라이언트의 연결 요청을 수락하였을때 호출되며 args.AcceptSocket을 통해 연결된 클라이언트의 소켓을 전달 받았다.

Listener.cs

void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    if(args.SocketError == SocketError.Success)
    {
        _onAcceptHandler.Invoke(args.AcceptSocket);
    }
    ... 생략

이벤트 핸들러 내에서 GameSession을 생성하고 시작하는 코드를 작성하는 것이 더 좋다. 왜냐하면 위의 설명과 같이 접속 요청과 연결 요청이 완료되었을때 GameSession을 생성하고 클라이언트와의 통신을 처리해야하기 때문이다.

void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    if(args.SocketError == SocketError.Success)
    {
        GameSession session = new GameSession();
        session.Start(args.AcceptSocket);
        _onAcceptHandler.Invoke(args.AcceptSocket);
    }
    ... 생략

외부에서 Session객체 만들기

원래 안에서 Session객체를 만들었었는데 이 부분을 외부에서 만들도록 할 것이다.

 

 

void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    if(args.SocketError == SocketError.Success)
    {
        GameSession session = new GameSession();
        session.Start(args.AcceptSocket);
        Session.OnConnected(args.AcceptSocket.RemoteEndPoint);
     }
     ... 생략

외부에서 객체를 만들게 되면 다양한 장점을 가지게 되며 생성 방식을 제어할 수 있고 변경 가능하게 하며 객체를 여러 곳에서 재사용할 수 있게 한다.

// 매개 변수가 없고 Session타입의 객체를 반환하는 델리게이트 형식이다.
Func<Session> _sessionFactory;
public void Init(IPEndPoint endPoint,Func<Session> sessionFactory)
{
     _sessionFactory += sessionFactory;
}

 void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    if(args.SocketError == SocketError.Success)
    {
        Session session = _sessionFactory.Invoke();
        session.Start(args.AcceptSocket);
        session.OnConnected(args.AcceptSocket.RemoteEndPoint);
    }
    ... 생략

_listener.Init(endPoint,()=> { return new GameSession(); });

()=> { return new GameSession(); } 이 부분을 통해 람다식을 사용하여 팩토리 메서드를 정의 한 것을 확인할 수 있다.

매개변수가 없고 GameSession객체를 반환하는 메서드이다.


최종 코드

public override void OnConnected(EndPoint endPoint)
{
    Console.WriteLine($"OnConnected : {endPoint}");
    byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to piggy Server");
    Send(sendBuff);

    Thread.Sleep(1000);

    Disconnect();
}

public override void OnDisconnected(EndPoint endPoint)
{
    Console.WriteLine($"OnDisconnected : {endPoint}");
}

public override void OnReceive(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($"Transfoerred bytes : {numofBytes}");
}

 

 

 

 

 

 

 

 

 

참고 :  본 내용은 MMORPG  PART4 강의를 수강하여 작성하였습니다.

https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4

 

 

반응형

댓글