물마중

Windows에서 소켓프로그래밍하기-기초 본문

Programming/Windows System

Windows에서 소켓프로그래밍하기-기초

zweistar2 2011. 2. 17. 13:49

으흠~~ 소켓프로그래밍을 할 때 꼭 들어가야 하는 필수 함수들을 정리해보고 가자~~!!

(이렇게 정리를 하고 가면 나중에 까먹어도 쉽게 찾을 수 있으니깐~~)

Let's~~~~~~~~~~~~~~~ GO~!!!

==========================================================================================

 소켓 통신을 할 때 윈도우즈에서 가장 먼저 시행되어야 할 함수는 WSAStartup()함수이다.

머하는 함수인고 하니...

사용할 윈도우소켓의 버전을 명시하고, 소켓 관련 정보들을 리턴하는 함수.

  이 함수를 실행시키지 않으면 다른 소켓관련 함수들을 쓸 수 없다고 한다. 인자 값으로는 2개의 인자가 들어간다. 인자의 용도와 인자의 쓰임을 정리해보자.

원형 : WSAStartup(
                     
WORD wVersionRequested,
                     
LPWSADATA lpWSAData
        
);

인자 : WORD wVersionRequested =-> 사용할 윈도우 소켓의 버전을 명시

         LPWSADATA lpWSAData =-> 소켓 정보를 저장할 공간

  간단한 팁을 써보면 소켓의 버전을 명시할 때 상위는 마이너 버전이고, 하위는 메이저 버전을 뜻하기 때문에 MAKEWORD()를 이용하면 쉽게 버전을 만들수 있다. 함수 사용은 아래 있는 예제를 참고하자.!!

  그리고 마지막에는 꼭 치워줘야 하겠지.. 이 때 사용하는 함수가 WSACleanup()이다. 인자는 없고 WSAStartup()이 나오면 반드시 마지막에 WSACleanup()으로 마무리 짓자.

 이제부터 진짜 소켓을 생성하고 데이터를 주고받는 함수들이다.~

  소켓을 생성할 차례이다. 소켓을 생성할 때 사용하는 함수는 socket()이다. 원형을 살펴보자.

원형 : SOCKET WSAAPI socket(
                      
 __in int af,
                     
__in int type,
                     
__in int protocol
         
)

  3개의 인자를 사용하는데 각 인자에 대해서 정리하여보자.

 

1. __in int af
MSDN에서는 The address family specification이라고 정의내리고 있는데 주소 가족?? 무슨 말인지 모르겠지만 인자로 쓰이는 아래 네모 표를 보면 대충 이해할 수 있을 것이다. 

 AF_UNSPEC  The address family is unspecified
 AF_INET  The IPv4 address family
 AF_NETBIOS  The NetBIOS address family
 AF_INET6  The IPv6 address family

   표의 설명은 MSDN에 있는 설명을 짤막하게 가져왔다. 더 많은 내용이 있지만 그 부분은 MSDN(너무~ 좋아 *^^*)을 참고하기 바란다.

  표의 내용을 보면 감이 오지 않는가?? 첫 번째 변수는 어떤 형태의 네트워크인가를 나타내고 있다. 현재는 대부분 IPv4를 사용하기 때문에 AF_INET으로 설정하면 될 것이다. 이제 곧 AF_INET6를 쓸 날이 오겠지만~~~.. = )

 2. __in int type
만들어질 소켓의 유형을 정의하는 인자이다. 어떤 타입이 있는지 살펴보자.

 SOCK_STREAM  TCP
 SOCK_DGRAM  UDP
 SOCK_RAW  raw socket that allows an application to manipulate the next upper-layer protocol header.
 SOCK_RDM   a reliable message datagram.
 SOCK_SEQPACKET   pseudo-stream packet based on datagrams.

 음.. 역시 영어는 어렵다. 가장 많이 쓰이는 타입은 당연히 SOCK_STREAM과 SOCK_DGRAM이다. 나중에 이 쪽에 관련된 일을 하게 된다면 다른 형태의 타입도 사용해보겠지만 현재는 TCP와 UDP에서 사용되는 2가지만 알아도 무사통과이지 싶다. 고로 더 자세한 내용은 패쓰..(여기는 기초부분이므로 =- )

 3. __in int protocol
헛.. 이 넘은 머하는 놈일까? 우선 0으로 셋팅하면 기본적인 소켓프로그래밍에서는 지장이 없어 보인다. MSDN에서는 이 넘을 0으로 설정하면 특별한 프로토콜을 규정하기를 원하지 않는다고 한다. 그리고 서비스가 사용하기 위한 프로토콜을 선택할 것이라고 되어있다.(오역이면 지적 좀.. ) 타입이 4종류가 나와있지만 기초부분에서 다룰 필요가 없어 보이니 넘어가도록 하자.

  socket()은 정상적으로 수행하였다면 소켓 디스크립터를 반환한다. 이 디스크립터는 데이터를 전송하거나 받을 때 사용하는 send()와 recv()에서 사용된다. socket()으로 소켓을 생성하였다면 소스 마지막 부분에 closesocket(SOCKET s)를 이용하여 소켓을 닫아줘야 한다. !_!

  자.. 소켓을 만들었으면 만들어진 소켓과 타켓을 연결해야 겠다. 이 행위를 위해 존재하는 함수가 connect()이다. 그럼 함수가 나왔으니 또 원형을 살펴볼까~~ (힘들구나.. 헥헥..)

원형 : int connect(
                     
__in SOCKET s,
                      __in const struct sockaddr *name,
                      __in int namelen
         );

  3개의 인자를 넘겨받아서 수행되는 함수이다. 각 인자를 살펴볼까~ (후비고~~!)

1. __in SOCKET s
다들 쉽게 눈치 챘을 거 같다. ㅋㅋ 바로 위에서 알아본 socket() 함수의 리턴 값이다.

2. __in const struct sockaddr *name
타켓의 주소를 지정하는 곳으로 sockaddr 구조체의 포인터를 입력하면 된다. inet_addr()을 이용하면 문자열로 된 IP주소를 sockaddr구조체로 바꿀 수 있다.

3. __in int namelen
sockaddr 구조체의 크기를 지정해주면 된다.

리턴값 : 정상적인 수행을 완료했으면 0을 리턴한다.

  connect()에 대해서도 알아보았다. connect()는 socket()에 비하여 비교적 알아볼 내용이 적었다. connect()가 정상적으로 수행되었다면 타켓 호스트와 데이터를 주고 받을 수 있다.

  이제 실질적으로 데이터를 주고 받는 기초적인 함수인 recv()와 send()를 살펴보자.

  우선 recv()부터닷~!

원형 : int recv(
                     
__in SOCKET s,
                     
__out char *buf,
                     
__int int len,
                     
__in int flags
        
);

  4개나 되는 인자를 사용하는 구나... 위의 함수들 처럼 자세히 하지 않고 간단하게 집고 넘어가자.

  첫 번쨰 변수야 다들알 것이니 패쓰(모르겠다면 다시 위로~), 두 번쨰 변수는 받은 데이터를 저장할 장소이고 세 번째 변수는 받을 데이터의 크기이다. 4번째 인수는 왜 사용하는지 잘 모르겠다. 저 값이 설정되는 예제를 본적도 없고 말이다. __;; 리턴 값은 실제 읽어들인 바이트의 개수이다.

 

  정말 간단히 알아보았다(너무 길어져서 ㅠ_ㅠ). 다음은 send를 알아보자.!

원형 : int send(
                      __in SOCKET s,
                      __in const char *buf,
                      __in int len,
                      __in int flags
         )

  이 함수는 recv와 반대의 역할을 수행한다. 첫 번째 인자는 동일하고 두 번째와 세 번째 인자는 recv와 반대로 전송하기 위한 데이터의 위치와 데이터의 크기이다.

 

==========================================================================================

 

  아흠 긴 여정이였다.. 이번 정리에서 아쉬운 점은 함수들의 인자로 쓰이는 다양한 인자들의 쓰임세와 에러들을 모두 정의해보지 못한 것이다. 윈도우즈 소켓프로그래밍은 소켓에 관련된 함수가 에러를 내면 WSAGetLastError()을 이용하여 확인할 수 있다.

 

  마지막으로 vortex0번 문제 풀 때 사용한 예제를 올리고 맞추겠다.

 

 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

int main(void)
{
      WSADATA      wsadata;
       SOCKET      sock_p;
       SOCKADDR_IN       servaddr;
       char      result[256] = {0};
       char* serv_ip = "69.55.233.89";
       unsigned int sum=0, num;
       unsigned short serv_port = 5842;
       int      i; 

       WSAStartup(MAKEWORD(2, 2), &wsadata);

       sock_p = socket(PF_INET, SOCK_STREAM, 0);

       memset(&servaddr, 0, sizeof(servaddr));

       servaddr.sin_family = AF_INET;
       servaddr.sin_addr.s_addr = inet_addr(serv_ip);
       servaddr.sin_port = htons(serv_port);
 
       connect(sock_p, (SOCKADDR *)&servaddr, sizeof(servaddr));

       for (i = 0; i < 4; i++) {

           recv(sock_p, (char *)&num, sizeof(unsigned int), 0);
           sum += num; 
      }

       send(sock_p, (char *)&sum, sizeof(unsigned int), 0);
       recv(sock_p, (char *)result, sizeof(result), 0);

       printf(result);

       closesocket(sock_p);
       WSACleanup();

       return 0;
}