IOCP getqueuedcompletionstatus() 함수에서 키값 질문입니다...

season12의 이미지

안녕하세요 IOCP 공부시작한지 얼마 안되는 완전 초짜입니다..

제 질문이 답답하더라도 너그러히 용서를 바랍니다~~

getqueuedcompletionstatus() 함수에서 키값으로 클라이언트의 소켓값을 받고 있습니다...

한대의 컴퓨터에서 3개의 클라이언트를 실행했을때

첫번째 클라가 100 두번째 클라가 200 세번째 클라가 300 의 소켓 값을 갖는다고 가정한 상태에서

세번째 클라이언트를 종료했을때 getqueuedcompletionstatus() 의 키값을 확인하면 300 이 제대로 들어오는데

두번째 클라이언트를 종료했을때도 getqueuedcompletionstatus() 의 키값이 300 이 나오고

첫번째 클라이언트를 종료해도 getqueuedcompletionstatus() 의 키값이소켓값이 300 이 나오는 상태입니다;;;

제일 처음 ACCEPT 할때 소켓이 다른건 확인한 상태입니다...

그리고 또 다른 질문은 클라이언트를 여러개 띄어놓고 채팅메세지를 보내면 전체다 잘 봤는데요..

이게 시간이 지나면 어느 한 클라이언트에서 서버로 메세지 전송이 안됩니다...

근데 또 다른 클라이언트에서 보내는 메세지는 recv하네요....왜 이런지 도저히 모르겠네요..;

혹시 제가 놓친부분이 어디인지 가르침의 은혜를 주실분 모십니다~~

그럼 좋은 주말 되시길 바랄께요~~


chadr의 이미지

getqueuedcompletionstatus()이 리턴하는 값과 함수의 인자로 넣어서 가져온 값들의 에러 체크는 하셨나요?
접속이 종료되는 순간을 잘 확인하셔서 필요없는 리소스는 해제해주시는 것을 잘 해주셔야합니다.

일단 말씀하신 상황으로는 어느 부분이 더 문제인지 알기는 어렵습니다.

시간이 지나면 특정 클라이언트로 데이터가 안가는 것도 어디선가 메모리가 꼬인것 같습니다.
특히나 IOCP같은 경우에는 context에 유저데이터를 넣는데 이를 언제까지 메모리에 저장을 하고
해제 해야하는 시점이 매우많이 중요합니다. 그렇지 않으면 프로그램이 죽거나 엉뚱한 행동을 하게 됩니다.

또한 IOCP가 멀티스레드로 돌아가기 때문에 한번 문제 생기면 디버깅하기 정신없습니다.

MSDN을 주로 참고하시고 정확히 이해한 후에 인터넷에 널려있는 소스를 참고해보세요.

예전에 짠 코드를 보여드리겠습니다. 도움이 되실겁니다.

bool
Worker::Run()
{
  DWORD bytesTransferred = 0;
  IO_CONTEXT *context = NULL;
  Client *client = NULL;
  BOOL ret = GetQueuedCompletionStatus(this->m_completionPort, &bytesTransferred, (PULONG_PTR)&client, (LPWSAOVERLAPPED*)&context, 0);
 
  if (!(ret == FALSE && GetLastError() == WAIT_TIMEOUT))
  {
    if (context == NULL)
    {
      Log::GetInstance().Write("Cannot get data from Completion port : \"%s\"",
        Util::SystemErrorToString(GetLastError()).c_str());
 
      return true;
 
    }
 
    AutoLocker autoLocker(client->GetLocker());
    bool closed = false;
 
    if (ret == FALSE)
    {
      DWORD flags;
 
      if (WSAGetOverlappedResult(client->GetSocket(), context, &bytesTransferred, FALSE, &flags) == FALSE)
      {
        if (WSAGetLastError() != NO_ERROR)
        {
          //이곳에서는 상대방이 closesocket을 하여 전송을 요청한 컨텍스트를 더이상
          //전송 할 수 없을 때 발생한다. 전송 요청을 한 후 완료되지 않았을 때 closesocket을
          //했다면 전송 요청한 수 만큼 발생한다.
 
         /* Log::GetInstance().Write("Network name is no longer available. Closing client pended (IP : %s, Port : %hu, Socket : %u, Context : %p)",
            inet_ntoa(client->GetSockAddr().sin_addr),
            ntohs(client->GetSockAddr().sin_port),
            client->GetSocket(),
            context);*/
 
          closed = true;
 
        }
 
      }
 
    }
    else if (bytesTransferred == 0)
    {
      Log::GetInstance().Write("Client connection closed. Deleting client pended (IP : %s, Port : %hu, Socket : %u)",
        inet_ntoa(client->GetSockAddr().sin_addr),
        ntohs(client->GetSockAddr().sin_port),
        client->GetSocket());
 
      closed = true;
 
    }
 
    if (!client->ProcessContext(context->op, context, bytesTransferred, closed))
    {
      Log::GetInstance().Write("Cannot process context (IP : %s, Port : %hu, Socket : %u)",
        inet_ntoa(client->GetSockAddr().sin_addr),
        ntohs(client->GetSockAddr().sin_port),
        client->GetSocket());
 
      closed = true;
 
    }
 
    if (closed)
    {
      client->SetToBeClosed();
 
    }
 
  }
 
  this->m_server->GetClientProcessor().CollectDeadClient();
  this->m_server->GetClientProcessor().ProcessClientTasks();
 
  return true;
 
}

-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

season12의 이미지

bSucess = GetQueuedCompletionStatus(pDlg->GetCompletionPort(),&BytesTransferred,(LPDWORD)&PerHandleData,
(LPOVERLAPPED *)&PerIOData,INFINITE);
printf("socket = %d\n",PerHandleData->m_ClientSocket);
int sockError = WSAGetLastError();
CClientInfo * pTemp = pDlg->m_ClientList.GetClientInfo(PerHandleData->m_ClientSocket);

if (bSucess == FALSE && sockError != 64)
{
if (PerIOData == NULL)
{
printf("PerIOData == NULL = %d\n",sockError);
continue;

}
else
{
printf("i/o failed %d\n",sockError);
pDlg->m_ClientList.DeleteInfo(pTemp);
continue;
}
}
else
{
if (bSucess == ERROR_SUCCESS)
{

}
else
{
여기서 데이터 처리 합니다....제 코드는요...-0-
}

방금 클라이언트의 소켓이 전부같은거 해결했는데....
iocp에 연결할때 키값을 제가 원래
socket 값과 sockaddr_in 을 가지고 있는 구조체를 키값으로 넘겨줬습니다..
근데 그냥 소켓만으로 하니까 키값이 제대로 넘어오네요...

이문제는 해결이 됐는데 왜 됐는지 모르겠네요...
이전 코드도 어차피 소켓만 가지고 프로그래밍 했었는데...-ㅅ-

제가 찾아보니까 getqueuedcompletionstatus() 이 리턴하는게 4가지 경우가 잇는데..
i/o 동작 완료,실패 ,tiime-out , error_success 요렇게 4가지 경우라고 봤는데....
그래서 위에 처럼 일단 만들어는 봤습니다..;

답변주신거 보니까 WAIT_TIMEOUT 이건 뭔지 모르겠네요...;;

WSAGetOverlappedResult함수는 왜 확인하는건지 잘 모르겠습니다....
찾아보니 WSAGetOverlappedResult 함수는 매개변수로 지정한 소켓에 대한 오버랩 연산의 결과를 반환하는 함수 입니다
이렇게 나와있는데...데이터 잘 받았다는걸 확인하는건지;;

그리고 context 에 클라이언트 정보가 들어가나여???
context에는 클라이언트에서 전송한 데이터만 들어있는거 아닌가요??

공부시작한지 얼마되지 않아서 엄청해메는데 계속물어봐서 정말 죄송합니다...!
염치없지만 다시 답변해주시면 정말 감사하겠습니다!!

그리고 아주아주 간략하게라도 코드설명좀 부탁드려도 될까요??
정말 죄송합니다..

chadr의 이미지

PerHandleData로 받아오는 구조체 데이터들은 당연히 맨처음 IOCP를 소켓과 연결할때 동적할당해서 넘겨준 것이겠지요?
왠지 구조체는 안되고 소켓 번호를 주니까 된다는 말씀을 보니 동적할당을 안하신 것 같은 생각이 드네요.

WAIT_TIMEOUT은 일반적으로 서버에서 클라이언트가 데이터를 보내주면 그때서야 IOCP를 처리하는(get~~~status함수 호출하는 곳)
스레드에서 감지를 하고 데이터를 처리합니다. 특히 채팅프로그램의 경우에는 그렇습니다.

하지만 제가 짠 코드는 클라이언트가 데이터를 안보내줘도 계속 뭔가를 해야하기 때문에 타임아웃을 걸어놨습니다.
즉 저 스레드에서 데이터도 읽고 주기적으로 계속 뭔가를 해야하기 때문에 타임아웃을 IOCP를 생성할때 줬고 이를 처리하고 있습니다.
만약에 iocp를 처리하는 스레드에서 따로 하실게 없고 소켓으로 입출력 되는 데이터의 결과만 알고 싶으시면
타임아웃을 안걸고 그냥 하시는게 좋습니다. 또는 다른 스레드를 만들어서 iocp에서 처리한 데이터를 따로 처리하는 스레드가
있다면 당연히 타임아웃을 안걸어도 됩니다. 암튼 그런 처리가 필요해서 만들어놓은것입니다.

그리고 WSAGetOverlappedResult 로 검사하는 이유는 iocp에서 에러가 났을 경우 데이터 전송까지 안됬는지 확인 하기 위해서
확인 하는 부분입니다. 만약에 거짓을 리턴하는 경우 아직 데이터 전송이 안끝났다는 이유가 됩니다. 그런데 그 밑에 getlasterror에서
전송이 안끝난 이유가 정말 문제가 생겨서 안끝난건지 아니면 문제는 없고 정상적으로 iocp 큐에는 저장이 되어있는데
어떤 이유에 의해서 get~~~status 호출만 실패했을 경우 그냥 처리를 안하고 데이터 전송이 정상적으로 완료될때까지
다시 기다리기 위한 코드입니다. 해당 코드에서 이 조건이 모두 만족 한 경우(get~~~status도 비정상, 비동기 전송 요청도 비정상. 결과적으로 소켓이 끊겼을 경우(상대방에서 접속을 강제적으로 끊는 경우)를 처리하는 코드입니다. 이 경우는 상대방이 접속을 먼저 끊었을 경우에만 발생합니다.)

context는 완료 요청이 온 데이터 블럭(질문자님께서 키값으로 칭하는 것입니다.)에 대한 것을 저장한 코드입니다.
어차피 서버는 클라이언트와 통신 하는 것이고 입/출력 모두 특정한 소켓과 연결이 된 것들입니다.

그래서 context라는 구조체를 만들고 거기에 소켓 번호, 해당 데이터블럭의 메모리 주소등의 get~~status 호출후 처리를 하기 위해서
필요한 데이터들을 가지고 있습니다. 실제 클라이언트에게 전송할 데이터는 다른 주소에 넣을수 있습니다.(wsarecv, wsasend) context에 있는 데이터가
모두 클라이언트로 전송되는건 아닙니다. 따라서 context에 클라이언트에 전송할 데이터의 주소도 같이 저장해 놓으면
완료 처리를 손쉽게 할 수 있게 되는 것입니다.(단지 전송할 데이터의 포인터만 저장)

저도 IOCP를 처음 공부할때 참고할만한 정석적인 자료가 없어서 엄청 삽질했던 기억이 있네요.
msdn이 많이 도움이 됬었습니다. 다시한번 말씀드리지만 iocp를 제대로 다루기 위해서는 iocp에 관련된 함수가 리턴하는
모든 값과 상황에 대해서 대처하는 코드를 작성해주셔야합니다. 특히나 멀티스레드라서 디버깅하기 정말 어렵습니다.
-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

-------------------------------------------------------------------------------
It's better to appear stupid and ask question than to be silent and remain stupid.

익명 사용자의 이미지

ㅅㄷㄴㅅ

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.