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

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

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

Listener코드 알아보러 가기

 

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

blocking방식 이용한 Server코드 네트워크 프로그래밍 - C# 간단한 Server 소켓프로그래밍 구현 소켓프로그래밍 과정 알아보러가기 네트워크프로그래밍 - 소켓 프로그래밍(클라이언트 관점, 서버관점)

code-piggy.tistory.com


non-blocking으로 바꿀 코드

static void onAcceptHandler(Socket clientSocket)
{
    try
    {
        // 받는다.
        byte[] recvBuff = new byte[1024];
        int recBytes = clientSocket.Receive(recvBuff);
        string recvData = Encoding.UTF8.GetString(recvBuff, 0, recBytes);
        Console.WriteLine($"[From Client] {recvData}");

        // 보낸다.
        byte[] sendBuff = Encoding.UTF8.GetBytes("Weclome to code-piggy Server !");
        clientSocket.Send(sendBuff);

        // 쫓아 낸다.
        clientSocket.Shutdown(SocketShutdown.Both);
        clientSocket.Close();
    }
}

받는다. 부분

1. 초기화 및 등록

public void Init(Socket socket)
{
    _socket = socket;
    SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
    recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
    
    recvArgs.SetBuffer(new byte[1024],0,1024);
    
    RegisterRecv(recvArgs);
}

01 SocketAsyncEventArgs 객체 만들기

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

SocketAsyncEventArgs args = new SocketAsyncEventArgs();

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

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

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

03 Buffer설정

public void SetBuffer(byte[] buffer, int offset, int count);

매개변수

buffer - 비동기 소켓 메서드와 함께 사용될 데이터 버퍼offset - 작업이 시작되는 위치count - 버퍼에서 보내거나 받을 최대 데이터 양


04 수신 작업 시작

SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
recvArgs.SetBuffer(new byte[1024],0,1024);
RegisterRecv(args);

 

RegisterRecv메서드를 호출함에 따라 실제 데이터 수신 작업을 시작할 수 있다.


2. RegisterRecv

ReciveAsync을 통해 비동기 수신 작업을 시작한다.

void RegisterRecv(SocketAsyncEventArgs args)
{
    bool pending = _socket.ReceiveAsync(args);
    if (pending == false)
        OnRecvCompleted(null, args);
}

pending은 작업이 즉시 완료되었는지 여부를 나타내는 변수이다.

pending == fasle 인 경우

작업이 즉시 완료되었음을 의미하므로 바로 OnRecvCompleted메서드를 호출한다.


3. OnRecvCompleted

수신 작업이 완료된 경우 호출된다.

void OnRecvCompleted(object sender, SocketAsyncEventArgs args)

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

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


01 작업이 성공적으로 완료되는 경우

if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)

BytesTransferred는 실제로 수신된 데이터의 길이를 의미한다. 

 

만약 수신된 데이터의 길이가 0보다 크고 args.SocketError가 SocketError.Success인 경우 데이터 수신이 정상적으로 이루어졌으므로 if문내의 작업을 수행한다.

예외가 발생하지 않은 경우

args.Buffer(데이터를 포함하는 바이트 배열), args.Offset(수신된 데이터의 첫번째 인덱스), args.ByteTransferred(수신된 데이터의 길이)를 사용하여 수신된 데이터를 UTF-8문자열로 변환한다.

try
{
    // TODO
    string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
    Console.WriteLine($"[From Client] {recvData}");

    RegisterRecv(args);
}

RegisterRecv(args)를 통해서 다음 데이터 수신을 등록한다.


예외가 발생한 경우

catch블록에서 해당 예외를 처리하고 에러 메시지를 출력한다.

catch(Exception e)
{
    Console.WriteLine($"OnRecvCompleted Faield {e}");
}

02 작업이 성공적으로 완료되지 않은 경우

else
{
}

보낸다. 부분

public void Send(byte[] sendBuff)
{
   _socket.Send(sendBuff);
}

쫓아낸다. 부분

public void Disconnect()
{
    _socket.Shutdown(SocketShutdown.Both);
    _socket.Close();
}

최종 코드

static Listener _listener = new Listener();
static void onAcceptHandler(Socket clientSocket)
{
    try
    {               
        Session session = new Session();
        session.Init(clientSocket);

        byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to piggy Server");
        session.Send(sendBuff);

        Thread.Sleep(1000);

        session.Disconnect();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
}

 


만약에 동시다발적으로 누군가가 Disconnect를 한다거나 같은애를 두번 Disconnect가 된다면 어떻게 될까?

session.Disconnect();
session.Disconnect();

아래와 같이 오류가 나타나는 것을 확인할 수 있다.

그러므로 무조건 Disconnect는 한번만 실행해야 된다는 것을 알 수 있다.


flag을 사용해서 중복확인

int _disconnected = 0;

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

Interlocked에 대해 알아보러가기

 

운영체제 - Interlocked(Increment,Exchange,Add메서드 등)

Interlocked 다중 쓰레드에서 공유하는 변수에 대한 원자 단위 연산을 제공 장점 RaceCondition 알아보러 가기 운영체제 - RaceCondition, Atomic RaceCondition 여러개의 쓰레드들이 공유 변수 동시 접근 시 실행

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

 

 

 

 

반응형

댓글