[게임 서버] 3.7 Overlapped I/O 혹은 비동기 I/O

게시:     수정

카테고리:

태그:

📦 3. 소켓 프로그래밍

👉🏻 항목 7: Overlapped I/O 혹은 비동기 I/O

📊 논블록 소켓의 장단점

장점:

  • 스레드 블로킹이 없어 중도 취소 같은 통제 가능
  • 스레드 개수가 1개이거나, 소켓을 여러 개 다룰 수 있다
    • 스레드 개수가 적으므로 연산량이 낭비되지 않는다
    • 호출 스택 메모리도 낭비되지 않는다

단점:

  • 소켓 I/O 함수가 would block을 리턴하면 재시도 호출 낭비 발생
  • 소켓 I/O 함수를 호출할 때 입력하는 데이터 블록에 대한 복사 연산 발생
  • send()/receive() 함수는 재시도 호출하는 API가 일관되지 않다

🔄 TCP send()/receive() 처리

send():

  • 송신 버퍼에 1바이트라도 비어있다면 I/O 가능
    • would block이 발생하지 않는다
    • 예로, 보내려는 데이터는 5바이트인데 1바이트만 소켓 송신 버퍼에 채워져 리턴되는 경우

receive():

  • 수신 버퍼에 1바이트라도 있다면 I/O 가능

📮 UDP send() 처리

List<Socket> sockets;

void NonBlockSocketOperation() {
	while(true) {
		select(sockets, 100ms);

		foreach(s in sockets) {
			(result, length) = s.sendto(dest, data);
			if(length >= 0) { ... } // 송신 성공
			else if(result != EWOULDBLOCK) { ... } // 소켓 오류 처리 진행
			else { ... } // 여전히 would block
		}
	}
}

UDP 송신 특징:

  • 송신 버퍼에 1바이트라도 비어있다면 I/O 가능
  • 보내려는 데이터 5바이트, 빈 공간 1바이트인 경우 would block 발생
    • TCP와 달리 UDP는 일부만 보낼 수 없다!

메모리 복사 문제:

  • 소켓 I/O 함수를 호출할 때 입력하는 데이터 블록에 대한 복사 연산 발생
    • 이전에 나왔던 단점이었다
    • 사용자 프로세스에서의 데이터 블록을 소켓 버퍼로 옮길 때 메모리 복사 연산이 발생한다
    • RAM은 매우 느리기에 복사 연산을 무시할 수 없다

⚡ Overlapped/Asynchronous I/O

논블록 소켓의 경우:

  1. 소켓 I/O 가능 대기
  2. 소켓 논블록 액세스
  3. would block 발생 시 pass, 아닐 시 실행 결과 리턴 값 처리

Overlapped I/O의 경우:

  1. 소켓에 대해 Overlapped 액세스 걸기
  2. Overlapped 액세스 성공 확인 후, 성공 시 결과 처리

📊 Overlapped I/O 장단점

장점 단점
소켓 I/O 함수 호출 후 would block 값인 경우 재시도 호출 낭비가 없다 완료 전까지 데이터 블록을 훼손하면 안 된다
데이터 블록에 대한 복사 연산이 없다 윈도 플랫폼에서만 제공한다
send, receive, connect, accept 함수 호출 시 결과는 한 번만 오기에 깔끔하다 accept, connect 함수 계열 초기화가 복잡하다
I/O completion port와 조합하면 최고 성능의 서버를 개발할 수 있다  

💻 Overlapped I/O 구현 예시

void OverlappedSocketOperation() {
	// 1. 구조체 변수
	var overlappedSendStatus;
	// 2
	(result, length) = s.OverlappedSend(
		data,
		overlappedSendStatus
	);

	if(length > 0) { ... } // 3. 송신 성공
	else if(result == WSA_IO_PENDING) { // 4
		while(true) {
			// 5
			(result, length) = GetOverlappedResult(
				s, overlappedSendStatus
			);
			if(length > 0) { ... } // 3. 송신 성공
			else { ... } // I/O 송신 진행중
		}
	}
}

동작 과정:

  • 1번: Overlapped I/O를 걸 때 진행 상황을 보관할 구조체 선언
  • 2번: 블로킹 소켓 사용하며, Overlapped I/O 전용 함수 호출 (즉시 리턴됨)
  • 3번: 즉시 성공 시 OK 리턴
  • 4번: 그렇지 않다면 I/O pending (완료 대기 중) 리턴
  • 5번: Overlapped 완료 여부는 GetOverlappedResult 함수를 통해 확인
    • ‘완료’ 결과 나오면 나머지 처리 진행
  • Overlapped I/O의 수신자는 data 객체에 수신된 데이터가 채워져 있고 이를 액세스

⚠️ 주의사항

데이터 블록 관리:

  • 소켓 API에 인자로 넘긴 데이터 블록(data, overlappedSendStatus)을 제거/변경하면 안 된다
  • Overlapped I/O를 중첩시키려면 데이터 블록이 달라야 함

버퍼 사용:

  • Overlapped I/O 전용 송수신 함수는 데이터 블록 자체를 버퍼로 사용한다

성능 개선:

  • 현재는 송신 상태를 확인할 때 while문을 돌지만, 이는 나중에 해결할 것이다

🎯 디자인 패턴

리액터 패턴 vs 프리액터 패턴:

  • 논블록 소켓: 리액터 패턴
  • Overlapped I/O: 프리액터 패턴

🧐 정리

구분 논블록 소켓 Overlapped I/O
재시도 호출 필요 (would block 시) 불필요
메모리 복사 발생 발생 안 함
플랫폼 크로스 플랫폼 Windows 전용
데이터 블록 관리 자유로움 완료 전까지 보존 필요
결과 확인 재시도 필요 한 번만 확인
디자인 패턴 리액터 패턴 프리액터 패턴

핵심 포인트:

  • Overlapped I/O는 논블록 소켓의 성능 문제를 해결한 고성능 I/O 방식이다
  • 메모리 복사 없이 데이터 블록 자체를 버퍼로 사용하여 효율적이다
  • I/O completion port와 조합하면 최고 성능의 서버를 구현할 수 있다
  • Windows 전용이며, 데이터 블록 관리에 주의가 필요하다

GameServer 카테고리 내 다른 글 보러가기

댓글남기기