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

네트워크프로그래밍 - C# Non - blocking을 사용한 Server 소켓프로그래밍의 Connector

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

원래 더미 클라이언트 코드에서 Connect하는 부분을 Blocking함수를 이용했었다.

socket.Connect(endPoint);

 

하지만 최대한 블로킹 함수를 사용하는 것을 지양해야하기 때문에 non-blocking하는 것으로 바꿀것이다.

 

서버는 보통 대기 상태로 손님을 기다리는데 왜 연결하는게 필요할까?

1. 서버를 메인 용도지만 connect,receive,send는 공용으로 사용하면 좋으니까

2. 서버를 하나로 만들것인지 분산해서 만들것인지 분할해서 만드는 경우가 있다. 메인 서버가 있지만 다른 애들이랑 통신하기 위해서 서버끼리 통신하기 위해 필요하다.


Conncector 구현하기

1. TCP 소켓 연결을 수행하는 Connect메서드

public void Connect(EndPoint endPoint)
{
    Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
    args.Completed += OnConnectCompleted;
    
    args.RemoteEndPoint = endPoint;
    args.UserToken = socket;

    RegisterConnect(args);
}

01 args.Completed

Completed이벤트에 OnConnectCompleted 메서드를 이벤트 핸들러로 등록한다. 그리고 소켓 연결 작업이 완료 시점에 해당 메서드가 호출되도록한다.


02 args.RemoteEndPoint

endPoint에는 서버의 IP주소와 포트 번호를 포함하고 있다. 그래서 RemoteEndPoint을 사용하여 연결할 서버의 주소와 포트 번호를 설정한다.


03 args.UserToken

사용자가 임의로 지정할 수 있는 사용자 정의 토큰이다. 비동기 작업의 상태를 추적하거나 추가 정보를 전달하는데 사용될 수 있다. 보통 SokcetAsyncEventArgs객체와 관련된 사용자 정의 데이터를 저장하는데 사용된다.

args.UserToken = socket;

소켓 연결 작업이 완료되면 해당 객체에서 UserToken속성을 통해 socket객체에 접근할 수 있다.


2. 비동기 소켓 connect 작업을 등록하는 RegisterConnect

void RegisterConnect(SocketAsyncEventArgs args)
{
    Socket socket = args.UserToken as Socket;
    if (socket == null)
        return;

    bool pending = socket.ConnectAsync(args);
    if (pending == false)
        OnConnectCompleted(null, args);
}

01 args.UserToken as Socket

UserToken에서 저장된 사용자 정의 데이터를 가져오고 Socket객체인지 확인하기 위해 형변환을 수행한다.

형변환에 성공하였으면 socket이 할당되고 아니면 null이 할당된다.


02 socket.ConnectAsync(args)

ConnectAsync을 사용하여 비동기 소켓 연결 작업을 시작한다. 만약 작업이 즉시 완료된 경우 false를 반환하고 연결 작업이 완료되지 않은 경우 true를 반환한다.


3. Session 설정하기

Func<Session> _sessionFactory;
public void Connect(EndPoint endPoint, Func<Session> sessionFactory)
{
    Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    _sessionFactory = sessionFactory;
    ... 생략
}
void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
{
    if(args.SocketError == SocketError.Success)
    {
        Session session = _sessionFactory.Invoke();
        session.Start(args.ConnectSocket);
        session.OnConnected(args.RemoteEndPoint);
    }
    else
    {
        Console.WriteLine($"OnConnectCompletedFail : {args.SocketError}");
    }
}

01 Func<Session> _sessionFactory;

세션을 생성하는 델리게이트를 저장하는 변수이다. 동적으로 생성하기 위해 사용된다.


02 _sessionFactory = sessionFactory;

외부에서 전달된 세션을 _sessionFactory에 저장한다.


03 _sessionFactory.Invoke()

Invoke를 통하여 _sessionFactory에 할당된 델리게이트를 실행하여 세션이 생성한다. 그 결과 세션 객체를 반환한다.


04 args.ConnectSocket

비동기 소켓 연결 작업이 완료된 후에 연결되어있는 소켓을 가져온다.

public Socket ConnectSocket { get; }

05 session.OnConnected(args.RemoteEndPoint)

세선 객체의 OnConnected메서드를 호출하여 연결 작업이 완료되고 원격 EndPoint 정보를 전달하여 해당 세션의 초기화나 이벤트 처리 로직을 수행한다.

원래 있던 이벤트 처리 로직

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

우리가 작업하고 있던 서버 코어 같은 경우 서버 용으로 사용하고 있었고 클라이언트 역할은 더미클라이언트가 하고 있었다. 그러니까 생긴 문제는 Connector,Listener,Session을 클라이언트에 사용하지 못한다.

나중에는 DummyClient,Sever,ServerCore동시에 사용하고 ServerCore는 라이브러리로만 사용될 예정이다.


4. ServerCore 라이브러리 기능 설정하기

01 출력형식 - 클래스 라이브러리


02 시작 프로젝트로 설정


03 시작 버튼

간접적으로 실행시켜야 됨을 알 수 있다.


Sever와 DummyClient에서 Server참조하게 만들기

01  Server 우클릭 - 추가 - 프로젝트 참조


02 DummyClient우클릭 - 추가 - 프로젝트 참조


03 Servercore에 있던 코드 Server에 옮기기

namespace ServerCore
{
    class GameSession : Session
    {
        // 여기 안에 있는 모든 부분들을
        // Server Promgram.cs에 옮긴다.
    }
}

옮긴 후 Server 발생된 오류 고치기

namespace Server
{
    class GameSession : Session
    {
        
        // 코드 옮기기
     }
}

01 using ServerCore추가하기

using ServerCore;
namespace Server
{
    public class GameSession : Session
    {
       ... 생략
    }
}

02 Session보호 수준 public으로 고치기 

public abstract class Session

03 Listener 보호 수준 public으로 고치기

public class Listener

04 Connector 보호 수준 public으로 고치기

public class Connector

04 ServerCore Program.cs 삭제하기


05 속성 변경

이제는 DummyClient와 Server로 설


5. Connector를 DummyClinet에 연결 시키기

01 클라이언트와 서버간의 작업 수행

public class GameSession : Session
{
    // 클라이언트 측에서 서버로 초기 메시지를 보낸다.
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected : {endPoint}");
        // 보낸다
        for (int i = 0; i < 5; i++)
        {
            byte[] sendBuff = Encoding.UTF8.GetBytes($"HelloWorld! {i}");
            Send(sendBuff);
        }
    }

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

02 Connector인스턴스 생성 

Connector는 서버와의 연결을 담당한다.

Connector connector = new Connector();

03 서버에 연결 요청 및 GameSession을 생성하는 델리게이트 전달

Connect메서드를 호출하여 서버에 연결한다. 이때 연결할 서버의 endPoint 정보와 new GameSession을 반환한다.

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

결과 확인

 

 

 

 

 

 

 

 

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

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

 

 

반응형

댓글