IOCP가 뭐야? (InputOutputCompletionPort)
Windows에서 고성능 네트워크/파일 비동기 처리를 위해 만든 시스템 큐
비동기 작업이 끝나면 os가 큐에 완료했다고 넣어주고, 우리는 그걸 꺼내쓰는 방식...
게임서버의 핵심은 IOCP.. iocp를 쓰겠다고 하면, 커널에 iocp 객체를 생성, 걔를 통해서 io를 진행!
WSAOVERLAPPED
└ Windows에서 비동기 io작업을 위한 구조체임...
작업의 상태와 완료 정보를 담는 상자...
그냥 recv, send
└ 이 함수들을 호출 시 작업이 끝날 때 까지 코드가 멈춘다..
WSARecv, WSASend
└ 작업만 시작해주고, 바로 리턴. 나중에 작업이 완료되면 그 사실을 IOCP나 이벤트를 통해 통지받음.
이때 이 작업 진행 상태 및 결과 저장을 WSAOVERLLAPED 구조체가 하는 것임.
- 실제 데이터의 송/수신은 OS가 백그라운드에서 처리함.
- 그러니까... WSARecv의 경우, 받을 준비를 하는 것임.(진짜로 그때 받는게 아닌)
WSASend의 경우도 똑같음.. 서버->클라 의 경우, 클라에게 데이터를 보내려고 WSASend를 호출해놓음.
OS가... '어 보낼게, 보내고나서 알려줄게~' 하고 일단 리턴하는거임.
IOCP에서는.. GetQueuedCompletionStatus를 통해 작업완료통보를 받아야 실제 데이터가 도착했는지 확인이 가능함...
WSARecv() - 비동기로 데이터를 받는 함수.
└ WSARecv(사용할 소켓 핸들,
받을 버퍼 배열,
버퍼 개수,
NULL(Overlapped면 Null로..),
수신 플래그(보통 0),
Overlapped구조체 주소,
완료 콜백 루틴(iocp사용하므로 NULL))
여기서는.... 이 소켓 s로부터 데이터가 오면,
이 o->_wsabuf에 데이터를 저장해줘.(작업이 완료되면 나중에 os가 그 안에 데이터를 써 넣는다..)
그리고 결과는 o->_over에..
(o의 _over는 WSAOVERLAPPED로 선언된, overlapped 구조체임. 그래서 여기에 완료 정보를 담는다고~~)
*참고로 여기서는 EXP_OVER를 통해 overlapped 구조체를 확장시켜줬다....
*
다시 돌아와서..
EXP_OVER 내 만들어진 char _buffer는 진짜 메모리 공간.(내 실제 데이터가 여기에 들어감)
WSABUF _wsabuf는 os(winsock)한테 이 buffer라는 데이터를 써주세요..! 하는 것. winsock한테 이 구조체를 넘긴다.
WSARecv를 하면.. 그 내부에 _wsabuf[0].buf = _buffer 이렇게 되어있으니까..
받을 준비를 하고.. ㄹㅇ로 받게되면 나중에
GetQueuedCompletionStatus()이 IO작업(WSARcev, WSASend등)이 완료되었을 때!!!!!
이 결과를 큐에 넣고 꺼내주는 함수. (큐란? 선입선출)
근데.... 왜 큐에 넣지?
WSARecv/WSASend는.. 바로 리턴되고, 나중에 작업이 완료되면 os가 알려준다..
그런데 작업이 하나일리 없음. 여러개일 수 있다!!! -> 그러므로 큐에 넣어서 순서대로 진행하는 것...
os가 완료된작업들을.. 큐에 넣어준다... 그럼 우리는 그 큐에서 하나씩 꺼내서 처리해주면 되는 것!!!!
GetQueuedCompletionStatus(iocp로 만든 큐 핸들,
이 작업에서 처리된 바이트 수,
해당 소켓또는 세션과 연결된 고유의 키값,
완료된 작업의 OVERLAPPED 구조체 포인터,
기다릴 시간)
여기서는..... io_size에 몇 바이트 수신(송신) 완료 됐는지,
key는.. 언젠가 CreateIoCompletionPort에서 정해준 key값
o는... 어떤 작업이 끝났는지 알려주는 포인터
(후에 EXP_OVER* 로 캐스팅해서 사용)
정리하자면... os가.. 백그라운드에서 io작업을 처리/완료 하면..?!
이 GetQueuedCompletionStatus()가 완료된 걸 큐에서 꺼내옴 (얘가 큐에 넣는게 아니다. 큐에 넣는건 os가 함.. 그걸 꺼내오는 역할을 하는 것임!!!!!)
큐에서 꺼내오는 애가 WSAOVERLAPPED* o도 같이 꺼내와줘서..
o가 send_over인지, recv_over인지 확인이 된다...(안에 bool함수 있으니까)
비유하자면..
나: "야, 이 주문(WSARecv) 처리해줘~"
알바생(OS): "넹! 다 되면 박스(큐)에 결과 넣어둘게요..."
↓
알바생이 박스에 작업 완료 정보 넣음
↓
나: GetQueuedCompletionStatus() 호출해서 박스 열어봄 (무슨 주문?(정보?))
(여기서는 recv라는걸 확인 가능)
서버는.. 항상 '받을 준비'를 한다..
보낼 준비는? 보낼일이 있을 때 즉시 실행하면 됨..
왜냐면 클라가 언제 보낼지 모르니까, 항상 대기를 해야함.
그러니까 WSARecv로 항상 받을 준비(예약)을 걸어두는 것임..
서버는 보낼 타이밍을 스스로 정한다..
귀찮으니까 사진으로 대체... (현재 이건 일대일 통신임)
while문 안에서.. 매번 GetQueuedCompletionStatus 를 통해 방금 완료된 작업이 무엇인지 확인 가능!!!
만약 방금 완료 된 것이, WSARecv라면.. 해당 ex_over의 _is_recv가 true니까.. recv인 것을 확인 할 수 있음
그러면 받은걸 다시 보내줘야하니까.. 다시 WSASend를 해줘야한다...
(여기서는 에코를 구현했으니까 받은걸 복붙해서 클라한테 보내준다)
또한, 만약에 방금 완료 된 것이, WSASend라면.. 해당 ex_over의 _is_recv가 false니까..
방금 WSASend를 했다는 것을 알 수 있음. 그럼 다시 받을 준비를 해준다.
받고 보내고 받고 보내고의 반복...WSASend의 c_socket이 클라와 이미 연결되어있어서.. 어디로 보낼지에 대한 정보 자체가 c_socket안에 존재함!
이미 도착지는 소켓 연결 시점에 결정이 된다!!!!
CreateIoCompletionPort()는.. IOCP객체를 생성, 소켓을 IOCP에 연결해준다.
CreateIoCompletionPort(소켓 또는 핸들 -> 처음에는 INVALID_HANDLE_VALUE를 넣어주면 됨, 그 후에 c_socket같은 핸들,
처음엔 NULL(그 후엔 만들었던 iocp객체),
소켓을 식별하기 위한 키, -> 이걸 통해 무슨 클라인지(어떤 소켓?) 식별이 가능
큐에 연결된 최대 동시 스레드 수)
'서버' 카테고리의 다른 글
다중서버정리(iocp) (0) | 2025.04.06 |
---|---|
서버 2w1(네트워크프로그래밍) (0) | 2025.03.18 |
서버1w2 (0) | 2025.03.18 |
서버1w1 (0) | 2025.03.14 |