개인공부) 서버실습(21) - 서버 코드 구분, 비동기 처리(2)

Justin·2022년 6월 13일
0

서버공부

목록 보기
20/45

✅ 지난시간

코드를 조금 더 효율적이게 구분하고, 비동기 처리가 될 수 있게 구현을 해보았다. 과정중에서 이벤트 기안을 동작하는 SocketAsyncEventArgs 를 사용하여 아주 편하게 코드 구성을 할 수 있었다.

❓ 지난 시간의 의문점

  • 유저들이 많이 몰릴 경우 처리

    -> 해당 부분을 여러개 생성하여 관리하도록 요청하면 된다. 각 독립적으로 행동하기에 문제는 발생하지 않는다.

  • 메인 쓰레드는 잡혀있는데, Accept는 어떻게 실행되었을 까?

    -> 테스트를 할 때에 서버 코어 쪽에서 계속 대기 상태를 위해 while로 잡혀있는데, listenr Class에서 Accept는 ThreadPool에서 가져와서 사용한 것 이다. 그렇기에 Accept와 같이 따로 동작할 수 있는 부분은 멀티 쓰레드의 환경 고려해서 제작해야한다.

👾 Receive

이번에는 Receive 영역을 따로 Session 구분하여 처리를 할 것이다. Send는 비교적 복잡하기에 다음 시간에 진행 예정

  • 구분하기 위한 새로운 Class 생성
  • 받아와서 사용할 Socket 선언

🛫 Start()

public void Start(Socket socket)
        {
        	// listenr의 Init과 동일
            _socket = socket;
            SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
            recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);

            recvArgs.SetBuffer(new byte[1024], 0, 1024);

            RegisterRecv(recvArgs);
        }

SocketAsyncEventArgs를 생성하고, 이벤트 방식을 통해 완료가 되었을 때 동작할 OnRecvCompleted를 담아주는 것 까지는 전반적으로는 같으나

여기서는 시작 시에 receive할 Buff를 따로 세팅 해준다.

즉, 지난 시간에 위와 같이 처리를 했던 부분을 아래 처럼 간단한 코드 한 줄로 처리 할 수 가 있다.

Buffer의 크기, 시작 포인트, 개수 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024)
버퍼를 크게 만들고 나눠서 사용하기도 하기에 버퍼의 시작 포인트는 필요하다.

📌 Regiseter()

Start 시에 담아주며 해당 함수 호출이 되고, 검사를 통해 다음으로 넘겨 주는 방식은 지난 시간과 완전히 똑같다.

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

📍 OnRecvCompleted()

위에서 args.Completed 에 등록한 함수로 args가 성공처리 되면 동작하기도 하나, 예외에 경우에도 동작할 수 있다.

if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
   // todo
   try
   {
   // 위에서 설정한 값을 불러올 수 있음
		string recvDa = Encoding.UTF8.GetString(args.Buffer,
args.Offset, args.BytesTransferred);

		Console.WriteLine($"[From Client: ] {recvDa}");
    	RegisterRecv(args);
	}
    catch (Exception e)
    {
        Console.WriteLine($"OnRecvCompleted Fail {e}");
    }
}
            else
            {
                // todo
            }

여기에서 다른 포인트는 Receiv할 buffer가 연결 종료 등의 이유로 0이 올 때도 있기에 예외처리가 추가 된다.

또한 인코딩 과정에서는 args 에 최초에 등록 된 값들을 사용한다.

  • args.Buffer = 크기
  • args.Offset = 시작 위치
  • args.BytesTransferred = 개수
string recvDa = Encoding.UTF8.GetString(args.Buffer,
args.Offset, args.BytesTransferred);

📧 Send() 간단 처리

다음 시간에 다룰 것이기에 일단 간단하게 처리만 해둔다.

  // 샌드는 리시브와 다르게 조금 더 복잡할 수 있음
        public void Send(byte[] sendbuff)
        {
            _socket.Send(sendbuff);
        }

😴 Disconnect()

연결 종료를 하던 코드또한 Receive, Send하던 소켓을 종료해야하기에 해당 Class에서 함께 처리한다.

다만 이런식으로 간단하게만 처리할 경우 멀티쓰레드 환경에서 두 번 이상 Disconnect에 접속하며 오류가 발생한다. 그렇기에 멀티 쓰레드에서 배운 lock 구조를 처음 사용할 것이다.

  • 구분을 위한 flag 선언
    int _disconnected = 0;
  • 활용할 lock
    Interlock.Exchange()

매우 간단하게 두 번 처리만 안되면 되기에 Exchange를 활용해 0을 1로 변경시키고 조건 문을 사용할 것이다.
Interlock.Exchange(ref _disconnected, 1)

이렇게만 처리하면 간단하게 Disconnect는 상호배제 된 상태로 만들어 줄 수 있다.

📋 실행 함수 처리

위와 같이 Session 클래스 작업을 마친 후에는 동작하던 곳에서 해당 함수들을 작동 시키는 구조로 변경한다.

좌측이 기존, 우측이 변경한 구조이다.

🌐 마무리 테스트

다음 Ctrl+F5로 실행을 시켜보면 잘 작동하는 것을 확인할 수 있다.

profile
인디 게임을 만들며 공부하고 있습니다.

0개의 댓글