[게임 서버] 3.7 Overlapped I/O 혹은 비동기 I/O
카테고리: GameServer
태그: GameServer
📦 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
논블록 소켓의 경우:
- 소켓 I/O 가능 대기
- 소켓 논블록 액세스
- would block 발생 시 pass, 아닐 시 실행 결과 리턴 값 처리
Overlapped I/O의 경우:
- 소켓에 대해 Overlapped 액세스 걸기
- 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 전용이며, 데이터 블록 관리에 주의가 필요하다
댓글남기기