책정리/윈도우 네트워크 프로그래밍

2장 윈도우 소켓 시작하기

GONII 2015. 4. 10. 15:29
  • 오류 처리

    네트워크 프로그램에서는 여러 원인으로 인해 오류가 발생할 수 있으며, 발생 활률 또한 비교적 높은 편이다. 따라서 함수 호출 시 오류 처릴르 철저히 하는 것은 물론, 구체적인 오류 내용을 사용자에게 알려주는 것은 매우 중요하다. 윈속 함수는 오류 처리 방법에 따라 다음과 같이 세 가지 유형으로 나눌 수 있다.

    • 오류 처리를 할 필요가 없는 경우

      리턴값이 없거나 호출 시 항상 성공하는 일부 소켓 함수

    • 리턴값으로 오류를 처리하는 경우

      WSAStartup() 함수

    • 리턴값으로 오류 발생을 확인하고, 구체적인 내용은 오류 코드를 이용하여 확인하는 경우

      대부분의 소켓 함수

    소켓 함수 호출 결과 오류가 발생했다면 WSAGetLastError( ) 함수를 이용하여 오류 코드를 얻을 수 있다.

    int WSAGetLastError(void);

    • 사용 예

      if ( 소켓함수(...) == 오류 )

      {

      int errcode = WSAGetLastError();

      printf(errorcode 에 해당하는 오류 메시지);

      }

    WSAGetLastError( ) 함수의 리턴값을 그대로 화면에 표시할 경우 사용자가 직접 오류 코드를 조사해야 햐는 불편이 따른다. 따라서 해당 오류 코드를 적절한 문자열 형태로 출력 하는 것이 바람직하다. FormatMessage( )함수를 사용하면 오류 코드를 오류 메시지로 자동으로 바꿀 수 있다.

    DWORD FormatMessage(

    DWORD dwFlags,

    LPCVOID lpSource,

    DWORD dwMessageID,

    DWORD dwLanguageID,

    LPTSTR lpBuffer,

    DWORD nSize,

    va_list* Arguments

    ); // 성공 : 오류 메시지의 길이, 실패 : 0

    • DWORD dwFlags

      FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM 값을 사용한다.

      FORMAT_MESSAGE_ALLOCATE_BUFFER는 오류 메시지를 저장할 공간을 FormatMessage( )함수가 알아서 할당

      FORMAT_MESSAGE_FROM_SYSTEM은 운영체제로부터 오류 메시지를 가져온다는 의미

    • LPCVOID lpSource

      NULL 사용

    • DWORD dwMessageID

      오류 코드를 나타내며 WSAGetLastError( )함수의 리턴값을 여기에 사용

    • DWORD dwLanguageID

      오류 메시지를 어떤 언어로 표시할 것인지 결정

      MAKELANGID(LANG_NEWTRAL, SUBLANG_DEFAULT)를 사용하면 사용자가 제어판에서 설정한 기본 언어로 오류 메시지를 얻음

    • LPTSTR lpBuffer

      오류 메시지의 시작 주소가 여기에 저장됨

      오류 메시지를 저장할 공간은 FormatMessage( )가 알아서 할당하므로 사용자는 주소값을 저장한 변수 주소만 여기에 넣어주면 됨

      오류 메시지 사용이 끝나면 LocalFree( )함수를 이용하여 시스템이 할당한 메모리를 반환해야 한다

    • DWORD nSize

      0 사용

    • va_list* Arguments

      NULL 사용

         

    • err_quit( ) 함수

void err_quit(char *msg)

{

LPVOID lpMsgBuf;

FormatMessage(

FORMAT_MESSAGE_ALLOCATE_BUFFER |

FORMAT_MESSAGE_FROM_SYSTEM,

NULL, WSAGetLastError(),

MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),

(LPSTR)&lpMsgBuf, 0, NULL);

MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR);

LocalFree(lpMsgBuf);

exit(-1);

}

   

// 사용 예

if( retval == SOCKET_ERROR) err_quit("bind()");

사소한 오류가 발생했을 때마다 애플리케이션이 종료된느 것은 바람직하지 않으므로 이 경우 err_quit( )함수보다는 err_display( )함수를 사용하는 것이 좋다.

  • err_display( ) 함수

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);

}

  • 윈속 초기화와 종료

    모든 윈속 프로그램에서 소켓 API를 호출하기 전에 반드시 윈속 초기화 함수인 WSAStartup( )을 제일 먼저 호출해야 한다. WSAStartup( )함수는 프로그램에서 사용할 윈속 버전을 요청함으로써 윈속 라이브러리(WS_32.DLL)를 초기화하는 역할을 한다.

    int WSAStartup(

    WORD wVersionRequested,

    LPWSADATA lpWSAData

    ); // 성공 : 0, 실패 : 오류 코드

    • WORD wVersionRequested,

      프로그램이 요구하는 최상위 윈속 버전이다. 하위 8비트에 주(major)버전, 상위 8비트에 부(minor) 버전을 넣어서 전달한다. 예를 들어 윈속3.2 버전 사용을 요청한다면 0x0203 또는 MAKEWORD(3, 2)를 사용한다.

    • LPWSADATA lpWSAData

      WSADATA 타입 변수의 주소를 전달하며 이를 통해 시스템에서 제공하는 윈속 구현에 대한 세부 사항을 얻을 수 있다.

    프로그램이 종료할 때는 윈속 종료 함수인 WSACleanup( )을 호출해야 한다. WSACleanup( )함수는 윈속 사용을 중지함을 운영체제에 알리고 관련 리소스를 반환하는 역할을 한다. 함수 호출이 실패할 경우 WSAGetLastError( ) 함수를 호출함으로써 구체적인 오류 코드를 얻을 수 있다.

    int WSACleanup(void); // 성공 : 0, 실패 : SOCKET_ERROR

       

    • 예제 InitWinsock.cpp

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

int main(int argc, char* argv[])

{

// 윈속 초기화

WSADATA wsa;

   

if( WSAStartup(MAKEWORD(2,2), &wsa) != 0 )

{

return -1;

}

MessageBox(NULL, "윈속 초기화 성공", "성공", MB_OK);

   

// 윈속 종료

WSACleanup();

return 0;

}

  • 소켓 생성과 닫기

    소켓 생성

    소켓을 이용하여 통신을 하기 위한 기본 요건은 통신 양단이 동일한 프로토콜을 사용하는 것이다. 예를 들면 TCP를 사용할 것인지 UDP를 사용할 것인지를 약속해야 한다.

    sock( )함수는 사용자가 요청한 프로토콜을 이용하여 통신을 할 수 있도록 내부적으로 리소스를 할당하고, 이를 접근할 수 있는 일종의 핸들값(SOCKET타입, 32비트 정수)을 리턴한다. 이 값을 소켓 디스크립터(socket descriptor)라 부르며, 각종 소켓 함수를 호출 할 때 인자로 전달하여 사용하게 된다.

    SOCKET socket{

    int af,

    int type,

    int protocol

    ); // 성공 : 새로운 소켓, 실패 : INVALID_SOCKET

    • int af

      주소 체계를 지정한다

    • int type

      소켓 타입을 지정한다, SOCK_STREAM 또는 SOCK_DGRAM

      SOCK_STREAM : TCP이고, SOCK_DGRAM : UDP이다

    • int protocol

      사용할 프로토콜을 지정한다.

    • 주소 체계

      통신을 하기 위해서는 통신 상대를 유일하게 지정할 수 있는 주소가 필요하다. 주소 체계(address family)란 이러한 주소 지정 방법을 지칭하는 용어다. 주소 체계는 네트워크 프로토콜의 종류에 따라 달라지므로, 주소 체계를 지정하는 것은 자신이 사용할 프로토콜을 선택하기 위한 첫번째 관문이 된다.

      winsock2.h파일에 AF_로 시작하는 상수값이 있는데 이 중 자신이 사용할 프로토콜에 해당하는 값을 선택하여 socket( )함수의 첫 번째 인자에 전달하면 된다.

#define AF_UNSPEC 0 // unspecified

#define AF_UNIX 1 // local to host (pipes, portals)

#define AF_INET 2 // internetwork: UDP, TCP, etc.

#define AF_IMPLINK 3 // arpanet imp addresses

#define AF_PUP 4 // pup protocols: e.g. BSP

#define AF_CHAOS 5 // mit CHAOS protocols

#define AF_NS 6 // XEROX NS protocols

#define AF_IPX AF_NS // IPX protocols: IPX, SPX, etc.

#define AF_ISO 7 // ISO protocols

#define AF_OSI AF_ISO // OSI is ISO

.....

  • 소켓 타입

    소켓 타입은 사용할 프로토콜의 특성을 나타내는 값으로, 이 중 자주 사용되는 것을 요약하면 다음과 같다

소켓 타입

특성

SOCK_STREAM

신뢰성 있는 데이터 전송 기능 제공, 연결형 프로토콜

SOCK_DGRAM

비신뢰적인 데이터 전손 기능 제공, 비연결형 프로토콜

소켓 타입은 네트워크 프로토콜의 종류에 따라 달라지므로, 소켓 타입을 지정하는 것은 자신이 사용할 프로토콜을 선택하기 위한 두 번째 관문이 된다.

주소 체계가 같더라도 소켓 타입을 다르게 설정할 수 있고, 결과적으로 사용할 프로토콜의 종류가 달라질 수 있다

사용할 프로토콜

주소 체계

소켓 타입

TCP

AF_INET

SOCK_STREAM

UDP

AF_INET

SOCK_DGRAM

  • 프로토콜

    주소 체계와 소켓 타입을 결정하면 사용할 프로토콜을 유일하게 선택할 수 있는 경우가 있다. 그러나 일반적으로 주소 체계와 소켓 타입이 같더라도 이에 해당하는 프로토콜이 두 개 이상 존재할 수 있다. 따라서 이 경우에는 프로토콜을 명시적으로 지정해야 하며, socket( )함수의 세 번째 인자가 이러한 역할을 한다.

사용할 프로토콜

주소 체계

소켓 타입

프로토콜

TCP

AF_INET

SOCK_STREAM

IPPROTO_TCP

UDP

AF_INET

SOCK_DGRAM

IPPROTO_UDP

그러나 사용할 프로토콜을 결정하는 데 모호함이 없을 경우 다음과 같이 프로토콜 부분에 0을 사용해도 된다.

사용할 프로토콜

주소 체계

소켓 타입

프로토콜

TCP

AF_INET

SOCK_STREAM

0

UDP

AF_INET

SOCK_DGRAM

0

소켓 닫기

소켓을 이용한 통신이 끝나면 관련 리소스를 반환해야 한다. closesocket( )함수는 해당 소켓을 닫고 관련 리소스를 반환하는 역할을 한다.

int closesocket( SOCKET s ); // 성공 : 0, 실패 : SOCKET_ERROR

   

  • 예제 InitWinsock2.cpp

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

   

// 소켓 함수 오류 출력 후 종료

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);

}

   

int main(int argc, char* argv[])

{

// 윈속 초기화

WSADATA wsa;

   

if( WSAStartup(MAKEWORD(2,2), &wsa) != 0 )

{

return -1;

}

MessageBox(NULL, "윈속 초기화 성공", "성공", MB_OK);

   

// socket()

SOCKET tcp_sock = socket(AF_INET, SOCK_STREAM, 0);

if(tcp_sock == INVALID_SOCKET)

{

err_quit("socket()");

}

   

// closesocket()

closesocket(tcp_sock);

   

// 윈속 종료

WSACleanup();

return 0;

}

반응형

'책정리 > 윈도우 네트워크 프로그래밍' 카테고리의 다른 글

5장 멀티스레드  (0) 2015.04.20
4장 TCP 서버/클라이언트 구조  (0) 2015.04.13
3장 소켓 주소 구조체 다루기  (0) 2015.04.11
1장 네트워크와 소켓 프로그래밍  (0) 2015.04.07
목차  (0) 2015.04.07