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

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

by 코딩하는 돼징 2023. 6. 21.
반응형

blocking방식 이용한 Server코드

 

네트워크 프로그래밍 - C# 간단한 Server 소켓프로그래밍 구현

소켓프로그래밍 과정 알아보러가기 네트워크프로그래밍 - 소켓 프로그래밍(클라이언트 관점, 서버관점) 과정 손님 관점 클라이언트 관점 핸드폰 준비 소켓(클라이언트와 서번 간의 데이터 송수

code-piggy.tistory.com


blocking방식에서 문지기와 관련된 코드 깔끔하게 정리하기

원래 코드

Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
try
{
    listenSocket.Bind(endPoint);
    listenSocket.Listen(10);

    while (true)
    {
        Console.WriteLine("Listening...");
        Socket clientSocket = listenSocket.Accept();
}

고친 코드

static Listener _listener = new Listener();
public void Init(IPEndPoint endPoint)
{
    _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    // 문지기 교육
    _listenSocket.Bind(endPoint);

    // 영업 시작
    // backing : 최대 대기 수
    _listenSocket.Listen(10);
}

public Socket Accept()
{
    return _listenSocket.Accept();
}

Socket _listenSocket;
try
{
    _listener.Init(endPoint);
    while (true)
    {
        Console.WriteLine("Listening...");

        // 손님을 입장시킨다.
        Socket clientSocket = _listener.Accept();
    }
}

이렇게 코드를 고치면 코드의 가독성이 향상되고 재사용성 또한 확장된다.

_listenSocket을 멤버 변수로 사용함으로써 초기화와 수신 대기 작입이 한곳에 처리 된다. 또한 Accept메서드를 외부에서 호출할 수 있게 됨에 따라 다른 부분에서 _listernSocket의 수신된 데이터를 활용할 수 있다.


비동기(Non - blocking) 사용

( 맨 밑에 정리용으로 간단한 낚시예시로 정리해놓았어요 :) ) 

1. RegisterAccept

_listenOkcet으로부터 클라이언트의 연결 요청을 받기 위해 AcceptAsync을 호출하고 연결 요청이 완료되었을때에는 콜백 메서드인 OnAcceptCompleted가 호출된다.

void RegisterAccept(SocketAsyncEventArgs args)
{
    bool pending = _listenSocket.AcceptAsync(args);
    if (pending == false)
        OnAceeptCompleted();
 }

void OnAceeptCompleted()
{
}

pending == false인 경우

AcceptAsync메서드가 즉시 클라이언트 접속 요청이 와서 수락되었다면 pending이 false가 된다. 이는 즉시 OnAceeptCompleted를 호출한다.

pending == true인 경우

클라이언트 접속 요청이 아직 안온 것이므로 AcceptAysnc메서드를 통해 요청이 올때까지 기다린다. 그 후 OnAceeptCompleted메서드는 AcceptAysnc의 콜백으로 호출되어 연결 요청 완료처리를 수행한다.


2. 초기화 및 등록

public void Init(IPEndPoint endPoint)
{
    SocketAsyncEventArgs args = new SocketAsyncEventArgs();

    args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
}

void RegisterAccept(SocketAsyncEventArgs args)
{
    bool pending = _listenSocket.AcceptAsync(args);

    if (pending == false)
        OnAceeptCompleted(null,args);
}

void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{

 }

01 SocketAsyncEventArgs 객체 만들기

SocketAsyncEventArgs 의 객체에는 비동기 소켓 작업에 대한 이벤트와 데이터를 저장하는데 사용된다.

SocketAsyncEventArgs args = new SocketAsyncEventArgs();

02 args.Comleted이벤트에 대한 핸들러 등록

이 핸들러는 OnAccept메서드를 이벤트 핸들러로 사용하도록 한다. 이를 통해 클라이언트에 연결 요청이 들어온 후  accept한 경우 이 이벤트가 실행되도록 설정하는 역할이다.

args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);

03 최초 등록은 우리가 한번 해준다.

SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAceeptCompleted);
RegisterAccept(args);

이상태에서 클라이언트의 connect 요청이 왔다고 하면 콜백 방식으로 onAcceptCompleted가 호출된다


3. OnAceeptCompleted

AcceptAsync메서드의 비동기 작업이 완료되었을때 호출되는 이벤트 핸들러이다.

매개 변수

sender : 이벤트를 발생시킨 객체

args : 비동기 작업에 대한 정보와 결과를 포함하는 SocketAsyncEventArgs객체

void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    if(args.SocketError == SocketError.Success)
    {
        //todo
        _onAcceptHandler.Invoke(args.AcceptSocket);
    }
    else
        Console.WriteLine(args.SocketError.ToString());
    // 다음 연결을 요청
    RegisterAccept(args);
}

args.SocketError를 확인하여 비동기 작업의 완료여부를 확인한다.

작업이 성공적으로 완료된 경우

args.SocketError가 SocketError.Success인 경우 _onAcceptHandler.Invoke(args.AcceptSocket)를 호출하여 클라이언트와의 통신을 위한 작업이 수행된다.


_onAcceptHandler

_onAcceptHandler는 클라이언트 연결을 처리하기 위한 핸들러이다.

Action<Socket> _onAcceptHandler;
 public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
     _onAcceptHandler += onAcceptHandler;
}

4. 재사용하는 하는 경우

SocketAsyncEventArgs는 재사용 가능한 객체이다. Accept작업이 완료되고 클라이언트와의 통신이 종료된 후에 해당 객체를 다시 사용하기 위해서는 이전에 연결된 클라이언트 소켓에 대한 참조를 제거해야한다.

void RegisterAccept(SocketAsyncEventArgs args)
{
    // 재사용할을 위한 참조 제거
    args.AcceptSocket = null;
    bool pending = _listenSocket.AcceptAsync(args);

    if (pending == false)
        OnAceeptCompleted(null,args);
}

class Program
{
    static Listener _listener = new Listener();
    static void onAcceptHandler(Socket clientSocket)
    {
        try
        {
			// 생략
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
    static void Main(string[] args)
    {
        // 생략
        _listener.Init(endPoint,onAcceptHandler);
        Console.WriteLine("Listening...");
        while (true)
        {
            ;
        }
    }
}

낚시로 간단 예시

01 낚시대를 처음 던진다.

public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
    RegisterAccept(args);
}

02 낚시대를 던지자마자 생선이 물음( pending  ==  false )

if (pending == false)
    OnAcceptCompleted(null,args);

03 낚시대를 던지고 입질을 기다림 ( pending ==  true )

만약 물고기가 잡히면 작업이 완료되었다는 뜻이니까 args.Completed가 호출된다.

args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);

04 낚시대의 고기를 뺌

if(args.SocketError == SocketError.Success)
{
    _onAcceptHandler.Invoke(args.AcceptSocket);
}
else
    Console.WriteLine(args.SocketError.ToString());

05 낚시대를 다시 던짐

void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
    RegisterAccept(args)
}

non - blocking code에 대해 더 알아보기

 

네트워크 프로그래밍 - non-blocking code에 대해 더 알아보기

blocking 및 non-blocing에 대해 알아보러 가기 네트워크프로그래밍 - blocking(send등), non-blocking(BeiginSend,SocketAsyncEventArgs등) 입출력 작업은 블로킹(blocking) 또는 논블로킹(Non-blocking)방식으로 수행된다. 1.

code-piggy.tistory.com


 

 

 

 

 

 

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

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

반응형

댓글