IOCP 서버 제작에 있어서의 유의할 점들
현재 개발중인 게임 서버의 소켓이 4년전에 제작한 비동기 이벤트 셀렉트 방식인 관계로 퍼포먼스를 향상시키고자 IOCP 네트워크 구조를 최근 제작하게 되었다. 실제 게임서버에 적용 가능할지는 좀 더 고려해 보아야 하겠지만, IOCP 서버를 만들면서 겪었던 점들을 공유하고자 한다.
시중의 책들과 공개된 소스들을 참고해서 IOCP를 구현해 보면, 항상 과부하 테스트시에 문제가 발생했다. 내가 설정한 과부하 테스트는 다음과 같은 상황이다:
1) 클라이언트 측에서 과도할 정도로 Connect를 시도한다.
2) 서버는 Accept 직후 랜덤하게 연결을 끊어버린다.
3) 클라이언트는 Connect된 직후 서버로 데이터를 전송한다.
4) 서버는 클라이언트로부터 데이터가 수신되면 바로 응답메시지를 전송한다. 이때 응답메시지를 전송할 내부 버퍼(소켓 버퍼 아님)가 모자라는 경우 연결을 끊는다. 이 처리와는 별도로 데이터 수신시 랜덤하게 연결을 끊는다.
5) 클라이언트는 서버로부터 데이터를 수신하면 바로 응답메시지를 전송한다. 이때 응답메시지를 전송할 내부 버퍼(소켓 버퍼 아님)가 모자라는 경우 연결을 끊는다. 이 처리와는 별도로 데이터 수신시 랜덤하게 연결을 끊는다.
6) 클라이언트는 연결이 끊어진 커넥션이 발생하면 그에 대응하는 Connect를 시도한다.
클라이언트는 초기에 몇천개 이상의 Connect를 시도하고 연결된 커넥션들에 대해 각각 위의 규칙대로 처리를 반복하는 상황을 만들어 테스트 해 보았는데, 여러번의 삽질끝에 발견한 문제점들은 다음과 같다.
일단, 시중에 떠도는 IOCP소스들의 대부분은 에코(Echo) 서버들이다. 이 소스들은 항상 데이터를 recv한 다음 send를 하므로 참조 카운트가 1 이상 올라가지 않지만, 게임 서버는 그렇지 않다. 걸어놓은 recv에 대한 응답이 안 온 상태에서 send를 할 수 있으므로 소켓 1개에 대해 2개의 참조 카운트가 발생할 수 있다. GetQueuedCompletionStatus가 FALSE를 리턴한 경우, 대부분의 에코서버 소스에서는 바로 소켓을 close한 다음 커넥션 객체를 삭제해 버리는데, 이것은 참조 카운트가 1이상 올라가지 않기 때문이다. 이런 경우 게임서버에서는 그 소켓에 대한 참조카운트가 2라면, 그냥 소켓을 close한 다음 나머지 작업에 대한 실패 통보가 와서 참조 카운트가 0이 되었을때 커넥션 객체를 삭제해야만 한다. 마찬가지로 WSARecv에 대한 리턴이 왔는데 전송 바이트가 0인 경우에도 무조건 객체를 지워서는 안된다.
IOCP에서 WSASend, WSARecv를 사용할 때 소켓에러 없이(IO_PENDING는 에러가 아니므로 제외) 포스팅된 소켓 연산의 결과는 반드시 각각 GetQueuedCompletionStatus에서 리턴된다. 단, 함수의 반환값은 TRUE일수도 FALSE일수도 있다.
한가지 이상한 현상을 발견했는데, close한 소켓을 WSASend나 WSARecv에 사용했을때 에러가 반환되지 않는 경우가 있다. 예를 들어, 소켓 A에 대해서 WSARecv가 포스팅된 상태에서, 소켓 A를 close했다. 이 때 GetQueue... 에서 WSARecv에 대한 성공 결과가 리턴될 수 있다(close하기 전에 성공한 결과일 수 있으므로), 이때 다시 WSARecv를 걸려고 할 때 close된 소켓이므로 WSARecv가 에러를 발생해야 정상이므로 이 시점에 커넥션을 삭제하고자 했는데, WSARecv가 에러를 발생시키지 않는 거다. 원인은 잘 모르겠지만, 아마도 네트워크 과부하 상황일때 이런 현상이 발생하는게 아닌가 추측하고 있다. 결국 결국 소켓이 닫혔다는 별도의 플래그를 만들어 직접 해결할 수 밖에 없었다. 이런 상황에서 얻은 정보를 써 보자면:
서버에서 소켓을 close하는 것이 closesocket() 호출 이전에 포스팅한 WSASend, WSARecv를 꼭 실패시키지는 않는다. 성공할 수도 있고, 실패할 수도 있다.
서버에서 소켓을 close하면 이전에 포스팅된 연산에 대한 결과는 반드시 GetQueuedCompletionStatus에서 각각 모두 리턴된다.
이런 것들을 이해하고 나니, 과부하 테스트에서도 정확하게 동작하는 IOCP 네트워크 클래스를 제작할 수 있었다.
댓글 없음:
댓글 쓰기