블로그 이미지
래머
오늘도 열심히 개발하는 개발자입니다.

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

2014. 5. 2. 01:14 C/C++



[본문스크랩] [MSSQL] 함수

2010/02/21 21:20 수정 삭제

작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130081002003

출처 맑밝인 | 맑밝인
원문 http://blog.naver.com/ballkiss/30025302748

MSSQL 에는 여러가지 함수가 있다.

숫자함수, 문자함수, 날짜함수, 시스템함수, 보안함수 등...

그치만 이걸 다 어케 외워.. -_-;;

자주 쓰는 함수만 알아보자..

 

1. 숫자에 대한 산술적 연산 함수

○ CEILING

  - 올림이다. 천장이란 뜻

○ FLOOR

  - 버림이다. 마루라는 뜻

○ RAND

  - 0과 1 사이의 임의의 부동 소수점 수. 랜덤한 숫자를 구할 때 쓴다.

○ ROUND

  - 반올림이다.

 

 

2. 문자에 대한 함수

○ LEFT

  - 왼쪽에서부터 주어진 길이만큼의 잘라낸다.

○ LEN

  - 주어진 문자의 길이

○ LOWER

  - 소문자로 변환한다.

○ LTRIM

  - 왼쪽에 있는 빈칸을 삭제한다.

○ REPLACE

  - 문자열중에서 특정 문자를 지정한 문자로 바꾼다.

○ RIGHT

  - 오른쪽에서부터 주어진 길이만큼 잘라낸다.

○ RTRIM

  - 오른쪽에 있는 빈칸을 삭제한다.

○ SPACE

  - 빈칸을 지정한다.

○ STR

  - 숫자를 문자로 변환한다

○ STUFF

  - 지정한 만큼의 문자열을 지우고 새로 지정한 문자열을 삽입한다.

○ SUBSTRING

  - 지정한 길이만큼 문자열을 잘라준다.

○ UPPER

  - 대문자로 바꾼다.

 

 

3. 날짜에 대한 함수

○ GETDATE

  - 현재 날짜와 시간을 구한다.

  - SELECT GETDATE() --> 2007-12-12  22:21:35.056 이런식으로 나온다.

○ DATEADD

  - datapart 부분에 number 값을 더한다. (datepart, number, date)

○ DATEDIFF

  - 두 날짜 사이의 datepart 값

○ DATENAME

  - date 에 대한 datepart 를 이름으로 돌려준다.

  - SELECT DATENAME(dw, GETDATE()), DATENAME(mm, GETDATE()) --> 화요일, 12

○ DATEPART

  - date 에 대해 지정된 datepart 를 돌려준다.

  - SELECT DATEPART(mm, GETDATE()) --> 12

○ DAY

  - DATEPART(DAY, date) 와 같다.

○ MONTH

  - DATEPART(MONTH, date) 와 같다.

○ YEAR

  - DATEPART(YEAR, date) 와 같다.

 

* DATEPART 사용법

yy - year (1753~9999)

mm - month (1~12)

dd - day (1~31)

dw - weekday 요일 (1~7) 1 : 일요일

hh - hour (0~23)

mi - minute (0~59)

ss - second (0~59)

ms - millisecond (0~999)

 

 

4. 시스템 함수

○ DB_NAME

  - database 이름

○ ISDATE

  - datetime 이나 smalldatetime 자료형인지 아닌지 검사한다. 맞으면 1, 아니면 0

○ ISNULL

  - NULL 이면 지정한 값으로 바꾼다.

 

5. 연산 함수

○ AVG

  - 평균값

○ COUNT

  - 표현식의 개수

○ COUNT(*)

  - 선택된 모든행의 개수

○ MAX

  - 최대값

○ MIN

  - 최소값

○ SUM

  - 합계

 

이거 말고도 참 많다.

하지만 많이 사용하는 것만 살짝 적어봤다.




posted by 래머
2014. 5. 2. 01:12 C/C++



작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130082207567

출처 더 큰 세상을 향하여~ | 요셉처럼
원문 http://blog.naver.com/lkhmymi/100015006977

등급 : 초급

 

ADO oledb를 이용한 간단한 DB조작에 관련된 강좌입니다.

 

아시겠지만 DB조작은 ODBC를 이용하거나 OLEDB 등을 이용해 가능합니다.

 

ODBC는 배포시 ODBC설정등을 해줘야(코드로 대체할 수 있습니다.) 하므로 귀찮기에 전 VB건 ASP건 VC건 모두 OLEDB로 처리하는 편입니다.

 

짧은 경험상 책을 통한 지식(클래스를 이용한 DB조작)은 딱딱하기 그지 없습니다.

 

이는 위에 나열한 언어와 관련된 책 모두 동일했습니다.

(특히 VB로 처음 DB조작이란걸 경험했을때 DAO 컨트롤을 통해 레코드를 이동시키며 공부했을때의 그 막막함이란..ㅡㅡ

 Join이란 녀석 자체도 몰랐기에 테이블 만큼 컨트롤 생성하고 억지로 이것 저것..ㅡㅡ)

 

여튼.. 잡설은 그만두고.. 간단한 사용 예를 들어볼까 합니다.

 

[사전인지사항]

MyAdodb.h, MyAdodb.cpp : DB관련 변수와 초기화 연결등을 담당합니다.

SimpleAdoDlg.h SimpleAdoDlg.cpp : 실질적인 DB조작을 담당합니다.

 

[Step 1 : MyAdodb.h, MyAdodb.cpp]

1.프로젝트 하나를 생성합니다.

 

2.StdAfx.h를 열어 #import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "adoEOF")를

  복사해 넣습니다.

 

3.COM 라이브러리를 초기화해야 하니 CoInitialize(0);를 생성자에 포함시킵니다.

   이와 반대로 소멸자엔 CoUninitialize();를 삽입합니다.  

 

4. 커넥션과 커멘드 그리고 레코드셋 변수를 생성해줍니다.

    _ConnectionPtr m_pConn; // 커넥션은 말 그대로 DB와의 연결을 담당합니다. DB의 Open, Close...

    _RecordsetPtr m_pRs;      // 레코드셋 또한 말 그대로 조회결과인 레코드들을 담당합니다. 이녀석을 통하여 조회된 결과를 Movenext등을 이용해 원하는 결과물을 얻어낼 수 있습니다.

    _CommandPtr m_pComm;// 커멘드는 이 역시 명령과 관련된 녀석입니다. Update를 해라던지...

 

5.DB와 연결을 합니다.

BOOL CMyAdodb::InitDB(CString strUserId, CString strPassWord, CString strDbIp, CString strDbName, CString strPort)

{   

    CString strConn = "";

    strConn.Format("User ID=%s;Password=%s;Data Source=%s;"

                    "Initial Catalog=%s;Network Address=%s,%s;"

                    "Network Library=dbmssocn",

                    strUserId, strPassWord, strDbIp, strDbName, strDbIp, strPort);

 

    m_pConn = new _ConnectionPtr("ADODB.Connection");

    m_pRs = new _RecordsetPtr("ADODB.Recordset");

    m_pRs->CursorType = adOpenDynamic;

 

    m_pComm = NULL;

    m_pConn->Provider = "SQLOLEDB";

    m_pConn->ConnectionString = strConn.GetBuffer(0);  // 접속 스트링 설정.

 

    try

    {

        m_pConn->Open("","","", adConnectUnspecified);

    }catch(...)

    {

        return FALSE;

    }

 

    m_pComm.CreateInstance(__uuidof(Command));

    m_pComm->ActiveConnection = m_pConn;

    m_pComm->CommandType = adCmdText;

 

    return TRUE;

}

 

여기 까지 했다면 일단 사전 준비단계는 끝난거라봐도 무관합니다.

이제부턴 실질적인 연결작업과 조회 수정등을 해 볼 차례입니다.

 

[Step 2 SimpleAdo.h, SimpleAdo.cpp]

1.MyAdodb.h를 SimpleAdoDlg.h에 포함시키고 클래스를 만듭니다.

#include "MyAdodb.h"

CMyAdodb m_MyAdodb;

 

2.SimpleAdoDlg.cpp에서 DB연결 및 조작을 해봅니다.

  먼저 DB연결 문자열을 생성시킵니다.

  쿼리를 날렸을때 이녀석이 어떤 DB에 어떤 정보로 로긴등을 하여 조작이 가능한지에 대한 문자열입니다.

  잘 모르신다면 아래 강좌를 참조하시면 쉽게 설명되어있습니다.

http://www.devpia.com/Forum/BoardView.aspx?no=6840&page=1&Tpage=1&forumname=vc_lec&stype=&ctType=&answer=&KeyR=nameid&KeyC=

MS-SQL을 기준으로

User ID : DB에 로그인할 아이디 (SA정도가 됩니다.)

Password : 로그인할 아이디의 비밀번호

Data Source : MS-SQL이니 특정 아이피가 들어갑니다. 만약 MDB라면 해당 로컬 경로가 들어가있을겁니다.

Initial Catalog : DB의 이름입니다.

Network Address : MS-SQL이니 특정 아이피가 들어갑니다. 만약 MDB라면 해당 로컬 경로가 들어가있을겁니다. 특정 포트로 DB가 설정이 되어있지 않다면 기본포트인 1433이 입력됩니다.

Network Library : 모르겠군요. 책을 보면 나와있을겁니다. 죄송^^

 

Ex)

User ID=USERID;Password=PASSWORD;Data Source=XXX.XXX.XXX.XXX;Initial Catalog=DBNAME;Network Address=XXX.XXX.XXX.XXX,1433;Network Library=dbmssocn

 

위 정보를 토대로 Step1의 5번에서 만든 DB연결 함수를 호출합니다.

    if(m_MyAdodb.InitDB("USERID", "PASSWORD", "XXX.XXX.XXX.XXX", "DBNAME", "1433") == FALSE)

    {

        MessageBox("디비접속에러입니다. 작업을 진행할 수 없습니다.", "SIMPLEDB", MB_ICONSTOP);

        GetParentOwner()->PostMessage(WM_CLOSE);

        return;

    }   

 

 

3.DB와의 연결이 성공하였으니 가장 기본이 되는 조회(Select)를 해보겠습니다.

    // 알맞은 쿼리를 대입하십시요. join이든 union이든.. (당연하지만..ㅡㅡ)

    m_MyAdodb.m_pComm->CommandText = "Select * From Test_Tbl where 어쩌고 저쩌고";

    

    try

    {

        m_MyAdodb.m_pRs = m_MyAdodb.m_pComm->Execute(NULL,NULL,adCmdText);  // 저장된 select문을 수행합니다.

    }

    catch(...)

    {   // 에러일 경우 메세지 박스를 출력하고 조회 작업을 종료해 버립니다.(에러가 날 경우는 거의 없습니다.)

        MessageBox("조회 오류!", "SIMPLEDB", MB_ICONSTOP);

        return;

    }

    

    // 결과가 존재하지 않음

    if(m_MyAdodb.m_pRs->adoEOF)     

    {

        m_MyAdodb.m_pRs->Close();   // 레코드셋을 닫는다.(항상 레코드셋 오픈 후엔 닫아주는 작업을 수행해야 합니다.)

        MessageBox("조건에 맞는 데이터가 없습니다.", "SIMPLEDB", MB_ICONINFORMATION);

        return;

    }   

 

 

    _variant_t Field_1;     // 필드명입니다.(알맞은 필드명을 나열하십시요)

    _variant_t Field_2;     // 필드명입니다.

    

    char szField1[10+1]         = {'\0',};  // 필드명과 1:1대응하게 변수를 선언하십시요

    char szField2[10+1]         = {'\0',};

    

    // 조회된 레코드의 끝까지 루핑을 돌며 데이터를 확인합니다.

    while(!m_MyAdodb.m_pRs->adoEOF)

    {

        Field_1         = m_MyAdodb.m_pRs->Fields->GetItem("field_1")->GetValue();  // 해당 필드를 선언한 variant변수에 저장합니다.

        Field_2         = m_MyAdodb.m_pRs->Fields->GetItem("field_2")->GetValue();

        

        strcpy(szField1,    (char*)((_bstr_t)Field_1));                             // 저장된 variant변수를 조작이 편한 char변수에 복사합니다.

        strcpy(szField2,    (char*)((_bstr_t)Field_2));

 

 

        // 리스트 컨트롤을 추가하셨거나 화면에 출력하실 생각이 있으시다면

        // 루핑을 돌리며 값을 채워넣는 작업을 이부분에서 하시면 됩니다.

 

 

        m_MyAdodb.m_pRs->MoveNext();    // 레코드셋을 다음으로 이동시킵니다.

    }

    m_MyAdodb.m_pRs->Close();           // 레코드셋을 닫아줍니다.

 

그림으로 해보면..


 

4.Update를 해보겠습니다.

    _variant_t LRowCount;   // Update가 적용된 레코드의 로우수

    long lrowcount = 0;     // 적용된 variant형을 레코드의 로우수 저장

 

    char CommandString[400+1]={'\0',};

    strcpy(CommandString, "Update Test_Tbl set Field_1 ='01', Field_2='02' Where User='senosora'");    // 알맞는 update쿼리를 적용하십시요.

 

    m_MyAdodb.m_pComm->CommandText = CommandString;

                

    try

    {

        m_MyAdodb.m_pComm->Execute(&LRowCount,NULL,adCmdText); // Update 실행

    }

    catch(...)

    {

        // 에러일 경우 메세지 박스를 출력하고 update 작업을 종료해 버립니다.(에러가 날 경우는 update문 오류.)

        MessageBox("Update 오류!", "SIMPLEDB", MB_ICONSTOP);

        return;

    }

    

    lrowcount = atol((char*)((_bstr_t)LRowCount));  // variant형을 long형에 저장

    

    if(lrowcount == 0) // 업데이트된게 없다.

    {

        MessageBox("Update 적용된 레코드 없음!", "SIMPLEDB", MB_ICONSTOP);  

        return;

    }

 

 

 

여기까지입니다.

제 지식이 DB에 관해 command객체는 어떠어떠하고 레코드셋은 어떠하며 속도가 어떻다라 논할 실력이 된다면야 좋겠지만

그렇다고 책을 그대로 베껴놓을수도 없고.. 해서 배경지식은 적지 못했습니다.

틀린점이 있다면 바로잡아 주시면 감사하겠습니다.

첨부되는 파일은 실행하면 DB가 없으니..에러가 납니다.

그냥 눈으로 보시면 이해가 빠르지 않을까 싶어 첨부합니다.

테스트 하시려면 DB구성부터 하셔야 합니다.

근데.. 주절 주절 해놓고 보니 어째 더 복잡해보이는군요.ㅡㅡ

 

MYSQL을 사용하시려면 DB연결부분만 MYSQL로 변경해주시면 동일하게 사용이 가능합니다.

 

 

추가 : 대충 이런식으로 알고 있습니다. mysql을 잘 모르는데.. localhost라면 mysql은 pwd를 안넣더군요..

        // MySql

        strcpy(ConStr,"Driver={MySQL};server=");

        strcat(ConStr,DBIP);

 

        if(strcmp(DBIP, "localhost") == 0)

        {       

            strcat(ConStr,";Uid=root;pwd=;Database=yourdbname");

        }

        else

        {       

            strcat(ConStr,";Uid=root;pwd=yourpassword;Database=yourdbname");

        }

    

            pConn = _ConnectionPtr("ADODB.Connection");

            pRS =  _RecordsetPtr("ADODB.Recordset");

 

            pRS->CursorType = adOpenDynamic;

 

            pComm = NULL;

 

            pConn->ConnectionString = ConStr;  // 접속 스트링 설정.

 

            try

            {

                pConn->Open("","","", adConnectUnspecified);

            }catch(_com_error &e)

            {

                _bstr_t bstrSource(e.Source());

                _bstr_t bstrDescription(e.Description());

                

                AfxMessageBox("MySQL DB 접속 에러");

                exit(1);

            }

 

            pComm.CreateInstance(__uuidof(Command));

            pComm->ActiveConnection = pConn;

            pComm->CommandType = adCmdText;

        }

 

출처 : 데브피아 Visual C++

http://www.devpia.com/forum/BoardView.aspx?no=7041&page=1&forumname=vc_lec&stype=&ctType=087VCF





'C/C++' 카테고리의 다른 글

ActiveX 권한 상승에 대한 일반적인 이야기와 솔루션들 [펌]  (0) 2014.05.02
[본문스크랩] [MSSQL] 함수  (0) 2014.05.02
WSAEventSelect 모델  (0) 2014.05.02
WSAEventSelect  (0) 2014.05.02
WSASend  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:11 C/C++



WSAEventSelect 모델

WSAEventSelect 모델은 윈도우 메시지 대신에 이벤트 객체를 이용한다는 점을 제외하면  WSAAsyncSelect 모델과 여러 가지로 비슷하다. 물론 표 4에 나와있는 네트워크 이벤트도 WSAAsyncSelect 모델과 공유해 쓸 수 있다. WSAEventSelect 모델을 쓰려면 먼저 다음과 같이 WSACreateEvent 함수를 이용해 이벤트 객체를 만들어야 한다.

 

WSAEVENT  WSACreateEvent(void);

 

WSACreateEvent 함수를 통해 이벤트 객체를 만들었으면 이 이벤트 핸들을 소켓에 연결해야 한다(이때 원하는 이벤트를 지정해야 한다). 이 작업은 WSAEventSelect 함수를 통해 이루어지며, 다음은 이 함수의 원형이다.

 

int WSAEventSelect (

  SOCKET s,              

  WSAEVENT hEventObject,

  long lNetworkEvents

);

 

첫 번째 인자 s는 소켓을 나타내며, 두 번째 인자 hEventObject는 WSA CreateEvent를 통해 얻은 이벤트 객체를, 마지막 인자 lNetworkEvents는 네트워크 이벤트 타입이다.

 

 

WSACreateEvent는 논시그널드(nonsignaled) 상태의 매뉴얼 리셋(manual reset) 이벤트를 생성한다. 그러므로 이벤트가 시그널 상태로 되어 적절한 처리를 해준 후 다시 논시그널드 상태로 되돌리려면 다음의 WSA ResetEvent 함수를 호출해야 한다.

 

BOOL WSAResetEvent(

  WSAEVENT hEvent

);

응용 프로그램에서 이벤트 객체의 사용을 마치고 제거하려면 다음 WSACloseEvent 함수를 이용해 이벤트 객체를 해제해 주어야 한다.

BOOL WSACloseEvent(

  WSAEVENT hEvent  

);

WSAAsyncSelect의 윈도우 프로시저에서 메시지를 처리하는 부분은 WSAWaitForMultipleEvents 함수를 통해 이루어지며 이 함수의 원형은 다음과 같다.

 

DWORD WSAWaitForMultipleEvents(

  DWORD cEvents,                 

  const WSAEVENT FAR *lphEvents, 

  BOOL fWaitAll,                 

  DWORD dwTimeOUT,               

  BOOL fAlertable                

);

WSAWaitForMultipleEvents 함수는 WIN32의 WaitForMultipleEvents 함수와 비슷한 기능을 하며 대부분의 인자 배열도 비슷하다. 단지 이 함수가 동시에 처리할 수 있는 이벤트 객체의 수는 64개로 고정되어 있다. 다시 말해 이 모델은 한 스레드에서 64개의 소켓까지만 지원할 수 있다.

WSAWaitForMultipleEvents 함수의 반환 값을 통해 어느 이벤트 객체가 시그널드 상태로 됐는지 알아낼 수 있고 다음의 WSAEnumNetworkEvents 함수를 통해 어떤 네트워크 이벤트가 발생했는지 조사할 수 있다.

int WSAEnumNetworkEvents (

  SOCKET s,                          

  WSAEVENT hEventObject,              

  LPWSANETWORKEVENTS lpNetworkEvents 

);

 

마지막 인자는 WSANETWORKEVENTS 구조체에 대한 포인터이며, 발생한 네트워크 이벤트와 에러 코드를 담고 있다. 그 구조는 다음과 같다.

 

 

typedef struct _WSANETWORKEVENTS {

   long lNetworkEvents;

   int iErrorCode[FD_MAX_EVENTS];

} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

구조체에서 네트워크 이벤트를 조사하면서 그 이벤트에 대하여 에러가 발생했는지 같이 검사해야 한다. 다음의 예제와 같이 FD_READ 이벤트가 발생한 경우 이에 해당하는 에러코드의 배열(FD_READ_BIT)도 검사해 동작이 성공했는지 알아봐야 한다.

 

if (NetworkEvents.lNetworkEvents & FD_READ) {

  if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0) {

    printf(“FD_ERROR failed with error %d \n”,

          NetworkEvents.iErrorCode[FD_READ_BIT]);

  }

}

 

리스트 3은 WSAEventSelect 모델을 사용하는 프로그램의 부분적인 코드이다.

 

리스트 2 : WSAAsyncSelect 모델 예제

main()

{

// 생략

 

   Listen = socket ()

 

   WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE);

 

   InternetAddr.sin_family = AF_INET;

   InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);

   InternetAddr.sin_port = htons(PORT);

 

   bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr));

 

   listen(Listen, 5);

                       

   while(Ret = GetMessage(&msg, NULL, 0, 0))

   {

      if (Ret == -1)

      {

         printf(“GetMessage() failed with error %d\n”, GetLastError());

         return;

      }

 

      TranslateMessage(&msg);

      DispatchMessage(&msg);

   }

} // main 함수

 

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

   SOCKET Accept;

   LPSOCKET_INFORMATION SocketInfo;

   DWORD RecvBytes, SendBytes;

   DWORD Flags;

 

   if (uMsg == WM_SOCKET)

   {

      if (WSAGETSELECTERROR(lParam))

      {

         printf(“Socket failed with error %d\n”, WSAGETSELECTERROR(lParam));

         FreeSocketInformation(wParam);

      }

      else

      {

         switch(WSAGETSELECTEVENT(lParam))

         {

            case FD_ACCEPT:

 

               Accept = accept(wParam, NULL, NULL));

 

               WSAAsyncSelect(Accept, hwnd, WM_SOCKET,

                            FD_READ|FD_WRITE|FD_CLOSE);

               break;

 

            case FD_READ:

               // 데이터 읽기 작업

               break;

 

            case FD_WRITE:

               // 데이터 쓰기 작업

               break;

 

            case FD_CLOSE:

                    closesocket(wParam);

               break;

         }

      }

      return 0;

   }

 

   return DefWindowProc(hwnd, uMsg, wParam, lParam);

}




'C/C++' 카테고리의 다른 글

[본문스크랩] [MSSQL] 함수  (0) 2014.05.02
[본문스크랩] [펌]초급 : 간단하게 OLEDB 사용하기  (0) 2014.05.02
WSAEventSelect  (0) 2014.05.02
WSASend  (0) 2014.05.02
소켓 입출력 모델 - Overlapped 모델  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:10 C/C++


WSAEventSelect

WSAEventSelect 함수는 FD_XXX 네트웍 이벤트와 연관되는 이벤트 객체를 지정하기위해 사용되는 함수입니다.

int WSAEventSelect (
        SOCKET  
s,
        WSAEVENT  
hEventObject,
        long  
lNetworkEvents
);

 

Parameters

s
[입력] 대상 소켓 기술자

hEventObject
[입력] FD_XXX 네트웍 이벤트와 연관되는 이벤트 객체를 명시한 핸들

lNetworkEvents
[입력] 어플리케이션에서 발생시키고자 하는 FD_XXX 네트웍 이멘트의 비트조합

 

Remarks

WSAEventSelect 함수는 선택된 FD_XXX 네트웍 이벤트(INetworkEvents)와 연관될 이벤트 객체(hEventObject)를 명시하는데 사용하는 함수입니다. 이벤트 객체가 명시될 소켓은 s 매개변수입니다. 이벤트 객체는 등록된 네트웍 이벤트가 발생했을 때 설정됩니다.

WSAEventSelect 함수는 WSAAsyncSelect 함수와 매우 유사하게 동작합니다. 차이점이라면, 등록된 네트웍 이벤트가 발생됐을 때 동작되는 부분이죠. WSAAsyncSelect 함수는 명시된 윈도즈 메시지가 메시지 큐에 포스팅 되는 형태이고, WSAEventSelect 함수는 관련된 이벤트 객체를 설정하고, 내부적인 네트웍 이벤트의 발생을 남기는 형태입니다.

어플리케이션은 대기하거나 네트웍 이벤트 객체를 폴링하기 위해 WSAWaitForMultipleEvents 함수를 사용할 수 있습니다. 그리구, WSAEnumNetworkEvents 함수를 사용하여 내부적으로 발생한 네트웍 이벤트의 내용을 얻어낼 수 있습니다. 즉, 이렇게 함으로써 발생한 네트웍 이벤트가 어떠한건지 알아낼 수 있다는 거죠.

WSAEventSelect 함수는 INetworkEvents 의 값과는 상관없이 자동으로 소켓 s 를 비동기 모드로 설정합니다. 소켓을 다시 동기모드로 되돌려놓는 방법에 대해 알고 싶으며, ioctlsocket/WSAIoctl 함수를 살펴보도록 하세요.

INetworkEvents 매개변수는 아래에 나열된 값들을 OR 연산해서 얻은 값으로 구성됩니다.

ValueMeaning
FD_READ데이터를 읽을(수신) 수 있다라는 통지를 받고자 할 때
FD_WRITE데이터를 쓸(송신) 수 있다라는 통지를 받고자 할 때
FD_OOBout-of-band 데이터의 수신에 대한 통지를 받고자 할 때
FD_ACCEPT접속요구에 대한 통지를 받고자 할 때
FD_CONNECT접속작업이나 다중 "join" 연산의 통지를 받고자 할 때
FD_CLOSE소켓이 닫혔다라는 통지를 받고자 할 때
FD_QOS소켓 Quality of Service(QOS)가 바뀌었다는 통지를 수신하고자 할 때
FD_GROUP_QOS소켓 Group Quality of Service(GROUP_QOS)가 바뀌었다는 통지를 수신하고자 할 때
FD_ROUTING
_INTERFACE_CHANGE
특정한 목적지에 대해서 라우팅 인터페이스가 변경되었다는 통지를 받고자 할 때
FD_ADDRESS_LIST
_CHANGE
소켓 어드레스 집합에 대해서 로컬 어드레스 리스트가 변경했다는 통지를 받고자 할 때

한 소켓에 대해서 WSAEventSelect 함수를 호출하게되면, 같은 소켓에 대해서 이전에 WSAAsyncSelect 함수나 WSAEventSelect 함수의 호출을 무시하게 되고, 이전에 설정되었던 내부적 네트웍 이벤트 등록을 없애버립니다. 한 예로, 읽기와 쓰기 네트웍 이벤트 양쪽을 받고자 하는 상태를 만들기 위해서는 아래의 코드와 같이 FD_READ와 FD_WRITE를 양쪽다 같이 설정해 주어야 합니다. 달리 말하자면, 먼저 FD_READ하고 나중에 FD_WRITE 한다고 읽기와 쓰기 모두에 대해서 통지 메시지를 받지는 않는다는 점을 꼭 기억하세요. 이렇게 하면, FD_READ는 무시되고 FD_WRITE만 통지 받게 됩니다.

rc = WSAEventSelect(s, hEventObject, FD_READ|FD_WRITE);

다른 네트웍 이벤트에 대해서 다른 이벤트 객체를 명시하는 방법은 불가능 합니다. 아래의 코드는 제대로 동작하지 않을 것입니다. 즉, 두 번째 구문의 호출이 첫 번째 구문의 호출의 효과를 무시해 버릴 것입니다. 그리고, 단지 FD_WRITE 네트웍 이벤트 만이 hEventObject2에 대해서 발생할 것입니다.

rc = WSAEventSelect(s, hEventObject1, FD_READ);
rc = WSAEventSelect(s, hEventObject2, FD_WRITE);

관련된 그리고 한 소켓에 대해서 선택된 네트웍 이벤트를 취소하기 위해서 INetworkEvent 를 0으로 설정해서 WSAEventSelect 함수를 호출하면 됩니다. 이렇게 하면, hEventObject 매개변수는 앞으로 무시될 것입니다.

rc = WSAEventSelect(s, hEventObject, 0);

closesocket 함수로 소켓을 종료하게 되면 소켓에 대해서 WSAEventSelect로 지정된 네트웍 이벤트를 취소하게 됩니다. 하지만, 어플리케이션은 반드시 WSACloseEvent 함수를 호출해서 이벤트 객체와 리소스를 반환 해 줘야 합니다.

리슨소켓으로 접속허용(accepted)되어 생성된 소켓은 리슨소켓과 같은 속성을 가지게 됩니다. 예를들어 리슨소켓이 FD_ACCEPT, FD_READ, 그리고 FD_WRITE 속성을 가지도록 설정되어 있다면, 접속허용된 소켓도 리슨 소켓과 같은 FD_ACCEPT, FD_READ, FD_WRITE 속성을 가지게 된다는 것입니다. 새로 생성된 접속허용된 소켓에 다른 네트웍 이벤트를 설정하고 싶으면, WSAEventSelect 함수를 호출하여 새로운 정보로 설정해 주어야 합니다.

selectWSAAsyncSelect 함수의 경우와 같이 WSAEventSelect 함수는 데이터 전송/수신 연산(send/recv)이 일어날 수 있는 시점을 결정하는데 자주 사용됩니다. 하지만, 100%정확하게 이러한 시점에서 데이터를 전송하거나 수신할 수 있는 것은 아닙니다. 예외적인 상황이 발생 할 수도 있다는 거죠. 예를들어 데이터를 수신해도 된다는 FD_READ 이벤트를 받고나서 데이터를 수신했을 때 WSAEWOULDBLOCK 이라는 에러가 발생 할 수도 있다는 의미입니다. 그러므로 튼튼한 어플리케이션을 만들려면, 이러한 예외적인 상황에 대비할 수 있는 준비를 해야 합니다. 아래에 이러한 상황이 일어날 수 있는 시나리오를 한번 생각해 봤습니다. 참고하세요.

1. 데이터가 소켓 s 에 도착 했습니다. 윈도즈 소켓은 WSAEventSelect 이벤트 객체를 설정합니다.

2. 어플리케이션은 몇몇 다른 작업을 진행하고 있는 도중입니다.

3. 작업을 진행하고 있는동안 ioctlsocket( s, FIONREAD...)를 호출했다고 해봅시다. 그러면, 읽을 수 있는 데이터가 있다는 사실을 알게 되겠죠.

4. 어플리케이션은 데이터를 읽기 위해서 recv(s, ...)를 호출 할 것입니다.

5. 하던 작업이 끝나면, 1.에서 설정된 이벤트 객체가 있으므로 데이터를 읽을 수 있는 상태라는 것을 알 수 있게 됩니다.

6. 어플리케이션은 데이터를 읽기 위해서 recv(s, ...)를 호출할 것이고, 이미 이 데이터는 읽혀졌지 때문에 WSAEWOULDBLOCK 에러를 내면서 실패 하게 됩니다.

이벤트 객체와 연관된 신호와 네트웍 이벤트의 발생을 계속 성공적으로 발생시키려면, 네트웍 이벤트가 통지 되었을 때 이벤트 객체와 연관된 재활성화(re-enabling) 함수를 명시적으로 호출해 주어야 합니다. 그래야만, 다음에 발생하는 네트웍 이벤트나 신호를 받을 수 있게 됩니다. 아래의 표에 각 네트웍 이벤트에 해당하는 재활성화 함수(re-enabling function)을 나열해 보았습니다.

Network EventRe-enabling function
FD_READrecv, recvfrom, WSARecv, 또는 WSARecvFrom
FD_WRITEsend, sendto, WSASend, 또는 WSASendTo
FD_OOBrecv, recvfrom, WSARecv, 또는 WSARecvFrom
FD_ACCEPTaccept 또는 WSAAccept 에러코드가 WSATRY_AGAIN이 발생했을 경우는 예외
FD_CONNECT없당
FD_CLOSE없당
FD_QOSSIO_GET_QOS 컴맨드로 호출된 WSAIoctl
FD_GROUP_QOSSIO_GET_GROUP_QOS 컴맨드로 호출된 WSAIoctl
FD_ROUTING
_INTERFACE_CHANGE
SIO_ROUTING_INTERFACE_CHANGE 컴맨드로 호출된 WSAIoctl
FD_ADDRESS_LIST
_CHANGE
SIO_ADDRESS_LIST_CHANGE 컴맨드로 호출된 WSAIoctl

FD_READ, FD_OOB, 그리고 FD_ACCEPT 이벤트에 대한 메시지 포스팅 방법은 "level-triggered"라고 합니다. 무슨 말이냐구요? 만약 재활성화 루틴이 호출되고, 이와 관련된 상태가 루틴 호출후에도 여전히 남아 있다면, 메시지는 어플리케이션에 다시 포스팅 됩니다. 이러한 포스팅 방법으로 어플리케이션은 메시지 구동 방식이 됩니다. 아직 이해가 잘 않되시죠? 아래의 순서를 한번 생각해 보세요... 지금 제가 한말을 쉽게 이해 할 수 있을 겁니다.

1. 네트웍 전송/수신 스택이 소켓에 100 바이트의 데이터를 수신받고, 윈속이 FD_READ 메시지를 포스팅 했습니다.

2. 어플리케이션이 recv( s, buffer, 50, 0 )을 호출해서 50바이트를 수신했습니다.

3. 읽을 데이터가 여전히 남아있는 동안 FD_READ 메시지가 포스팅 됩니다.

이러한 구조상에서, 어플리케이션은 하나의 FD_READ 메시지에 대해서 모든 가능한 데이터를 읽어낼 필요가 없습니다. 각각의 FD_READ 메시지에 대해서 하나의 recv를 사용하는 것이 적절한 방법입니다. 만약 어플리케이션이 하나의 FD_READ에 대해서 여러개의 recv를 호출할 경우, 여러개의 FD_READ 메시지를 수신할 수도 있습니다. 이러한 어플리케이션에서는 recv함수를 호출하기전에 WSAAsyncSelect 함수를 FD_READ 이벤트를 셋팅하지 않는 방법으로 FD_READ 메시지 수신을 막아 버려야할 필요가 있습니다.

FD_QOS 그리고 FD_GROUP_QOS 이벤트는 "edge triggered"로 간주됩니다. 메시지는 Quality Of Service가 바뀌었을 때 정확히 한번 포스팅 됩니다. 게다가 메시지는 프로바이더가 QOS에서의 다른 변화를 감지하든지, 아니면, 어플리케이션이 소켓에 대한 QOS를 재설정할 때 까지 메시지는 수신되지 않습니다.

FD_ROUTING_INTERFACE_CHANGE 메시지는 SIO_ROUTING_INTERFACE_CHANGE 컴맨드로 WSAIoctl 함수가 호출될 때 포스팅 됩니다.

FD_ADDRESS_LIST_CHANGE 메시지는 SIO_ADDRESS_LIST_CHANGE 컴맨드로 WSAIoctl 함수가 호출될 때 포스팅 됩니다.

만약 어플리케이션이 WSAAsyncSelect함수를 호출하거나 재활성(re-enabling)함수를 호출했을 때 이미 이벤트가 발생되었다면, 이때의 메시지는 적절히 포스팅 됩니다. 이해를 쉽게 하기위해 아래의 일어날 수 순서 상황을 생각해 보세요.

1. 어플리케이션이 생성한 소켓에 대해서 listen 함수를 호출 했습니다.

2. 접속 요구가 들어왔으나 아직 accept 하지 않았습니다.

3. 어플리케이션은 이 소켓에 대해서 FD_ACCEPT로 WSAAsyncSelect 함수를 호출하였습니다. 이때 이벤트의 영속성이 보존되기 때문에 윈속은 FD_ACCEPT 메시지를 바로 포스팅 하게 됩니다.

FD_WRITE 이벤트는 약간 다르게 운용됩니다. 즉, 소켓이 connect/WSAConnect 함수의 호출로 첨 접속되었을 때, 또는 accept/WSAAccept 함수로 접속허용되었을 때, 또는, send 연산이 WSAEWOULDBLOCK 으로 실패(비동기소켓은 항상 WSAEWOULDBLOCK입니다)한 상태에서 전송용 버퍼공간이 사용 가능해졌을 때 FD_WRITE 메시지는 포스팅 됩니다. 따라서 어플리케이션은 FD_WRITE 메시지를 받았을 때 데이터의 전송이 가능하다고 판단 할 수 있습니다.

FD_OOB 이벤트는 소켓이 out-of-band 데이터를 부분적으로 수신하여 나열할 때만 사용됩니다. 만약 소켓이 out-of-band 데이터를 인라인 방식으로 수신하도록 나열 한다면, out-of-band데이터는 일반 데이터와 같이 다루어 지게 됩니다. 이때, 어플리케이션은 interest(?)를 등록해야 합니다. 일반데이터와 같이 다루어 진다는 의미는 뭔지 아시겠죠? FD_OOB 이벤트가 아닌 FD_READ이벤트가 발생했을 때 데이터를 수신한다는 의미입니다. 어플리케이션은 out-of-band 데이터가 SO_OOBINLINE 옵션으로 setsockopt함수나 getsockopt함수를 사용하여 핸들링 하는 방법을 설정할 수 있습니다.

FD_CLOSE 메시지의 에러코드는 소켓이 우아한닫힘(graceful close)인지 그렇지 않은 닫힘인지를 나타냅니다. 만약 에러코드가 0이라면, 소켓은 우아한닫힘 이라는 의미이고, 에러코드가 WSAECONNRESET 이라면, 소켓의 가상 회선망이 리셋되었다는 것을 의미합니다. 이러한 것들은 SOCK_STREAM과 같은 접속 지향형 소켓일 경우에만 사용될 수 있는 것들입니다.

FD_CLOSE 메시지는 소켓과 연관된 가상 회선망이 끊겼다는 것을 수신받았을 때 포스팅 됩니다. TCP 의 경우는 접속이 TIME WAIT 나 CLOSE WAIT 상태로 들어갔을 때 FD_CLOSE 메시지가 포스팅 되게 됩니다. 즉, 원격 종단점에서의 전송영역의 셧다운이나 closesocket 함수의 호출의 결과로 일어날 수 있는 메시지입니다. FD_CLOSE 메시지는 모든 데이터가 소켓으로부터 읽혀지고 난 후에 포스팅 됩니다 그렇긴 해도, 최대한 손실된 데이터들을 피하고자 한다면, FD_CLOSE 메시지를 받은 후에 남아있는 데이터들을 체크하는 것이 좋겠죠?

 

Return Values

지정된 네트웍 이벤트와 이벤트 객체가 성공적으로 설정되었으면, 함수는 0을 반환 합니다. 에러가 발생한 경우 SOCKET_ERROR을 리턴하고, WSAGetLastError 함수를 호출하여 특정한 에러코드를 얻어낼 수 있습니다.

 

Error Codes

WSANOTINITIALISED

이 함수를 사용하기 이전에 WSAStartup 함수를 성공적으로 호출해야 합니다.

WSAENETDOWN

네트웍 서브 시스템에서 에러가 났습니다.

WSAEINVAL함수의 매개변수가 잘못 사용되었습니다.
WSAEINPROGRESS

블럭킹 윈속 v1.1이 진행상태에 있거나, 서비스 프로바이더가 아직 콜백함수를 진행하고 있습니다.

WSAENOTSOCK지정된 소켓기술자가 소켓이 아닙니다.

 

QuickInfo

Windows NT : 사용가능
Windows : 사용가능
Windows CE : 지원되지 않음
Header :
          Win16/32 : winsock.h
          Win32-II : winsock2.h
Import Library :
          Win16 : winsock.lib
          Win32 : wsock32.lib
          Win32-II : ws2_32.lib

See Also

overview, WSAAsyncSelect, WSACloseEvent, WSACreateEvent, WSAEnumNetworkEvents, WSAWaitForMultipleEvents





'C/C++' 카테고리의 다른 글

[본문스크랩] [펌]초급 : 간단하게 OLEDB 사용하기  (0) 2014.05.02
WSAEventSelect 모델  (0) 2014.05.02
WSASend  (0) 2014.05.02
소켓 입출력 모델 - Overlapped 모델  (0) 2014.05.02
iocp주의점  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:10 C/C++



WSASend WinSock

2010/03/20 02:04 수정 삭제

작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130082711115

WSASend

WSASend 함수는 접속된 상대방 소켓에게 지정한 데이터를 보내는 함수입니다.

int WSASend (
        SOCKET   
s,
        LPWSABUF   
lpBuffers,
        DWORD   
dwBufferCount,
        LPDWORD   
lpNumberOfBytesSent,
        DWORD   
dwFlags,
        LPWSAOVERLAPPED   
lpOverlapped,
        LPWSAOVERLAPPED_COMPLETION_ROUTINE   
lpCompletionROUTINE
);

 

Parameters

s
[입력] 접속된 소켓을 가리키는 소켓 기술자

lpBuffers
[입력]
WSABUF 구조체의 포인터. WSABUF 구조체는 버퍼의 포인터와 퍼퍼의 길이를 나타내는 멤버로 구성되는 구조체로 이 구조체의 배열을 포인트 해서 넘겨줍니다.

dwBufferCount
[입력] lpBuffer 배열로 지정된
WSABUF 구조체의 갯수

lpNumberOfBytesSent
[출력] 이 함수의 호출로 인해서 전송된 바이트의 갯수를 포인트 합니다.

dwFlags
[입력] 어떻한 방식으로 전송을 수행 할 것인지 설정하는 플래그

lpOverlapped
[입력]
WSAOVERLAPPED 구조체의 포인터 ("넌오버랩" 소켓에 대해서는 무시됩니다.)

lpCompletionRoutine
[입력] 전송연산이 완료되었을 때 호출되는 전송완료 루틴에 대한 포인터입니다. ("넌오버랩" 소켓에서는 무시됩니다.)

 

Remarks

WSASend 함수는 기존의 send 함수의 기능을 모두 다 수행 하면서, 아래의 두가지 항목을 추가적으로 지원합니다.

    1. 중복된 전송 연산을 수행하도록 중복(overlapped)소켓을 가지고 작업할 수 있는 기능

    2. 여러개의 전송 버퍼를 두어 전송 할 수 있는 기능

WSASend 함수는 s 매개변수로 로 지정된 접속지향형 소켓에서 한 개 또는 그 이상의 데이터를 전송 하기위해서 사용하는 함수입니다. 이 함수는 비접속 지향형 소켓(SOCK_DGRAM 과 같은 소켓이겠죠?) 에서도 사용 가능합니다. 하지만, 이렇게 사용하려 하는 경우에, 소켓은 connect 함수나 WSAConnect 함수에 의해서 상대방 어드레스가 지정되어 있어야 합니다.

오버랩 소켓(WSASocket 함수를 WSA_FLAG_OVERLAPPED 플래그를 두어 생성한 소켓)은 오버랩 입/출력을 사용하여 정보를 전송하게 됩니다. 하지만, lpOverlappedlpCompleteRoutine 매개변수가 NULL인 경우 소켓은 넌-오버랩 소켓으로 간주되어 처리되게 됩니다.

전송하는데 지정된 버퍼가 다 사용되었을 때 완료루틴이 호출되거나, 이벤트 객체가 셋팅되는 방식으로 작업이 완료 되었다는 것을 알 수 있습니다. 만약 작업이 바로 완료되지 않는 경우엔 완료루틴이나, WSAGetOverlappedResult 함수에 의해서 마지막 완료 상태를 얻어낼 수 있습니다.

넌-오버랩 소켓에서는 마지막 두 매개변수(lpOverlapped, lpCompletetionRoutine)은 무시됩니다. 그리고, WSASend 함수는 send 함수와 같은 동작을 하도록 사용되어 집니다. 데이터는 지정된 버퍼로부터 전송버퍼로 복사됩니다. 만약 소켓이 비동기 모드인 스트림 소켓이고, 전송버퍼에 충분한 공간이 남아있지 않다면, WSASend 함수는 전송하는데 사용되었던 버퍼 만큼만을 반환 할 것입니다. 블록킹 소켓의 경우는 전송버퍼 공간이 남아서 지정된 버퍼를 전송버퍼로 다 복사 할 때 까지 블록킹 됩니다.

lpBuffers 매개변수에 의해서 포인트되는 WSABUF 구조체 배열은 일시적인 공간입니다. 전송연산이 오버랩 처리를 완료했다면, 이 공간은 서비스 프로바이더의 책임입니다. 즉, WSASend 함수가 반환하기전에 이 WSABUF 구조체를 서비스 프로바이더가 캡쳐해야 하는 것이죠. 이러한 처리방식은 WSABUF 배열을 스택기반으로 이루어질 수 있도록 하게 됩니다.

메시지 지향형 소켓(비접속 지향형 소켓)의 경우 서비스 프로바이더에서 지원할 수 있는 메시지의 최대 크기를 넘지 않도록 조심해야 합니다. 이 최대값은 SO_MAX_MSG_SIZE 옵션으로 얻어낼 수 있죠. 만약 전송할 데이터가 전송하기에 너무 큰데이터라면, WSAEMSGSIZE 라는 에러를 반환 하게 됩니다. 글구... 아무런 데이터도 전송되지 않게 됩니다.

Note : WSASend 함수를 성공적으로 수행 했다고 해서 데이터가 성공적으로 상대방에게 전송되었다고 확신 할 수는 없습니다.

dwFlags 매개변수는 함수를 호출했을 때 어떻게 데이터를 전송할 것인지 세부적인 함수의 행동을 설정하는데 사용하는 값입니다. 즉, 이 함수의 동작을 결정하는 것은 소켓 옵션과 dwFlags 매개변수라고 할 수 있습니다. dwFlag 매개변수에 들어가는 값은 아래에 제시한 값들을 OR 로 구성하여 넘겨주면 됩니다.

ValueMeaning
MSG_DONTROUTE데이터를 라우팅하도록 하지 않도록 명시합니다. 윈도즈 소켓 서비스 프로바이더는 이 플래그를 무시할 수 있습니다.
MSG_OOBSOCK_STREAM으로 생성한 연결지향형 소켓에서 out-of-band 데이터를 전송하는데 사용되는 값입니다.
MSG_PARTIALlpBuffers 매개변수가 부분적인 메시지만을 담도록 명시합니다. 만약 부분적인 메시지 전송을 지원하지 않는 경우에는 WSAEOPNOTSUPP 에러코드를 반환 할 것입니다.

 

Overlapped socket I/O

오버랩 연산이 바로 완료 되었다면, WSASend 함수는 0 값을 반환 합니다. 그리고, lpNumberOfBytesSent 매개변수는 전송된 바이트의 수를 포인트 합니다. 오버랩 연산이 성공적으로 시작되었으나, 나중에 완료될 것이라면, WSASend 함수는 SOCKET_ERROR을 반환하고, WSA_IO_PENDING 이라는 에러코드를 발생할 것입니다. 이러한 경우에 lpNumberOfBytesSent 매개변수는 전송된 값을 포인트 하지 않습니다. 오버랩 연산이 완료되었을 때, 전송된 데이터의 수치는 완료루틴의 cbTransferred 매개변수나 WSAGetOverlappedResult 함수의 lpcbTransfer 매개변수를 가지고 알 수 있습니다.

WSASend 함수는 이전에 호출한 WSARecv, WSARecvFrom, WSASend, WSASendTo 함수들의 완료함수 내부에서 호출될 수 있습니다.

만약 여러개의 I/O 연산이 동시에 일어난 다면, 각각의 연산은 서로다른 WSAOVERLAPPED 구조체를 참조해야 합니다.

lpCompletionRoutine 매개변수가 NULL이라면, lpOverlapped 매개변수의 hEvent 필드는 오버랩 연산이 완료 되었을 때 신호를 받게 됩니다. 어플레케이션은 WSAWaitForMultipleEventsWSAGetOverlappedResult 함수를 사용해서 이벤트 객체에 대해서 대기하거나 폴링할 수 있습니다.

lpCompletionRoutine 매개변수가 NULL이 아니 이라면, hEvent 필드는 무시되고, 완료루틴을 위한 흐름정보(context information)를 사용할 수 있게 됩니다. 이렇게 NULL 이 아닌 lpCompletionRoutine 을 넘겨주는 콜러와 나중에 같은 오버랩 I/O 요청을 위해 호출되는 WSAGetOverlappedResult 함수는 WSAGetOverlappedResult 함수를 TRUE로 호출하기위한 fWait 맵개변수를 셋팅하지 않습니다. 이러한 경우에 hEvent 필드를 사용하는 특별한 방법은 정의되지 않습니다. 그리고, hEvent 필드를 사용해 대기하려 하면, 예측할 수 없는 결과를 가져 올 수도 있습니다.

완료루틴은 Win32 파일 I/O 완료루틴과 같은 규칙을 따르게 됩니다. 완료루틴은 WSAWaitForMultipleEvents 함수가 fAlertable 매개변수를 TRUE로 설정하여 호출될 때와 같이 작업쓰레드가 반응할수있는(alertable) 대기 상태에 있을 때 까지 호출되지 않을 것입니다.

완료루틴의 프로토타입은 아래와 같습니다.

void CALLBACK CompletionROUTINE(
        IN DWORD  
dwError,
        IN DWORD  
cbTransferred,
        IN LPWSAOVERLAPPED  
lpOverlapped,
        IN DWORD  
dwFlags
);

CompletionRoutine 함수는 어플리케이션-정의 또는 라이브러리 정의 함수 이름으로 사용됩니다. dwError 매개변수는 lpOverlapped 에 의해서 표현되는 오버랩 연산에 대한 완료의 상태를 가리킵니다. cbTransferred 매개변수는 전송한 데이터의 바이트 수를 나타냅니다. 일반적으로 dwFlags 매개변수는 잘 사용되지 않습니다. 이값은 0 값을 가지게 될 것입니다. 이 함수는 반환값이 없습니다.

 

Return Values

에러가 발생하지 않고, 전송연산이 바로 완료되었다면, WSASend 함수는 0을 반환 합니다. 이러한 경우에 완료루틴은 호출쓰레드가 반응할 수 있는(alertable) 상태로 호출되기 위한 준비를 이미 같추었다고 볼 수 있습니다. 에러가 발생한 경우 SOCKET_ERROR 값이 반환 됩니다. 그리고, WSAGetLastError 함수를 호출해 특정한 에러코드를 얻어낼 수 있습니다.

 

Error Codes

WSANOTINITIALISED

이 함수를 사용하기 이전에 WSAStartup 함수를 성공적으로 호출해야 합니다.

WSAENETDOWN

네트웍 서브 시스템에 에러가 발생했습니다.

WSAEACCES지정된 어드레스는 브로드캐스팅 어드레스 인데, 적절한 플래그가 사용되지 않았습니다.
WSAEINTR블럭킹 윈속 v1.1 이 WSACancelBlockingCall 함수에서 취소되었습니다
WSAEINPROGRESS

블럭킹 윈속 v1.1 이 현재 진행 중입니다.

WSAEFAULTlpBuffers, lpNumberOfBytesSent, lpOverlapped, lpCompletionRoutine 매개변수가 올바른 어드레스를 넘기지 않았습니다.
WSAENETRESET작업이 진행되고 있는 도중에 에러상황이 검출되어서 접속이 해제 되었습니다.
WSAENOBUFS버퍼 사용중 데드락에 걸렸습니다.
WSAENOTCONN지정된 소켓이 접속된 소켓이 아닙니다.
WSAENOTSOCK지정한 소켓 기술자가 소켓기술자가 아닙니다.
WSAEOPNOTSUPPMSG_OOB 플래그가 지정되었으나, 소켓이 SOCK_STREAM 과 같은 타입의 스트림 형태의 소켓이 아닌 경우 이거나, 소켓이 out-of-band 데이터를 지원하지 않는 형태의 소켓입니다. 그리고, MSG_PARTIAL 형태가 지원되지 않거나, 소켓이 단방향 이어서 데이터 수신 연산만을 지원하는 형태입니다.
WSAESHUTDOWN소켓이 셧다운 되었습니다. 소켓에 대해서 shutdown 함수를 호출한 후에는 WSASend 함수를 사용할 수 없습니다.
WSAEWOULDBLOCK오버랩 소켓      : 너무많은 오버랩 I/O가 발생했습니다.
넌-오버랩 소켓 : 소켓이 비동기 소켓일 경우 전송 연산이 바로 완료될 수 없습니다. ( 언젠가는 완료가 될 것이므로 확실한 에러상황이라고 볼 수 없죠.)
WSAEMSGSIZE소켓이 메시지지향 소켓(SOCK_DGRAM)일때, 전송하려는 데이터의 크기가 지원되는 최대 크기보다 더 큽니다.
WSAEINVAL소켓이 bind 함수로 바인드 되지 않았거나, 소켓이 오버랩 플래그를 사용해서 생성되지 않았습니다.
WSAECONNABORTED가상 연결망이 타임아웃이나 그외 다른 에러상황으로 끊겨 버렸습니다.
WSAECONNRESET가상 연결망이 상대방에 의해서 리셋되었습니다.
WSA_IO_PENDING오버랩 연산이 성공적으로 시작되었고, 작업이 잠시 후에 완료될 예정입니다.
WSA_OPERATION_ABORTED오버랩 연산이 소켓의 닫힘 때문에 취소되었거나, SO_FLUSH 컴맨드로 WSAIoctl 함수를 수행해서 연산이 도중에 취소 되었습니다.

 

QuickInfo

Windows NT : 사용가능
Windows : 사용가능
Windows CE : 지원되지 않음
Header :
          Win16/32 : winsock.h
          Win32-II : winsock2.h
Import Library :
          Win16 : winsock.lib
          Win32 : wsock32.lib
          Win32-II : ws2_32.lib

See Also

overview, WSACloseEvent, WSACreateEvent, WSAGetOverlappedResult, WSASocket, WSAWaitForMultipleEvents




'C/C++' 카테고리의 다른 글

WSAEventSelect 모델  (0) 2014.05.02
WSAEventSelect  (0) 2014.05.02
소켓 입출력 모델 - Overlapped 모델  (0) 2014.05.02
iocp주의점  (0) 2014.05.02
중첩된 io  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:09 C/C++


소켓 입출력 모델 - Overlapped 모델

 

 

[동기 입출력(synchronous I/O)]

 

애플리캐이션은 입출력 함수를 호출한 후 입출력 작업이 끝날때 까지 대기하다 끝나면 입출력 결과를 처리하거나 다른 작업을 할수 있다

Select, WSAAsyncSelect, WSAEventSelect 소켓 모델은 모두 동기 입출력 방식으로 동작.  단 입출력 함수를 안전하게 호출할수

있는 시점을 운영체제가 알려주기 때문에 단순한 동기 입출력 방식보다 편하게 여러 소켓을 처리할수 있다

- 이와 같이 운영체제가 함수 호출 시점을 알려주는 개념을 비동기 통지라고 부른다.

 

 

[비동기 입출력(asynchronous I/O = overlapped I/O)]

 

어플리캐이션은 입출력 함수를 호출한 후 입출력 작업의 완료 여부와 무관하게 다른 작업을 할 수 있다

입출력 작업이 끝나면 운영체제는 작업 완료를 어플리캐이션에게 알려준다

비동기 입출력 방식에서는 입출력 완료를 운영체제에서 알려주는 개념이 반드시 필요하므로 비동기 통지도 사용한다고 볼수 있다

- Overlapped, Completion Port(IOCP)는 비동기 입출력과 비동기 통지를 결합한 형태라 할수 있다

 

 

[Overlapeed 모델]

 

* 사용 절차

 

1. 비동기 입출력을 지원하는 소켓을 생성

  - socket() 함수로 생성한 소켓은 기본적으로 비동기 입출력을 지원한다

 

2. 비동기 입출력을 지원하는 소켓 함수 호출(총 13개의 함수)

  - AcceptEx(), ConnectEx(), DisconnectEx(), TransmitFile(), TransmitPackets(), WSAIoctl(), WSASPIoctl(),

    WSAProviderConfigChange(), WSARecvMsg(), WSARecv(), WSARecvFrom(), WSASend(), WSASendTo()

 

3. 운영체제는 소켓 입출력 작업 완료를 애플리캐이션에게 알려주고(=비동기 통지), 애플리케이션은 결과를 처리

 

 

* 비동기 통지 방식에 따른 Overlapped 모델 분류

 

1. Overlapped 모델( I )

: 소켓 입출력 작업이 완료되면 운영체제는 애플리케이션이 등록한 이벤트 객체를 신호상태로 바꾼다

 애플리케이션은 이벤트 객체를 관찰함으로써 작업 완료를 감지할수 있다

 

2. Overlaped 모델( II )

: 소켓 입출력 작업이 완료되면 운영체제는 애플리케이션이 등록한 함수를 자동으로 호출한다

  일반적으로 운영체제가 호출하는 애플리케이션 함수를 콜백 함수(callback function) 라 부르는데.

  특별히 Overlapped 모델에서는 완료 루틴(completion routine) 이라 칭한다

 

 

[WSASend(), WSARecv()]

 

int WSASend(

    SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,

    LPDWORD lpNumberOfBytesSent,

    DWORD dwFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoution

);    성공 : 0.   실패 : SOCKET_ERROR

 

int WSARecv(

    SOCKET s, LPWSABUF lpBUffers, DWORD dwBufferCount,

    LPDWORD lpNumberOfBytesRecvd,

    LPDWORD lpFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);    성공 : 0.    실패 : SOCKET_ERROR

 

s : 비동기 입출력을 할 소켓

lpBuffers : WSABUF 구조체 배열의 시작주소. 각각의 배열 원소(WSABUF 타입)는  버퍼의 시작 주소와 길이(바이트단위)를 담고 있다

dwBufferCount : WSABUF 구조체 배열의 원소개수

lpNumberOfBytesSent, lpNumberOfBytesRecvd

 : DWORD 형 변수 주소값으로 함수 호출이 성공하면 이 변수에 보내거나 받은 바이트가 저장된다

dwFlags, lpFlags : send(), recv() 함수의 마지막 인자와 동일한 역활

lpOverlapped

 : WSAOVERLAPPED 구조체 변수의 주소값

   이 구조체는 비동기 입출력을 위한 정보를 운영체제에 전달하거나 운영체제가 비동기 입출력 결과를 애플리케이션에 전달할때 사용

   WSAOVERLAPPED 구조체 변수중 처음 네 개는 운영체제가 내부적으로만 사용.

   마지막 변수인 hEvent 는 이벤트 객체 핸들값으로 Overlapped 모델( I ) 에서만 사용한다.

   입출력 작업이 완료되면 hEvent 가 가리키는 이벤트 객체는 신호상태가 된다.

lpCompletionRoutine :  입출력 작업이 완료되면 운영체제가 자동으로 호출할 완료루틴(콜백함수)의 주소값

 

 

* WSASend(), WSARecv() 함수 특징

 

송신측에서 WSABUF 구조체를 사용하면 여려개의 버퍼에 저장된 데이터를 모아서(Gather) 보낼 수 있다.

수신측에서도 역시 WSABUF 구조체를 사용하면 여러개의 버퍼에 흩어(Scatter)저장할 수 있다.

 

buf[128];

buf[256];

WSABUF wsabuf[2];

wsabuf[0].buf = buf1;

wsabuf[0].len = 128;

wsabuf[1].buf = buf2;

wsabuf[1].len = 256;

 

// 송신측 코드

WSASend(sock, wsabuf, 2, ...);

 

// 수신측 코드

WSARecv(sock, wsabuf, 2, ...);




'C/C++' 카테고리의 다른 글

WSAEventSelect  (0) 2014.05.02
WSASend  (0) 2014.05.02
iocp주의점  (0) 2014.05.02
중첩된 io  (0) 2014.05.02
IOCP예제  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:08 C/C++


현재 개발중인 게임 서버의 소켓이 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 네트워크 클래스를 제작할 수 있었다.
  




'C/C++' 카테고리의 다른 글

WSASend  (0) 2014.05.02
소켓 입출력 모델 - Overlapped 모델  (0) 2014.05.02
중첩된 io  (0) 2014.05.02
IOCP예제  (0) 2014.05.02
[본문스크랩] 파일의 CRC32 값 구하기 프로그래밍팁  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:06 C/C++


*동작원리

1.비동기 입출력 함수를 호출함으로써 운영체제에 입출력 작업을 요청한다.
2.해당 스레드는 곧바로 alertable wait 상태에 진입한다. 여기서 alertable wait 상태
란 비동기 입출력을 위한 특별한 대기 상태로, 비동기 입출력 함수를 호출한 스레드 이
상태에 있어야만 완료 루틴이 호출될수 있다. 스레드를 alertable wait 상태로 만드는
함수는 다양하다. 몇 가지 예를 들면 WaitForSingleObjectEx(),WaitForMulitipleOvjectsEx()
,SleepEx(),WSAWaitForMultipleEvent()등이 있다. 마지막 함수인 WSAWaitForMultipleEvents()
는 WSAEventSelect모델과 Overlapped모델(I)에서 다룸, 마지막 인자에 TRUE를 사용하면 해
당 스레드는 alertable wait상태가 된다.
3.비동기 입출력 작업이 완료되면 운영체제는 스레드의 APC큐에 결과를 저장한다.
여기서 APC큐(Asynchronous Procedure Call Queue)란 비동기 입출력 결과 저장을 위해
운영체제가 각 스레드마다 할당하는 메모리 영역이다.
4.비동기 입출력 함수를 호출한 스레드가 alertable wait 상태에 있으면 운영체제는APC
큐에 저장된 정보(완료 루틴의 주소)를 참조하여 완료 루틴을 호출한다. 완료 루틴
내부에서 는 데이터를 처리한 후 다시 alertable wait상태에 진입해야한다.


========================= 소켓 입출력 절차 ====================================

1.비동기 입출력을 지원하는 소켓을 생성한다.
2.비동기 입출력 함수를 호출한다. 이때 완료 루틴의 시작 주소를 함수 인자로 전달
한다. 비동기 입출력 작업이 곧바로 완료되지 않으면, 소켓 함수는 오류를 리턴하고
오류코드는 WSA_IO_PENDING으로 설정된다.
3.비동기 입출력 함수를 호출한 스레드를 alertable wait상태로 만든다. 앞에서 소개한
WaitForSingleObjectEx(), WaitForMultipleObjectEx(),SleepEx(),WsaWaitForMultipleEvents()
등의 함수 중에서 적절한 것을 선택하여 사용하면 된다.

4.비동기 입출력 작업이 완료되면 운영체제는 완료 루틴을 호출한다. 완료 루틴에서는
비동기 입출력 결과를 확인하고 데이터를 처리한다.

5. 완료 루틴 호출이 모두 끝나면 스레드는 alertable wait 상태에서 빠져나온다

6.새로운 소켓을 생성하면 1~5 그렇지 않으면 2~5를 반복한다.

========================= 스레드를 alertable wait 상태로 바꾸는 함수)

void CALLBACK CompletionRoutine(
     DWORD dwError,
     DWORD cbTransferred,
     LPWSAOVERLAPPED lpOverlapped,
     DWORD dwFlags);

dwError:비동기 입출력 결과를 나타낸다. 오류가 발생하면 이 값은 0이 아닌 값이된다.
cbTransferred:전송바이트 수를 나타낸다. 통신 상대가 접속을 종료하면 이 값은 0이된다.
lpOverlapped:비동기 입출력 함수 호출 시 넘겨준 WSAOVERLAPPED구조체의 주소값이 이
인자를 통해 다시 애플리케이션에 넘어온다. Overlapped모델(II)에서는 이벤트 객체
를 사용하지 않으므로 WSAOVERLAPPED 구조체를 완료 루틴 내부에서 직접 사용할 일은 거의 없다.
dwFlags:항상 0이므로 적어도 현재까지는 사용하지 않음




'C/C++' 카테고리의 다른 글

WSASend  (0) 2014.05.02
소켓 입출력 모델 - Overlapped 모델  (0) 2014.05.02
iocp주의점  (0) 2014.05.02
IOCP예제  (0) 2014.05.02
[본문스크랩] 파일의 CRC32 값 구하기 프로그래밍팁  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:06 C/C++


========================== 예제 소스 코드 ==============================
 
#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>
 
#define BUFSIZE 512
 
// 소켓 정보 저장을 위한 구조체
struct SOCKETINFO
{
WSAOVERLAPPED overlapped;
SOCKET sock;
char buf[BUFSIZE+1];
int recvbytes;
int sendbytes;
WSABUF wsabuf;
};
 
SOCKET client_sock;
 
// 소켓 입출력 함수
DWORD WINAPI WorkerThread(LPVOID arg);
void CALLBACK CompletionRoutine(
DWORD dwError, DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags
);
// 오류 출력 함수
void err_quit(char *msg);
void err_display(char *msg);
void err_display(int errcode);
 
int main(int argc, char* argv[])
{
int retval;
 
// 윈속 초기화
WSADATA wsa;
if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)
  return -1;
 
// socket()
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock == INVALID_SOCKET) err_quit("socket()");
 
// bind()
SOCKADDR_IN serveraddr;
ZeroMemory(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(9000);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
retval = bind(listen_sock, (SOCKADDR *)&serveraddr, sizeof(serveraddr));
if(retval == SOCKET_ERROR) err_quit("bind()");
 
// listen()
retval = listen(listen_sock, SOMAXCONN);
if(retval == SOCKET_ERROR) err_quit("listen()");
 
// 이벤트 객체 생성
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(hEvent == NULL) return -1;
 
// 스레드 생성
DWORD ThreadId;
HANDLE hThread = CreateThread(NULL, 0, WorkerThread,
  (LPVOID)hEvent, 0, &ThreadId);
if(hThread == NULL) return -1;
CloseHandle(hThread);
 
while(1){
  // accept()
  client_sock = accept(listen_sock, NULL, NULL);
  if(client_sock == INVALID_SOCKET){
   err_display("accept()");
   continue;
  } 
  if(!SetEvent(hEvent)) break;
}
 
// 윈속 종료
WSACleanup();
return 0;
}
 
// 스레드 함수
DWORD WINAPI WorkerThread(LPVOID arg)
{
HANDLE hEvent = (HANDLE)arg;
int retval;
 
while(1){
  while(1){
   // alertable wait
   DWORD result = WaitForSingleObjectEx(hEvent, INFINITE, TRUE);
   if(result == WAIT_OBJECT_0) break;
   if(result != WAIT_IO_COMPLETION) return -1;
  }
 
  // 접속한 클라이언트 정보 출력
  SOCKADDR_IN clientaddr;
  int addrlen = sizeof(clientaddr);
  getpeername(client_sock, (SOCKADDR *)&clientaddr, &addrlen);
  printf("[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
   inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
 
  // 소켓 정보 구조체 할당과 초기화
  SOCKETINFO *ptr = new SOCKETINFO;
  if(ptr == NULL){
   printf("[오류] 메모리가 부족합니다!\n");
   return -1;
  }
  ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
  ptr->sock = client_sock;
  ptr->recvbytes = 0;
  ptr->sendbytes = 0;
  ptr->wsabuf.buf = ptr->buf;
  ptr->wsabuf.len = BUFSIZE;
 
  // 비동기 입출력 시작
  DWORD recvbytes;
  DWORD flags = 0;
  retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1, &recvbytes,
   &flags, &(ptr->overlapped), CompletionRoutine);
  if(retval == SOCKET_ERROR){
   if(WSAGetLastError() != WSA_IO_PENDING){
    err_display("WSARecv()");
    return -1;
   }
  }
}
 
return 0;
}
 
// 완료 루틴
void CALLBACK CompletionRoutine(
DWORD dwError, DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
int retval;
 
// 클라이언트 정보 얻기
SOCKETINFO *ptr = (SOCKETINFO *)lpOverlapped;
SOCKADDR_IN clientaddr;
int addrlen = sizeof(clientaddr);
getpeername(ptr->sock, (SOCKADDR *)&clientaddr, &addrlen);
 
// 비동기 입출력 결과 확인
if(dwError != 0 || cbTransferred == 0){
  if(dwError != 0) err_display(dwError);
  closesocket(ptr->sock);
  printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
   inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
  delete ptr;
  return;
}
 
// 데이터 전송량 갱신
if(ptr->recvbytes == 0){
  ptr->recvbytes = cbTransferred;
  ptr->sendbytes = 0;
  // 받은 데이터 출력
  ptr->buf[ptr->recvbytes] = '\0';
  printf("[TCP/%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr),
   ntohs(clientaddr.sin_port), ptr->buf);
}
else{
  ptr->sendbytes += cbTransferred;
}
 
if(ptr->recvbytes > ptr->sendbytes){
  // 데이터 보내기
  ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
  ptr->wsabuf.buf = ptr->buf + ptr->sendbytes;
  ptr->wsabuf.len = ptr->recvbytes - ptr->sendbytes;
 
  DWORD sendbytes;
  retval = WSASend(ptr->sock, &(ptr->wsabuf), 1, &sendbytes,
   0, &(ptr->overlapped), CompletionRoutine);
  if(retval == SOCKET_ERROR){
   if(WSAGetLastError() != WSA_IO_PENDING){
    err_display("WSASend()");
    return;
   }
  }
}
else{
  ptr->recvbytes = 0;
 
  // 데이터 받기
  ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
  ptr->wsabuf.buf = ptr->buf;
  ptr->wsabuf.len = BUFSIZE;
 
  DWORD recvbytes;
  DWORD flags = 0;
  retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1, &recvbytes,
   &flags, &(ptr->overlapped), CompletionRoutine);
  if(retval == SOCKET_ERROR){
   if(WSAGetLastError() != WSA_IO_PENDING){
    err_display("WSARecv()");
    return;
   }
  }
}
}
 
// 소켓 함수 오류 출력 후 종료
void err_quit(char *msg)
{
LPVOID lpMsgBuf;
FormatMessage(
  FORMAT_MESSAGE_ALLOCATE_BUFFER|
  FORMAT_MESSAGE_FROM_SYSTEM,
  NULL, WSAGetLastError(),
  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  (LPTSTR)&lpMsgBuf, 0, NULL);
MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR);
LocalFree(lpMsgBuf);
exit(-1);
}
 
// 소켓 함수 오류 출력
void err_display(char *msg)
{
LPVOID lpMsgBuf;
FormatMessage(
  FORMAT_MESSAGE_ALLOCATE_BUFFER|
  FORMAT_MESSAGE_FROM_SYSTEM,
  NULL, WSAGetLastError(),
  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  (LPTSTR)&lpMsgBuf, 0, NULL);
printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
}
 
// 소켓 함수 오류 출력
void err_display(int errcode)
{
LPVOID lpMsgBuf;
FormatMessage(
  FORMAT_MESSAGE_ALLOCATE_BUFFER|
  FORMAT_MESSAGE_FROM_SYSTEM,
  NULL, errcode,
  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  (LPTSTR)&lpMsgBuf, 0, NULL);
printf("[오류] %s", (LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
}




'C/C++' 카테고리의 다른 글

WSASend  (0) 2014.05.02
소켓 입출력 모델 - Overlapped 모델  (0) 2014.05.02
iocp주의점  (0) 2014.05.02
중첩된 io  (0) 2014.05.02
[본문스크랩] 파일의 CRC32 값 구하기 프로그래밍팁  (0) 2014.05.02
posted by 래머
2014. 5. 2. 01:04 C/C++



[본문스크랩] 파일의 CRC32 값 구하기 프로그래밍팁

2010/07/02 00:09 수정 삭제

작성자: 그라센(choi98772)

복사 http://blog.naver.com/choi98772/memo/130089128854

출처 별의꿈 | 태기
원문 http://blog.naver.com/tack0829/100026806875
Tuesday, May 23, 2006

[C언어] 비주얼C로, 파일의 CRC32 값 구하기 (빠르고 메모리 점유 없이)

제가 몇 년 전에 외국 사이트에서 CRC32 계산 코드를 구했는데, 실제 쓰려고 보니까 실용성에 문제가 있었습니다. 파일을 메모리에 모두 읽어들인 후에 계산하는 것이었습니다. 그래서 만약 700메가짜리 CD 이미지 파일의 CRC32를 구하려면 메모리가 700메가나 필요했습니다.

그래서 메모리에 로딩하지 않고 파일을 조금 조금씩 읽어서 계산하도록 만든 것이 아래에 소개하는 코드입니다. 버퍼로서 불과 32768 바이트만 필요합니다. 사실상 메모리를 전혀 요구하지 않는 것입니다. (삼성 이건희 회장의 얼굴이 파랗게 질릴 수도 있겠군요^^)



속도

어셈블리로 만들어진 CRC32 계산 프로그램의 속도와 같았습니다. 비주얼C/C++가 그만큼 최적화를 잘 해준다는 의미일 것입니다.



호환성

ZIP 과 RAR 의 CRC32 계산 값과 동일합니다.



사용법

컴파일하면 getCRC.exe 라는 파일이 만들어집니다. 명령 프롬프트 창에서,
getCRC <파일 이름>
이런 형식으로 사용하시면 됩니다.

파일 이름에 공백이 들어 있다면 쌍따옴표로 둘러싸 주어야 합니다. 그렇지 않으면 파일을 찾지 못하고 에러(Cannot open input file)를 출력합니다:

D:\Z>getcrc "Merriam-Webster Collegiate's Dictionary.iso"
A745B2FE: Merriam-Webster Collegiate's Dictionary.iso

D:\Z>getcrc Merriam-Webster Collegiate's Dictionary.iso
Cannot open input file.

D:\Z>




다음의 소스 파일 2개를
getCRC.cpp // 프로그램 본체
CRC32.cpp // CRC32 알고리즘 라이브러리
같은 디렉토리에 넣고

cl getCRC.cpp

이런 명령으로 컴파일하면 됩니다.



다음 창은 cl getCRC.cpp 로 컴파일함과 동시에, 컴파일된 파일에 옵션을 주어 한꺼번에 실행시키는 장면입니다.

D:\Z>cl getCRC.cpp & getCRC 0.htm
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

getCRC.cpp
Microsoft (R) Incremental Linker Version 7.10.3077
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:getCRC.exe
getCRC.obj
24531DE5: 0.htm
D:\Z>


0.htm 이라는 파일의 CRC32 값인 24531DE5 가 즉시 계산되었습니다.




소스 코드: getCRC.cpp



#include <stdio.h>
#include <stdlib.h>
#include "CRC32.cpp"

void help(void);
void error(int);


int main(int argc, char *argv[]) {
  FILE *in;

  if (argc == 1) help();
  if( (in = fopen(argv[1], "rb")) == NULL) error(1);

  printf("%08lX: %s\n", getFileCRC(in), argv[1]);

  fcloseall();
  return 0;
}



void help(void) {
  printf("\n\tGet the CRC32 of a given file\n\n\tUsage: this_program_name <file_name>\n");
  exit(1);
}


void error(int number) {
  switch (number) {
    case 1 : fputs("Cannot open input file.\n", stderr); break;
  }
  exit(number);
}





소스 코드: CRC32.cpp



unsigned long getFileCRC(FILE *);
unsigned long calcCRC (const unsigned char *, signed long, unsigned long);
void makeCRCtable(unsigned long *, unsigned long);


unsigned long getFileCRC(FILE *s) {
  unsigned char buf [32768];
  unsigned long CRC = 0;

  size_t len;
  while ( (len = fread(buf, 1, sizeof(buf), s)) != NULL )
    CRC = calcCRC(buf, (unsigned long) len, CRC);

  return CRC;
}


unsigned long calcCRC(const unsigned char *mem, signed long size, unsigned long CRC) {
  unsigned long table[256];

  CRC = ~CRC;
  makeCRCtable(table, 0xEDB88320);

  while(size--)
    CRC = table[(CRC ^ *(mem++)) & 0xFF] ^ (CRC >> 8);

  return ~CRC;
}


void makeCRCtable(unsigned long *table, unsigned long id) {
  unsigned long i, j, k;

  for(i = 0; i < 256; ++i) {
    k = i;
    for(j = 0; j < 8; ++j) {
      if (k & 1) k = (k >> 1) ^ id;
      else k >>= 1;
    }
    table[i] = k;
  }
}








벤치마크

4NT의 timer 명령으로 위의 프로그램의 속도를 측정해 보았습니다 (4NT가 설치되어 있어야 함):

[1] D:\Warehouse\My Virtual Machines>timer & getcrc "Windows 2000 Professional + MSO [v5.5].zip" & timer
Timer 1 on:  3:37:29p
6B517356: Windows 2000 Professional + MSO [v5.5].zip
Timer 1 off:  3:37:49p  Elapsed: 0:00:19.83

[0] D:\Warehouse\My Virtual Machines>


782MB (820,136,835 바이트) 라는 큰 파일인데도, 불과 20초가 걸렸습니다. 엄청난 속도입니다.




관련 게시물: [QnA] ZIP 이나 RAR 파일의, CRC32 란 무엇입니까? / 'CRC 에러'란? / CRC 값을 어떻게 구하나요?
 




'C/C++' 카테고리의 다른 글

WSASend  (0) 2014.05.02
소켓 입출력 모델 - Overlapped 모델  (0) 2014.05.02
iocp주의점  (0) 2014.05.02
중첩된 io  (0) 2014.05.02
IOCP예제  (0) 2014.05.02
posted by 래머
prev 1 2 next