'Network/Network_programming'에 해당되는 글 1건

  1. 2009.03.10 IOCP

IOCP

|


처음이라 어떻게 시작해야 할 지 모르겠군요.
저는 강좌를 제가 여러분들에게 무엇을 "알려드린다"기 보다 서버 제작시의 여러가지의 것들에 대해서, 사적인 이야기 하듯이 착각하기 쉬웠던 점이나 제작시에 어려웠던 점등에 대해서, 제가 먼저 얘기하고, 리플을 보며, 더 좋은 방법을 찾아가는 식으로 진행하고 싶군요.
이곳에 고수분들이 많이 계시고, 아직 중수도 안 된 저로썬 그것이 최선의 방식이라 생각됩니다.

먼저 저희가 제작할 서버는 아주 간단한 채팅 서버 입니다.
사용자가 접속해서, 문자열을 1개 날리면, 접속한 모든 사용자에게 다시 문자열을 날려줍니다.
(접속 처리부를 이용해서, 다른 기능으로 사용하실 수 도 있겠군요...손보셔야 할 게 많겠지만^^)

먼저 저희가 만들 서버란 것에 대해서, 추상적으로 생각해 보죠.
프로그램을 실행시키면, 일단 아무도 접속해 있지 않고 서버는 접속을 기달리고 있는 상태가 되겠죠?
그러다가 1명이 접속을 시도하면, 서버에선 1개의 연결을 마련합니다.
그렇게 100명이 접속하고, 100개의 연결이 현재 준비되어 있다고 생각해 봅시다.
이중에 47번째 사람이 메세지를 날립니다. 서버는 메세지를 47번째 연결으로 부터 메세지를 하나 읽습니다. 그 메세지를 100명에게 차례로 날려줍니다. 그리고, 다음 메세지가 올것을 기달리고 있습니다.

그럼 IOCP에 들어가기에 앞서, 몇가지(멀티 스레드, 비동기 I/O)에 대해 먼저 얘기해 볼까요?^^


[멀티 스레드와 멀티 프로세서]

클라이언트랑 다른 것이 멀티쓰레드 환경이라는 것입니다.
클라이언트 제작시에는 동시에 함수가 2개가 실행된다는 생각은 안하고 짭니다만..
서버는 함수가 중복되서, 실행될 수 있다는 거죠. 그래서, 동시에 2곳에서 실행되지 말아야 할 곳은 크리티컬 섹션이라는 것으로 감쌌습니다.
(동시에 2곳에서 접근 못하게 해주죠...현재 누군가 접근해 있다면, 기달리고 있습니다.)
멀티 쓰레드란 것중에 흥미 있는 거 몇개만 생각해 볼까여?
먼저 멀티스레드를 이용하는 것엔 크게 2가지가 생각납니다.
1. UI를 위해서
- 예를들면, 워드프로그램에서 사용자가 글을 쓰고 있는 동안 짬짬이 맞춤법 검사를 할때
2. 성능의 향상을 위해서
- 일을 몇개를 중복해서, 시키면, 정말 쉴새없이 일하게 할 수 있죠...-_-;;(마치 저를 보는 듯..ㅠ_ㅠ)
  (근데, 일(Thread)가 너무 많아지면, 각 일을 하다가 다른 일로 바꾸는 데 걸리는 시간 때문에 결국 아무짓도 못하는 수가 있죠..^^..마찬가지로 서버에서 Thread가 넘 많아지면, 현재 작업 Thread를 계속 바꾸게 되서(Context Switching), 이 Context Switching에 시간을 더 잡아먹게 되죠. 배보다 배꼽이 크다고 할까요)
  Single CPU에서(단일 프로세서) Multi Thread일 필요는 없습니다.
  예를 들면, 10명(10 CPU)이 한 팀인데, 10가지 작업이 있다면, 각자 1작업이 있다면, 1 Time에 끝납니다.
  하지만, 혼자하는 작업(1 CPU)이라면, 순서대로 10개를 끝내는 것이 빠르지 동시에 10개를 시작해서, 이것하다가 조금있다가 저것하는 식으로 한다면, 더 느리겠죠?^^
  (이것하다 저것하다 하는 것을 context switching한다라고 합니다)
  대신에 가끔은 Multi Thread도 괜찮을 때가 있는데, 예를들면, 저희가 프린트 하는 작업과 청소하는 일이 있다면, 프린트 작업의 특성상 쉬는 시간이 많죠?(많은 문서를 프린트하고 있는 중간..)
그 사이에 청소를 하는 건 좋겠죠? 마찬가지로 일의 특성에 따른 문제란 건데, 아무튼 CPU가 계속 바쁘게 돌아가는 일이라면, Multi Thread는 속도의 저하를 가져올 수있죠.
서버에서 스레드를 쓰는 이유는 주로 2번때문이겠죠?
일단 크게 착각할 수 있는 부분이 왜 서버에 멀티 스레드가 필요한가 인데, 착각의 대표적인 예가 "서버가 다중 접속을 받기 때문에 멀티 쓰레드를 쓴다"는 것이죠.
그렇게 "할 수"는 있지만, 1개의 접속마다 Thread를 한개씩 돌린다면 1000명이 접속하면, 쓰레드 1000개가 돌아가야 한다는 소린데...설마 그렇게 무식하게 쓰지는 않겠죠?^^ 원칙적으로 서버에서 멀티 쓰레드를 쓰는 이유는 서버 같은 경우 CPU가 여러개인 경우가 많고, 그럴 경우 "CPU가 2개 이상일때, 효율적으로(CPU를) 부려먹기 위해서" 입니다.
(뭐 listen해야 하기 때메 어짜피 멀티 스레드로 돌긴하겠지만...쩝...그 listen하는 스레드랑 저희가 앞으로 얘기할 멀티스레드상의 sync문제는 저희가 만든 스레드들이 한 코드를 공유할 때 생기는 문제점에 대해서, 얘기하는 거 거니깐요..)

*멀티 스레드와 멀티 프로세서
그냥 단일 스레드 프로그램을 멀티 프로세서에다 돌린다고 중복되서 실행되지는 않습니다. 멀티 스레드로 만들어 줘야 멀티 스레드를 효과적으로 사용할 수 있다는 것이죠.
그리고, 단일 프로세서환경에서 멀티 스레드로 짜줘봤자 "동시에" 실행되는 건 아닙니다. 1프로세서(CPU)에서 "동시처럼" 쪼개서 실행되는 거죠^^
멀티 프로세서에다가 단일 스레드 프로그램을 짜준다면, 1프로세서(CPU)를 제외하곤 아마 쉬고 있겠죠.
글구 윈도우 자체는 멀티 스레드로 돈다는 거 잊지 마시고..

*1곳에서만 실행되어야 하는 코드의 경우
CRITICAL_SECTION이라는 것을 사용합니다.(사용법은 다들 아시겠죠?^^;;)

[비동기 I/O]
*I/O 개념 잡기
네트워크 카드도 하나의 I/O일 뿐이다.(저같은 경우 처음에 이것을 이해하기 힘들었는데 다른분들은 어떠실지?)
결국 네트워크 카드에 쓰고(Send), 읽고(Read)하는 거죠.
여기서, Async I/O를 쓰게 되는 거구요.

*Overlapped I/O
개념은 별거 아닙니다. 저희 MP3 Player틀어놓구, 동영상 틀면, 소리 2개가 중복해서 동시에 들리죠?
(긍까 자신의 일이 끝날때까지 자원을 잡아먹고 있는 것이 아니라 OS한테 주문만 하고, 어떻게 어떤 순서로
 처리할지는 OS한테 맡긴다고 보면 될까요?^^)
걍 그렇게 중복되서 성능을 높이는 거 정도라고만 알아주세요^^
(근데 예를 들어 파일을 열고, 동일한 위치에 "ABCD"라고 쓰라고 하고, 또 "1234"라고 쓰라고 했는데,
 OS가 "1234"부터 쓰고, "ABCD"를 쓰면, 저희가 원한 결과가 안나오겠죠?^^)

*Async I/O(비동기 I/O)
개념을 잡아보겠습니다.

네트워크 통신도 I/O라는 것을 먼저 생각해 주시기 바랍니다.
프린트하건, 그래픽을 표현하건, 컴퓨터 입장에서는 모두 I/O에 해당되죠.
I/O에서 비동기적인 것은 필수죠.(I/O는 컴퓨터 입장에선 느린 편이니까요)
예를 들면, 프린트 하면서, 딴 작업을 할 수 없다면, 무지 불편해 버리겠죠?

직원(고씨) == Application (흠..저희 회사 사장님 성이 고씨 이십니다...--;...컴맹이십니다...죄송합니다 사장님)
사장님 == Programmer

*사장님이 직원에게 일을 시킵니다. 직원은 여러가지 일을 할 수 있는데 이렇게 시킨다고 합시다.(중복 I/O)

1. 사장 : "어이~ 고씨. 틈나면, 프린트 1000장 해놓구, 커피 타다 놓구, 컴퓨터 100대 딱아놔~"
  (이렇게 여러개의 작업을 set시킵니다. 작업 순서는 고씨 마음이겠죠?^^)
2. 고씨(application)를 async하게 일을 할 경우.
- 프린트 1000장을 프린터에게 시켜놓구(set), 커피물을 올리고(set), 컴퓨터를 딱다가
  켜피가 끓는걸 알면,(notify) 커피타서, 사장님께 1잔 올리고,
  계속 컴퓨터를 딱다가 프린트가 1000장이 다 된것을 알게 되면(notify), 사장님께 갖다 드리고
  컴퓨터를 딱겠죠
(커피를 언제 타가지고 올지 모릅니다...-_-;;)
(대신 총작업시간은 최소화 되겠죠)

* 동기적으로 일을 시키는 경우는 이렇습니다.(작업 하는 건 간단하죠)
1. 사장 : "어이~ 고씨. 프린트 1000장 해와"
2. 고씨 : "....-_-;..(..씨발)....(한참후에)....1000장 해왔는데요"
3. 사장 : "목마르다 커피 타와라~"
4. 고씨 : "....-_-;..(조까..)....(좀 있다가)...커피 드세요^^"
5. 사장 : "...^-_-^...(끝난 줄 알았지??)...컴퓨터 100대만 딱아라 -_-"
6. 고씨 : "....(한꺼번에 시키지 뭐하는 짓이냐?..-_-;...알았다).....알겠습니다^^"
(매번 일이 끝난 것을 확인하고, 순서를 제어하는 것이 가능합니다.)
(좀 느리죠??)

비효율적이죠?^^...문제는 책에 있는 소스나 그런 서버들이 이런 방식으로 짜여져 있다는 것입니다.
(뭐 일단 이해하기 좋으니깐요^^)
서버는 최적화되게 짜야 하는데, 이런방식을 채택하고, 2-3 의 단계를 빨리 해주는 코딩을 해봤자 소용없는 짓이 되겠죠^^

그럼 저희가 IOCP(async i/o)를 쓰기 위해서 어떤 것을 해주면 되느냐~!!
먼저 우리가, set해주고, 딴일을 하고 있으면, I/O 완료시 OS가 notify해주죠.
이 2가지를 잘 다루는 것이 IOCP의 사용법이라 생각됩니다^^.

*동기적인 소켓과의 비교
제일 비효율적인 것부터 소개를 해보죠(CSocket)

1. CSocket (동기화)
send나 recv를 쓰면, 블록킹 되다가 그것의 성공 여부까지 리턴해 줍니다.
recv의 경우 메세지가 올때까지 기달려서, 온 메세지를 확실히 읽어주죠^^
(set -> notify 시간 동안 기달려서, return합니다.)

2. CAsyncSocket(넌블록킹)
send : 그냥 보냄. 제대로 갔는지 안 갔는지는 모른다.
recv : recv버퍼를 보고 없으면, false를 리턴하고, 있으면 데이터를 읽어온다.
2가지 면에서 안 좋은데(제가 그냥 생각한 것입니다.)
1. 언제 왔는지 모르니까 계속 함수를 불러줘야 한다.
2. 이미 특정 위치에 data가 있는데, 다시 우리가 정한 문자열에 copy해 줘야 한다.
(데이터를 읽어온다는 것이 생각해보면, 이미 컴퓨터의 임의의 위치에 데이터가 있는 데 그것을 다시 저희가 만든 프로그램의 저장위치(문자열)로 복사해 온다는 것이죠)
2-1. recv함수가 실행중에 다른 data가 오면, 리시브함수가 안 불리는 것으로 알고 있습니다.
2-2. 2개 이상의 CPU를 가진 컴퓨터일 경우. 컴퓨터는 10개의 cpu가 동시에 10개를 처리할 수 있음에두 불구하고, recv함수가 끝날때까지(1개의 data복사가 끝날때까지) 다같이 기달려야 하는 상황이 발생하는 것입니다. 그게 끝나면 다음 data를 복사하면서 또, 10개의 cpu가 기달리고요.
결국 1개나 10개나 삐까삐까 해지는 것입니다.

3. IOCP
WriteFile : 틈나면, 이 위치부터 어느 크기 만큼 보내라고 set해줌.(진짜로 보낼때 그 데이터가 변해 있거나 메모리가 해제 되어 있을 수 있음)
ReadFile : 메세지가 오면, 요따가 요만큼 쓰라고, set해줌. OS에서 요따가 요만큼 쓰고 나서 다 썻다고 하면(notify), 그 자리를 보면, 메세지가 기록되어 있음.

[I/O Completion Port와 기존의 간단한 서버와의 비교]
기존의 서버...간단한 CAsync, Async소켓과 비교해 보면,(Sync는 어짜피 서버로 쓰지 않겠죠...설마^^)
1. 메세지가 오면,함수를 하나 실행시켜 줍니다. 하지만, 그 함수 안에서 Recv()라는 함수를 써야 겠죠?
결국 이미 컴퓨터 안에 정보(문자열)가 있는데, 또 Copy하는 결과가 되겠죠.
IOCP같은 기술의 경우 저희가 공간을 셋팅해주면, OS가 정보를 그따가 기록하고, 다 썻을 경우(Completion)함수에서부터 실행되게 되죠.


처음 이 글을 올린다는 글을 1달도 전에 올렸었는데, 이제야 올리네요. 여러가지로 바뻐서 죄송합니다...ㅠ_ㅠ
이제는 글을 1주일 간격으로 쭈르륵 올릴 생각입니다.

참고 서적 : Programming Server-Side Application (Richter, Clark)

 

먼저 글 들어가기 전에..

고임님을 깜박했군요...(훗...고씨 == 고임님^^..을 생각하면서 썻을지도 모른다는 생각이 문득 드네요....^^)

위에 3분 칭찬해 주셔서 고맙습니다....^^....오늘 떨면서, 데브피아에 들어왔는데,
큰힘이 되었답니다...^^

특히 최인호님...처음 IOCP 공부하려구 할때부터 지금까지 고맙습니다.
인호님이 쓰신 글을 읽으면서 많은 도움이 됐습니다...(제 머리속에 있는 인호님 글을 제거해 낼 수가 없으니 나중에 표절이라구 하셔두 전 할말이 없겠군요...^^;;)

오늘은 역시 통신의 여러가지 방법들을 쭉 보기로 하죠.
사실 이부분 건너뛰고, IOCP API만 설명해도 쓰는데는 그렇게 지장이 없겠지만
딴 사람들한테 아는 척 할려면, "이런 저런 것들이 있고, 이거의 장점은 요것이지!!"
해야 되지 않겠어요^^....(근데, 이걸루 아는 척할만한 것두 아닌게 좀 흠이죠....-_-;;)
참고로 더 자세히 알고 싶으시면, Server-Side Application 책을 보십시요
(근데, 앞에 한 30page볼려구 7만원짜리 책사는 사람이 있을까요??...)
(....그런 사람이 있군요......저....-_-;;)

아마 이번(1)과 다음(2)으로 끝나지 않을까 생각합니다.

IOCP로 채팅 서버를 만들자~

ㄱ. IOCP의 기술에 대한 이해.

1. 소켓 통신 방법 6가지.( sync, async, device, event, alertable I/O, IOCP)

- 제일 먼저 Unix Network Programming의 그림 소개.
   Unix 계열에선 크게 5가지로 나누더군요. 일단 그림을 봐 주시죠.

<그림을 보시면서, 생각하시면 좋은 것>
1. 데이터가 왔는지 어케 알까?(그냥 함수에서 처리, select문 사용, OS가 직접 알려주기)
2. OS에 온 데이터를 Application에서 그 내용을 어떻게 알것인가(일반적으로 recv를 사용. Application의 문자열에다 카피해옴)

#그림 (제길...이런게 그림이라니...-_-;)

              1.blocking     2.nonblocking    3.I/O multiplexing   4.signal-driven I/O   5.asynchronous I/O
            ---    |             check              check                                     initiate
            |      |             check                |                               
wait        |      |             chack                |
for         |      |b            check                |block
data        |      |l            check                |
            |      |o            check                |
            ---    |c              |                ready             notification
            ---    |k              |b              initiate             initiate              (nothing)
copy data   |      |e              |l                 |                    |
from kernel |      |d              |o                 |                    |
to user     |      |               |c                 |block               |block
            |      |               |k                 |                    |
            ---    *               *                  *                    *
                complete       complete            complete             complete            notification
일단 각각의 설명을 드리죠.
1. blocking : 걍 readfrom()을 쓰면, 계속 기다리다가 데이터가 copy가 완성되면 return한다.
2. nonblocking : 데이터가 있는지 계속 체크하면서 딴일하다가 데이터가 있으면, copy해 온다.(recvfrom)
3. I/O multiplexing : select문을 쓰는 것으로 내부적으로 데이터가 왔는지 검사하다가 있으면 읽어온다.(select+recvfrom)
4. signal-driven I/O : 데이터가 오면, OS자체에서 알려준다.(우리가 검사하지 않아도 된다)(OS에다 등록, recvfrom)
5. asynchronous I/O : OS에다 "혹시 데이터가 오면, 이 공간에다 집어넣구 다 집어넣은 경우에만 나한테 알려라~" 라고 한다.(set, notify의 단계)

그럼 여기서 어떤것이 왜 좋은지 볼까요??^^(좋기야 당연히 5번이 젤 좋지요)
앞으로 크게 2부분에서 봅시다.
1. 데이터가 왔는지 어떻게 아는 지와
2. 데이터가 온 다음에 그 데이터를 써먹게 저희가 아는 곳에
어떻게 저장시키는지 보지요.
1. 다른 것들은 저희가(Application)에서 검사합니다.(select를 쓴다던가, recv()를 써서 성공여부를 검사한다던가...)
   But, Async에서는 OS가 알려줍니다.(딴것들은 OS가 알려주는 걸 체크하지요)
2. 이미 내용이 컴퓨터안에 있는데, recv를 통해서 저희의 어플리케이션에다 copy해 옵니다.
   But, Async에서는 처음부터 저희가 지정해 준곳에다가 OS가 알아서, copy해 놓고 완료되면 저희에게 알려줍니다.

- 윈도우에서 Async를 다루는 방법 4가지.(마지막이 IOCP입니다.

윈도우에서 지원하는 Async는 크게 2가지 단계로 이루어져 있고, notify하는 방법이 4가지 입니다.
1. OS에다가 "자료오면, 이따 써놔라~"(Set)
2. 다쓰고 나면, OS가 어케 우리에게 "데이터를 다 써놨어요~^^"(Notification)

1. 윈도우에서 Async의 방법 4가지다 Set하는 방법은 똑같습니다.
   ReadFile, WriteFile을 이용하죠, 마지막 인자인 OVERLAPPED* pOverlapped를 씁니다.
   이러면, "너 틈날때 내가 지정해준 작업(읽거나 쓰기) 해라~"고 OS에게 알려주는 것입니다.
   WriteFile( (HANDLE)m_sSocket, // 쓸 디바이스를 정해 줍니다.(당연히 소켓에서 읽어옵니다)
  (PVOID)g_stSend[m_i2].szSend,  // 쓸 내용이 들어있는 장소를 정해 줍니다.
  (DWORD)SEND_BUFFER,   // 쓸 크기를 정해 줍니다.
  (PDWORD)NULL,    // 실제 쓰여진 크기라 리턴 됩니다(IOCP에선 안 쓰입니다)
  (OVERLAPPED*)&g_stSend[m_i2]) // OVERLAPPED구조체를 넘깁니다.
   ReadFile((HANDLE)m_sSocket, // 읽어올 디바이스를 정해 줍니다. g_stRecv[m_i].szRecv, RECV_BUFFER, NULL, &g_stRecv[m_i])

[overlapped 구조체]
typedef struct _OVERLAPPED {
 DWORD Internal; // Error Code (Notify 될때 값이 채워져서 옵니다.)
 DWORD InternalHigh; // 전송된 크기(Notify 될때 값이 채워져서 옵니다.)
 DWORD Offset;  // 
 DWORD OffsetHigh; // 2개다 메모리 주소를 셋팅하는데, file i/o외엔 0으로 셋팅하라는 군요.
 HANDLE hEvent;  // IOCP에선 안 씁니다.
} OVERLAPPED, *LPOVERLAPPED;
이 overlapped로 notify시에 정보가 넘어오는데
이 overlapped만으로 쓰기에는 가지고 있는 인자수가 작죠...예를들어 저희가 각 overlapped구조체마다 번호를 매긴다거나
추가 정보를 덧붙여서 set해주고, notify시에 알고 싶을때가 있을 수 있습니다.
그래서, 이 overlapped에서 상속 받아서 필요한 정보를 멤버변수로 설정해 주고, 그것의 pointer를 넘기는 방법이 있습니다.

2. Notify하는 방법이 4가지가 서로 다른 데요.(딴거는 대충 이런 게 있나보다...만 하고 넘어가겠습니다.)
   1. Signaling a device kernel object : WaitForSingleObject를 사용(1인용...)
   2. Signaling an event kernel object : WaitForMultipleObject를 사용(이 함수자체가 64개가 끝)
   3. Using alertable I/O : CPU많으면 쓸모 없지만, 암튼 IOCP의 중간단계임.(죄송 설명이 귀찮아서..--;)
   4. Using I/O completion ports : Queue랑 Stack 같은 것을 사용.

2. IOCP를 쓰는 이유(앞의 단점들을 보여주면서)

IOCP에서는 데이터가 오면, 내부적으로 어떻게 처리하는 지 볼까요??^^(어떤 Struct들을 쓰는지 봐주세용^^)

1. IOCP에서 쓰는 구조
 1. Device List : 관련된 소켓들을 이따가 기록해 놓습니다.
 2. I/O Completion Queue(FIFO) : I/O가 완료 됐을 때, 완료됐다는 메세지를 쌓아놓는 Queue입니다.
 3. Waiting Thread Queue(LIFO) : 대기중인 Thread 모아놓기.
 4. Released Thread List : 현재 돌아가구 있는 Thread들.
 5. Paused Thread List : 돌아가구 있다가 잠시 정지해 있는 Thread들.
2. 돌아가는 거(개념적으로)
        1. 데이터가 오면, OS가 지정해 놓은 메모리 위치에 써놓구 notification을 2.I/O Completion Queue(IOCQ)에 올린다.
 2. IOCQ에 뭐가 들어오면, 3.Waiting Thread Queue(WTQ)에 대기된 Thread가 있는지 보고, 있다면
 3. WTQ에 대기된 Thread에 일을 맡긴다.
 4. 그 Thread는 WTQ -> Released Thread List(RTL)로 가게 된다.
 5. 혹시 작동중 Sleep등의 함수를 만나면, RTL->PTL로 이동했다가 RTL로 다시 돌아온다.
 6. 온 데이터에 대한 처리가 끝나면, 그 Thread는 다시 WTQ로 이동한다.

그럼 여기까지가 이론적인 내용이었구요.

실제로 IOCP기술을 이용하기 위해서, 알아야 할 함수들을 봅시다.
1. IOCP를 1개 맹근다(리턴값이 IOCP HANDLE입니다.)
m_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, dwNumberOfConcurrentThreads);

2. 1명 추가로 접속시마다 그 연결을 IOCP에 추가 시킨다.
CreateIoCompletionPort((HANDLE)hSocket, m_hIOCP, dwCompKey, 0);

3. Thread가 이 함수를 만나면, WTQ로 들어가고, data쓰기가 완료되면 OS에서 알아서 이 함수를 RTL로 보낸다.
(lpOverlapped를 이용해서, 짝지워서 보내 줍니다.)
GetQueuedCompletionStatus(m_hIOCP, // 어떤 IOCP에서 가져오냐?
   &dwIoSize,  // 얼만큼 가져 왔냐?
   &dwCompKey,  // CompKey
   &lpOverlapped, // 구조체 시작 주소 가져옴.
   INFINITE);

* 실제로 코드에서 저는 이렇게 썼습니다.
while(1)
{
 bSuccess = GetQueuedCompletionStatus( m_hIOCP, // 어떤 IOCP에서 가져오냐?
      &dwIoSize,  // 얼만큼 가져 왔냐?
      &dwCompKey,  // CompKey
      &lpOverlapped, // 구조체 시작 주소 가져옴.
      INFINITE);
 // 이 GetQueueCompletionStatus에서 data의 읽기, 쓰기가 완료후 notify가 뜰때까지 기달립니다.

 lpOverlappedBasic = (OVERLAPPED_BASIC*)lpOverlapped; // 저희가 정한 구조체로 typecasting

 switch(lpOverlappedBasic->iMode) {
 case EVENT_READ: // Recv가 완료 됐을 때.
  // Recv 처리.
  break;
 case EVENT_WRITE: // Send가 완료 됐을 때.
  // Send 처리.
  break;
 default:
  break;
 }
 // 잠금 풀어줘야지~!
 lpOverlappedBasic->SetWriteableFlag(TRUE);
}

자세한 건 코드를 보시면 아실 껍니다...^^(앗 이런 무책임한 발언이...ㅠ_ㅠ)

3. 조심할 점(메모리의 제대로 된 확보, Critical Section, Race condition)

1. 메모리의 제대로 된 확보
저희가 어떤 메모리를 OVERLAPPED를 통해서, 지정해 줄 때(set), set하는 시간이야 저희 코드안에 있지만,
그 set한게 언제 끝날지 모르죠.
틀린 예를 들어보겠습니다.
VOID ReadData(HANDLE hfile) {
   OVERLAPPED o = {0};
   BYTE b[100];
   ReadFile(hfile, b, 100, NULL, &o);
}
원래는 o 라는 객체가 ReadFile이후에 계속 살아있어야 하는데, 함수가 끝나면, 사라지죠??
글구, 저 함수에서는 Read하면, 그 메모리 주소에다가 쓸려고 할테고...런타임 에러 되겠습니다.
(아무튼 함수가 불린 이후에도 메모리는 계속 살아있어야 하죠...)

그렇다고...전역변수로 OVERLAPPED o = {0}라고 한다고 문제가 해결되는 거 아닙니다.
확실히 메모리는 계속 확보가 되어있습니다만, 여러곳에서 o를 건드리면, 저희가 생각한 값이 아닌 값이
들어있을 확률이 있죠.
1. 저희가 WriteFile을 이용해서, o에다 data1의 시작 위치를 정해주고, 전송해라(set)합니다.
2. 아직 전송이 안됐는데, WriteFile을 또 불러서, o에다 data2의 위치를 정해주고, 전송 시킵니다(set)
3. 결국 실제로 데이터의 전송이 일어날때 보면, data1의 위치는 간데없고, data2가 2번 전송 됩니다.
(각자 따로 o를 만들어서 안 쓰고, o를 1개 만들어서, 써서 문제가 일어나는 거겠죠)

이 문제를 해결하는데 쓰인 저의 방법은 다음 문서에 올리겠습니다....^^(각자 생각해 보세요)
(제가 쓴 방법보다 더 좋은 방법이 얼마든지 나울 수 있다고 생각합니다....^^)

2. Critical Section
위의 문제가 현재 o를 data1이 쓰고 있는데, 딴 놈이 건드린 경우죠??
이런 경우를 막아주기 위해, CRITICAL_SECTION이란 것을 지원해 줍니다.
 CRITICAL_SECTION m_csListenSocket;  // 일단 1놈 만듭니다.

 InitializeCriticalSection(&m_csListenSocket); // 초기화 해줍니다.(걍 생성자에다가 넣어버리세요)
 DeleteCriticalSection(&m_csListenSocket); // 자원을 회수 합니다.(걍 소멸자에다가 넣어버리세요)
 EnterCriticalSection(&m_csListenSocket); // 이 놈을 쓸때 씁니다.
 LeaveCriticalSection(&m_csListenSocket); // 이 놈을 다 쓰고, 나올때 씁니다.
예를 들어, 누가 Enter했는데, 딴 Thread가 어디서, Enter를 불렀다면, 뒤에 부른 놈은 먼저 부른 놈이 Leave를 불러서
나오기까지 기다립니다.(쉽게 말해서, Leave를 제대로 안해주면, 프로그램이 멈출 수 있다는 겁니다.)

3. Race Condition
멀티 스레드로 돌아가다 보면, 동시에 1 스레드만 건드려야 할 것이 동시에 2개의 스레드가 자원을 공유하면서
문제가 생길 수 있습니다. 값이 생각같이 안 나올 수 있죠.
race condition : 동시에 2곳이 건드려서 문제가 생기는 것. 이라고 생각해 주세요(전 critical section을 써서 막아봤습니다)

[출처] IOCP|작성자 multist


And
prev | 1 | next