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

Justin·2022년 6월 13일
0

서버공부

목록 보기
21/45

✅ 지난시간

Receive 하는 부분을 새로운 Session Class로 구분하여 코드를 정리하고, listener 처리와 동일하게 SocketAsyncEventArgs를 사용해 비동기 처리를 제작하였다. 오늘은 이어서 Send 부분을 처리할 것이다.

🔲 Send 비동기 처리

Receive는 사용 시점이 정해져있지만, Send의 경우는 언제 사용될지, buffer의 byte가 얼마 일지 알수가 없어 다른 구조로 비동기 처리를 해야했다.

Send를 동작 시킬 큰 흐름은 Send -> RegisterSend -> OnSendCompleted 로 지난 번과 유사하다.

하지만 Start에서 Asyn 소켓을 생성했던 Receive와는 다르게 여기서는 이벤트만 연결해줄 것이다.

또한 buff 크기 설정의 이유로 SocketAsyncEventArgs를 처음에만 생성하고 돌려쓰는 방식은 불가능하여 멤버 변수로 선언하여 사용한다.

        SocketAsyncEventArgs _sendArgs = 
        new SocketAsyncEventArgs();

🎈 Queue를 통해 하나씩 처리하기

Queue란?
간단하게 선입선출의 개념이라고 생각하면 된다. 먼저 저장된 애는 먼저 뽑아서 사용하여야 한다. 비슷한 개념으로 Stack(나중에들어온게 먼저나간다)이 있다.

❔ 왜 Queue 형식을 사용할까?

_sendArgs를 여러개 생성해놓거나, 할 때마다 생성하는 구조가 아닌 멤버변수로 선언하여 하나씩 돌아가며 사용해야 하니 대기 하고 있는 작업들을 저장해놓기 위해 Queue를 사용한다.

🚝 Send() 코드 작성

// 버퍼의 사이즈에 Send 할 때 값이 달라지기에 byte[]사용
Queue<byte[]> _sendQueue = new Queue<byte[]>();
// lock도 걸어서 사용할 것이니 미리 추가해두자
object _lock = new object();
// 한 번에 실행되지 않게 하기 위한 boll 값 추가
bool _pending = false;

_sendQueue.Enqueue() 하여 우선 queue에 담아준다.

Register라는 곳에서 sendAsyn에 _sendArgs를 하나씩 넣어 동작 시키기 위해서는 한 번에 여러개가 들어가면 안되기에 lock 구조 안에서 실행시킨다.

마지막으로 _pending을 처리해주어, 이미 실행 중이라 대기중일 때 일감이 들어온다면 Queue만 들어가도록 해준다.

📶 Register()

void RegisterSend()
        {
            _pending = true; // 들어오자마자 막기
            byte[] buff = _sendQueue.Dequeue();

            _sendArgs.SetBuffer(buff, 0, buff.Length);

            // args가 true되어 콜백으로 OnSend를 부르기 전에 Send()에서 lock을 뚫고,
            // EnQueue 해준 애는 어디선가 누군가가 처리는 해주어야한다.
            bool pending = _socket.SendAsync(_sendArgs);
            if (pending == false)
            {
                OnSendCompleted(null, _sendArgs);
            }
        }

위에서 넣어준 일감을 Dequeue로 뽑아 _sendArgs.SetBuffer()를 통해 사이즈를 지정해준 뒤에 소켓에 담고 다음 작업으로 날린다.

이 부분 역시 Recev와 동일하게 여기서 _socket.SendAsync(_sendArgs)이 뱉는 값이 true라면 알아서 콜백을 해주고, 만약에 너무 빨라서 true인데도 pending은 false인 상황이라면 스스로 실행시켜준다.

⭕ OnSendCompleted()

해당 영역 역시 콜백을 통해서도 들어올 수 있고, Register에서도 pending이 false인 상황에서도 들어오기에 lock으로 독립적인 구역을 만들어준다.

우선 먼저 Queue에 남은 게 있는지 체크를 하고 남아있다면 다시 buffer 설정 부터 일감 처리를 시키고, 아니라면 pending을 false 시켜 다시 대기 상태로 돌린다.

💨 테스트

그후 실행시켜보면 잘 작동하는 걸 볼 수 있다. 다만, 아직은 너무 케이스가 작기도 하니 나중에 스트레스 테스트를 하며 찾아내볼 수 있을 것이다.

또한 처리 과정 중 _socket.SendAsync(_sendArgs)를 계속 호출하는 방법은 비효율적이기에 다음 시간에 구조를 개선할 것이다.(어떤 것이 비효율인지는 아직 모르겠다.)

📝 간단 정리

그동안 배운 개념들을 사용하여 비동기 상태로 변경해주었다. 개념만 알고 잘 사용하지 않던 Queue 같은 것도 이렇게 활용해보니 좋다. 나라면 아마 List에 담아서 사용했을 것 같다.

  • SocketAsyncEventArgs 멤버변수로 선언, Session - Start() 함수에서는 이벤트 연결만 해주기

  • Send() 함수 생성
    1) Queue를 통해 한 번에 몰리지 않게 조정
    2) lock 구조로 멀티 쓰레드 환경조성

  • RegisterSend() 함수 생성
    1) DeQueue 방식으로 buff 값 넣기

  • OnSendCompleted() 함수 생성
    1) lock 구조로 Queue에 남은 게 있다면 다시 Register를 동작 시켜 처리하기

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

0개의 댓글